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
.keepmarkers. - 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
namespacesubdirectory 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— callhandle.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
new WebFsStorageRepository(options): WebFsStorageRepository;Parameters
| Parameter | Type |
|---|---|
options | WebFsStorageRepositoryOptions |
Returns
WebFsStorageRepository
Overrides
Methods
createFolder()
createFolder(folderCreate): LaikaTask<{
createdAt?: string;
key: string;
type: "folder";
updatedAt?: string;
}>;Parameters
| Parameter | Type |
|---|---|
folderCreate | { key: string; type?: "folder"; } |
folderCreate.key | string |
folderCreate.type? | "folder" |
Returns
LaikaTask<{ createdAt?: string; key: string; type: "folder"; updatedAt?: string; }>
Overrides
StorageRepository.createFolder
createObject()
createObject(create): LaikaTask<{
content: {
[key: string]: any;
};
createdAt?: string;
key: string;
metadata?: {
extension?: string;
revisionId?: string;
};
type: "object";
updatedAt?: string;
}>;Parameters
| Parameter | Type | Description |
|---|---|---|
create | { content: { [key: string]: any; }; key: string; metadata?: { extension?: string; revisionId?: string; }; type?: "object"; } | - |
create.content | { [key: string]: any; } | - |
create.key | string | - |
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? | string | File 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? | string | Backend-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()
createOrUpdateObject(create): LaikaTask<{
content: {
[key: string]: any;
};
createdAt?: string;
key: string;
metadata?: {
extension?: string;
revisionId?: string;
};
type: "object";
updatedAt?: string;
}>;Parameters
| Parameter | Type | Description |
|---|---|---|
create | { content: { [key: string]: any; }; key: string; metadata?: { extension?: string; revisionId?: string; }; type?: "object"; } | - |
create.content | { [key: string]: any; } | - |
create.key | string | - |
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? | string | File 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? | string | Backend-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()
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
| Parameter | Type |
|---|---|
key | string |
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
getCapabilities()
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()
getFolder(key): LaikaTask<{
createdAt?: string;
key: string;
type: "folder";
updatedAt?: string;
}>;Parameters
| Parameter | Type |
|---|---|
key | string |
Returns
LaikaTask<{ createdAt?: string; key: string; type: "folder"; updatedAt?: string; }>
Overrides
getObject()
getObject(key): LaikaTask<{
content: {
[key: string]: any;
};
createdAt?: string;
key: string;
metadata?: {
extension?: string;
revisionId?: string;
};
type: "object";
updatedAt?: string;
}>;Parameters
| Parameter | Type |
|---|---|
key | string |
Returns
LaikaTask<{ content: { [key: string]: any; }; createdAt?: string; key: string; metadata?: { extension?: string; revisionId?: string; }; type: "object"; updatedAt?: string; }>
Overrides
listAtoms()
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
| Parameter | Type |
|---|---|
folderKey | string |
options | ListAtomsOptions |
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
listAtomSummaries()
listAtomSummaries(folderKey, options): LaikaStream<
| {
createdAt?: string;
key: string;
type: "object-summary";
updatedAt?: string;
}
| {
createdAt?: string;
key: string;
type: "folder-summary";
updatedAt?: string;
}, LaikaDone>;Parameters
| Parameter | Type |
|---|---|
folderKey | string |
options | ListAtomsOptions |
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()
removeAtoms(keys): LaikaStream<string, RemoveAtomsDone>;Parameters
| Parameter | Type |
|---|---|
keys | readonly string[] |
Returns
LaikaStream<string, RemoveAtomsDone>
Overrides
subscribeChanges()
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
| Parameter | Type |
|---|---|
options | SubscribeChangesOptions |
listener | ChangeListener |
Returns
Inherited from
StorageRepository.subscribeChanges
updateObject()
updateObject(update): LaikaTask<{
content: {
[key: string]: any;
};
createdAt?: string;
key: string;
metadata?: {
extension?: string;
revisionId?: string;
};
type: "object";
updatedAt?: string;
}>;Parameters
| Parameter | Type | Description |
|---|---|---|
update | { content?: { [key: string]: any; }; key: string; metadata?: { extension?: string; revisionId?: string; }; type?: "object"; } | - |
update.content? | { [key: string]: any; } | - |
update.key | string | - |
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? | string | File 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? | string | Backend-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; }>