Files
plainleaf/lib/spaces/datastore_space_primitives.ts
T
Zef HemelandGitHub dc2987a3f9 Sync engine rearchitecture (#1516)
* 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
2025-09-12 14:43:57 +02:00

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;
}
}