Skip to content

TypeScript API / laikacms/storage/web-fs / WebFsStorageRepository

Class: WebFsStorageRepository ​

WebFsStorageRepository implements the StorageRepository contract against the browser's File System API — any FileSystemDirectoryHandle, whether it points into the origin-private file system (the default), a user-picked showDirectoryPicker() directory (Chromium then reads and writes real on-disk files), or an injected shim — so LaikaCMS content can be read and written directly in the browser with no server involved.

The File System API exposes a real hierarchical file system, so unlike the flat-store implementations (storage-r2, storage-web) nothing is simulated:

  • Folders are real directories; empty folders exist without .keep markers.
  • Object contents are stored as raw files (readable by any other consumer of the same directory tree), not JSON envelopes.
  • Every operation is scoped under a real namespace subdirectory so distinct repositories can share one root without colliding.

The default backing root (navigator.storage.getDirectory()) is resolved lazily, on first use — never at import or construction time — so this module and an unused repository instance are safe to include in isomorphic/SSR bundles.

Permissions & handle validity ​

The repository does not care where a handle came from — freshly picked, restored from wherever the application persists handles (also in a service worker, where permission prompts are unavailable), or origin-private. Whatever was true when the handle was obtained may not be true now: policies change, browsers change, permissions change. So before every operation the repository re-checks queryPermission({ mode }) when the handle exposes it (handles without it are treated as granted) and verifies the handle still connects to an existing directory. It never calls requestPermission() — that needs a user gesture — and instead fails with a typed error that tells the application how to recover:

  • PermissionPromptRequiredError — call handle.requestPermission({ mode }) in a user gesture, then retry the operation.
  • PermissionDeniedError — access was refused (or revoked mid-session); the user must grant it anew.
  • StaleHandleError — the directory was deleted/moved or the persisted handle expired; have the user pick the directory again and replace the stored handle.

A root provider throwing (e.g. the application has no handle stored yet) surfaces as a typed IllegalStateException, so every failure mode of "give me a usable directory" is distinguishable by the caller.

See WebFsStorageRepositoryOptions for the security note on the backing directory not being a secret store.

Extends ​

Constructors ​

Constructor ​

ts
new WebFsStorageRepository(options): WebFsStorageRepository;

Parameters ​

ParameterType
optionsWebFsStorageRepositoryOptions

Returns ​

WebFsStorageRepository

Overrides ​

StorageRepository.constructor

Methods ​

createFolder() ​

ts
createFolder(folderCreate): LaikaTask<{
  createdAt?: string;
  key: string;
  type: "folder";
  updatedAt?: string;
}>;

Parameters ​

ParameterType
folderCreate{ key: string; type?: "folder"; }
folderCreate.keystring
folderCreate.type?"folder"

Returns ​

LaikaTask<{ createdAt?: string; key: string; type: "folder"; updatedAt?: string; }>

Overrides ​

StorageRepository.createFolder


createObject() ​

ts
createObject(create): LaikaTask<{
  content: {
   [key: string]: any;
  };
  createdAt?: string;
  key: string;
  metadata?: {
     extension?: string;
     revisionId?: string;
  };
  type: "object";
  updatedAt?: string;
}>;

Parameters ​

ParameterTypeDescription
create{ content: { [key: string]: any; }; key: string; metadata?: { extension?: string; revisionId?: string; }; type?: "object"; }-
create.content{ [key: string]: any; }-
create.keystring-
create.metadata?{ extension?: string; revisionId?: string; }Optional capability-driven metadata hints. Read by the storage's determineExtension callback to decide the on-disk format.
create.metadata.extension?stringFile extension to use when (de)serializing this object. Active on backends whose Capabilities.fileExtensions.supported is true. Must match a key in Capabilities.fileExtensions.supportedExtensions. On writes: hint to the backend to use this extension's serializer. On reads: the extension the backend used to (de)serialize the object.
create.metadata.revisionId?stringBackend-specific revision identifier (e.g., GitHub commit SHA, R2 ETag, version ID). Active on backends that track per-object revisions.
create.type?"object"-

Returns ​

LaikaTask<{ content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }>

Overrides ​

StorageRepository.createObject


createOrUpdateObject() ​

ts
createOrUpdateObject(create): LaikaTask<{
  content: {
   [key: string]: any;
  };
  createdAt?: string;
  key: string;
  metadata?: {
     extension?: string;
     revisionId?: string;
  };
  type: "object";
  updatedAt?: string;
}>;

Parameters ​

ParameterTypeDescription
create{ content: { [key: string]: any; }; key: string; metadata?: { extension?: string; revisionId?: string; }; type?: "object"; }-
create.content{ [key: string]: any; }-
create.keystring-
create.metadata?{ extension?: string; revisionId?: string; }Optional capability-driven metadata hints. Read by the storage's determineExtension callback to decide the on-disk format.
create.metadata.extension?stringFile extension to use when (de)serializing this object. Active on backends whose Capabilities.fileExtensions.supported is true. Must match a key in Capabilities.fileExtensions.supportedExtensions. On writes: hint to the backend to use this extension's serializer. On reads: the extension the backend used to (de)serialize the object.
create.metadata.revisionId?stringBackend-specific revision identifier (e.g., GitHub commit SHA, R2 ETag, version ID). Active on backends that track per-object revisions.
create.type?"object"-

Returns ​

LaikaTask<{ content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }>

Overrides ​

StorageRepository.createOrUpdateObject


getAtom() ​

ts
getAtom(key): LaikaTask<
  | {
  createdAt?: string;
  key: string;
  type: "folder";
  updatedAt?: string;
}
  | {
  content: {
   [key: string]: any;
  };
  createdAt?: string;
  key: string;
  metadata?: {
     extension?: string;
     revisionId?: string;
  };
  type: "object";
  updatedAt?: string;
}>;

Probes getObject first; only a NotFoundError triggers the folder fallback. Any other failure (e.g. IllegalStateException from a missing default root during SSR) propagates as-is rather than being masked by a swallowed boolean check.

Parameters ​

ParameterType
keystring

Returns ​

LaikaTask< | { createdAt?: string; key: string; type: "folder"; updatedAt?: string; } | { content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }>

Overrides ​

StorageRepository.getAtom


getCapabilities() ​

ts
getCapabilities(): LaikaTask<{
  changes:   | {
     description: string;
     supported: false;
   }
     | {
     changeFeed: boolean;
     description: string;
     subscription: boolean;
     supported: true;
     syncToken: boolean;
   };
  compatibilityDate: string & Brand<"StorageRepositoryCompatibilityDate">;
  fileExtensions:   | {
     description: string;
     supported: false;
   }
     | {
     description: string;
     supported: true;
     supportedExtensions: {
      [key: string]: object;
     };
   };
  pagination:   | {
     description: string;
     supported: false;
   }
     | {
     description: string;
     styles: {
        cursor: boolean;
        offset: boolean;
        page: boolean;
     };
     supported: true;
   };
}>;

Returns ​

LaikaTask<{ changes: | { description: string; supported: false; } | { changeFeed: boolean; description: string; subscription: boolean; supported: true; syncToken: boolean; }; compatibilityDate: string & Brand<"StorageRepositoryCompatibilityDate">; fileExtensions: | { description: string; supported: false; } | { description: string; supported: true; supportedExtensions: { [key: string]: object; }; }; pagination: | { description: string; supported: false; } | { description: string; styles: { cursor: boolean; offset: boolean; page: boolean; }; supported: true; }; }>

Overrides ​

StorageRepository.getCapabilities


getFolder() ​

ts
getFolder(key): LaikaTask<{
  createdAt?: string;
  key: string;
  type: "folder";
  updatedAt?: string;
}>;

Parameters ​

ParameterType
keystring

Returns ​

LaikaTask<{ createdAt?: string; key: string; type: "folder"; updatedAt?: string; }>

Overrides ​

StorageRepository.getFolder


getObject() ​

ts
getObject(key): LaikaTask<{
  content: {
   [key: string]: any;
  };
  createdAt?: string;
  key: string;
  metadata?: {
     extension?: string;
     revisionId?: string;
  };
  type: "object";
  updatedAt?: string;
}>;

Parameters ​

ParameterType
keystring

Returns ​

LaikaTask<{ content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }>

Overrides ​

StorageRepository.getObject


listAtoms() ​

ts
listAtoms(folderKey, options): LaikaStream<
  | {
  createdAt?: string;
  key: string;
  type: "folder";
  updatedAt?: string;
}
  | {
  content: {
   [key: string]: any;
  };
  createdAt?: string;
  key: string;
  metadata?: {
     extension?: string;
     revisionId?: string;
  };
  type: "object";
  updatedAt?: string;
}, LaikaDone>;

Parameters ​

ParameterType
folderKeystring
optionsListAtomsOptions

Returns ​

LaikaStream< | { createdAt?: string; key: string; type: "folder"; updatedAt?: string; } | { content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }, LaikaDone>

Overrides ​

StorageRepository.listAtoms


listAtomSummaries() ​

ts
listAtomSummaries(folderKey, options): LaikaStream<
  | {
  createdAt?: string;
  key: string;
  type: "object-summary";
  updatedAt?: string;
}
  | {
  createdAt?: string;
  key: string;
  type: "folder-summary";
  updatedAt?: string;
}, LaikaDone>;

Parameters ​

ParameterType
folderKeystring
optionsListAtomsOptions

Returns ​

LaikaStream< | { createdAt?: string; key: string; type: "object-summary"; updatedAt?: string; } | { createdAt?: string; key: string; type: "folder-summary"; updatedAt?: string; }, LaikaDone>

Overrides ​

StorageRepository.listAtomSummaries


removeAtoms() ​

ts
removeAtoms(keys): LaikaStream<string, RemoveAtomsDone>;

Parameters ​

ParameterType
keysreadonly string[]

Returns ​

LaikaStream<string, RemoveAtomsDone>

Overrides ​

StorageRepository.removeAtoms


subscribeChanges() ​

ts
subscribeChanges(options, listener): Unsubscribe;

Subscribe to live change notifications for a scope (a folder, or the whole store when omitted). listener is invoked with a coalesced, non-empty batch of ChangeSummary entries each time the scope changes. Returns an Unsubscribe that stops delivery; when the last listener leaves, the repository releases any underlying watch resource.

This is the PUSH counterpart to the pull getSyncToken / listChanges feed. Non-abstract on purpose: existing subclasses stay source-compatible. The base implementation throws NotImplementedError; check getCapabilities().changes (the enabled variant's subscription flag) before calling.

Parameters ​

ParameterType
optionsSubscribeChangesOptions
listenerChangeListener

Returns ​

Unsubscribe

Inherited from ​

StorageRepository.subscribeChanges


updateObject() ​

ts
updateObject(update): LaikaTask<{
  content: {
   [key: string]: any;
  };
  createdAt?: string;
  key: string;
  metadata?: {
     extension?: string;
     revisionId?: string;
  };
  type: "object";
  updatedAt?: string;
}>;

Parameters ​

ParameterTypeDescription
update{ content?: { [key: string]: any; }; key: string; metadata?: { extension?: string; revisionId?: string; }; type?: "object"; }-
update.content?{ [key: string]: any; }-
update.keystring-
update.metadata?{ extension?: string; revisionId?: string; }Optional capability-driven metadata hints. May carry a revisionId from a prior read for optimistic concurrency on backends that support it.
update.metadata.extension?stringFile extension to use when (de)serializing this object. Active on backends whose Capabilities.fileExtensions.supported is true. Must match a key in Capabilities.fileExtensions.supportedExtensions. On writes: hint to the backend to use this extension's serializer. On reads: the extension the backend used to (de)serialize the object.
update.metadata.revisionId?stringBackend-specific revision identifier (e.g., GitHub commit SHA, R2 ETag, version ID). Active on backends that track per-object revisions.
update.type?"object"-

Returns ​

LaikaTask<{ content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }>

Overrides ​

StorageRepository.updateObject

Released under the MIT License.