* Don't require auth for .ping * Finally introducing loggers * Remove file list caching from disk_space_primitives I don't think this is required anymore with the sync model * Simplification of space primitives and more tests * More reliable online status * Add .zed settings * Remove selfUpdate argument from `writeFile` no longer used - Add `sync` config schema and document sync options - Move sync config handling to client boot logic - Move BootConfig type to ui_types.ts - Remove sync env vars from server - Update documentation for sync and architecture * Improve sync logic and proxy routing for full sync state * Add BoxProxy for late-bound client and improve Lua boot - Add isMobile syscall for editor * Add concurrency to sync process and log indexing duration * Better service worker lifecycle management
121 lines
3.0 KiB
TypeScript
121 lines
3.0 KiB
TypeScript
import type { SpacePrimitives } from "./space_primitives.ts";
|
|
import { mime } from "mimetypes";
|
|
import type { FileMeta } from "../../type/index.ts";
|
|
import { notFoundError } from "../constants.ts";
|
|
import type { KvPrimitives } from "../data/kv_primitives.ts";
|
|
|
|
const filesMetaPrefix = ["meta"];
|
|
const filesContentPrefix = ["content"];
|
|
|
|
export class DataStoreSpacePrimitives implements SpacePrimitives {
|
|
constructor(
|
|
private kv: KvPrimitives,
|
|
) {
|
|
}
|
|
|
|
async fetchFileList(): Promise<FileMeta[]> {
|
|
const fileMetas: FileMeta[] = [];
|
|
// Iterate over all keys with the fileMetaPrefix
|
|
for await (const meta of this.kv.query({ prefix: filesMetaPrefix })) {
|
|
fileMetas.push(this.cleanFileMeta(meta.value as FileMeta));
|
|
}
|
|
return fileMetas;
|
|
}
|
|
|
|
async readFile(
|
|
path: string,
|
|
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
|
// Fetch content and metadata in parallel
|
|
const [fileMeta, fileContent] = await this.kv.batchGet([
|
|
[
|
|
...filesMetaPrefix,
|
|
path,
|
|
],
|
|
[
|
|
...filesContentPrefix,
|
|
path,
|
|
],
|
|
]);
|
|
if (!fileMeta) {
|
|
throw notFoundError;
|
|
}
|
|
|
|
return {
|
|
meta: this.cleanFileMeta(fileMeta),
|
|
data: fileContent,
|
|
};
|
|
}
|
|
|
|
async writeFile(
|
|
path: string,
|
|
data: Uint8Array,
|
|
suggestedMeta?: FileMeta,
|
|
): Promise<FileMeta> {
|
|
let meta: FileMeta | undefined;
|
|
try {
|
|
// Build off of the existing file meta, if file exists
|
|
meta = await this.getFileMeta(path);
|
|
} catch {
|
|
// Not found, that's fine
|
|
}
|
|
if (!meta) {
|
|
// No existing meta data, let's set some defaults
|
|
meta = {
|
|
name: path,
|
|
created: suggestedMeta?.lastModified || Date.now(),
|
|
perm: "rw",
|
|
contentType: mime.getType(path) || "application/octet-stream",
|
|
// Overwritten in a sec
|
|
lastModified: 0,
|
|
size: 0,
|
|
};
|
|
}
|
|
meta.lastModified = suggestedMeta?.lastModified || Date.now();
|
|
meta.size = data.byteLength;
|
|
if (suggestedMeta?.perm) {
|
|
meta.perm = suggestedMeta.perm;
|
|
}
|
|
|
|
// Write metadata and content in same transaction
|
|
await this.kv.batchSet([
|
|
{
|
|
key: [...filesMetaPrefix, path],
|
|
value: meta,
|
|
},
|
|
{
|
|
key: [...filesContentPrefix, path],
|
|
value: data,
|
|
},
|
|
]);
|
|
return meta;
|
|
}
|
|
|
|
async deleteFile(path: string): Promise<void> {
|
|
const [fileMeta] = await this.kv.batchGet([
|
|
[...filesMetaPrefix, path],
|
|
]);
|
|
if (!fileMeta) {
|
|
throw notFoundError;
|
|
}
|
|
return this.kv.batchDelete([
|
|
[...filesMetaPrefix, path],
|
|
[...filesContentPrefix, path],
|
|
]);
|
|
}
|
|
|
|
async getFileMeta(path: string, _observing?: boolean): Promise<FileMeta> {
|
|
const [fileMeta] = await this.kv.batchGet([[...filesMetaPrefix, path]]);
|
|
if (!fileMeta) {
|
|
throw notFoundError;
|
|
}
|
|
return this.cleanFileMeta(fileMeta);
|
|
}
|
|
|
|
cleanFileMeta(fileMeta: FileMeta): FileMeta {
|
|
if (!fileMeta.created) {
|
|
fileMeta.created = fileMeta.lastModified;
|
|
}
|
|
return fileMeta;
|
|
}
|
|
}
|