@bandeira-tech/b3nd-save 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/byte-entity-shim.d.ts +47 -0
- package/esm/byte-entity-shim.d.ts.map +1 -0
- package/esm/byte-entity-shim.js +138 -0
- package/esm/clients/mod.d.ts +15 -0
- package/esm/clients/mod.d.ts.map +1 -0
- package/esm/clients/mod.js +14 -0
- package/esm/clients/save-client.d.ts +87 -0
- package/esm/clients/save-client.d.ts.map +1 -0
- package/esm/clients/save-client.js +174 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts +40 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js +31 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts +30 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.js +70 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts +16 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.js +50 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts +235 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.js +619 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts +43 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.js +54 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts +47 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.js +134 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts +20 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.js +34 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts +46 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.js +131 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts +27 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.js +25 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts +59 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.js +188 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts +35 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.js +57 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts +117 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.js +125 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts +112 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.js +21 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts +35 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.js +106 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts +88 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.js +86 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts +83 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.js +115 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts +164 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.js +67 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts +99 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.js +190 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts +185 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.js +103 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts +58 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.js +64 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts +295 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.js +898 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts +167 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.js +5 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts +278 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js +89 -0
- package/esm/dispatch.d.ts +35 -0
- package/esm/dispatch.d.ts.map +1 -0
- package/esm/dispatch.js +40 -0
- package/esm/elasticsearch/mod.d.ts +31 -0
- package/esm/elasticsearch/mod.d.ts.map +1 -0
- package/esm/elasticsearch/mod.js +8 -0
- package/esm/elasticsearch/store.d.ts +44 -0
- package/esm/elasticsearch/store.d.ts.map +1 -0
- package/esm/elasticsearch/store.js +213 -0
- package/esm/entity-store.d.ts +106 -0
- package/esm/entity-store.d.ts.map +1 -0
- package/esm/entity-store.js +46 -0
- package/esm/entity.d.ts +125 -0
- package/esm/entity.d.ts.map +1 -0
- package/esm/entity.js +66 -0
- package/esm/errors.d.ts +23 -0
- package/esm/errors.d.ts.map +1 -0
- package/esm/errors.js +25 -0
- package/esm/fs/mod.d.ts +28 -0
- package/esm/fs/mod.d.ts.map +1 -0
- package/esm/fs/mod.js +13 -0
- package/esm/fs/store.d.ts +45 -0
- package/esm/fs/store.d.ts.map +1 -0
- package/esm/fs/store.js +194 -0
- package/esm/indexeddb/mod.d.ts +9 -0
- package/esm/indexeddb/mod.d.ts.map +1 -0
- package/esm/indexeddb/mod.js +8 -0
- package/esm/indexeddb/store.d.ts +47 -0
- package/esm/indexeddb/store.d.ts.map +1 -0
- package/esm/indexeddb/store.js +299 -0
- package/esm/ipfs/mod.d.ts +24 -0
- package/esm/ipfs/mod.d.ts.map +1 -0
- package/esm/ipfs/mod.js +13 -0
- package/esm/ipfs/store.d.ts +42 -0
- package/esm/ipfs/store.d.ts.map +1 -0
- package/esm/ipfs/store.js +197 -0
- package/esm/localstorage/mod.d.ts +9 -0
- package/esm/localstorage/mod.d.ts.map +1 -0
- package/esm/localstorage/mod.js +8 -0
- package/esm/localstorage/store.d.ts +45 -0
- package/esm/localstorage/store.d.ts.map +1 -0
- package/esm/localstorage/store.js +147 -0
- package/esm/memory/mod.d.ts +12 -0
- package/esm/memory/mod.d.ts.map +1 -0
- package/esm/memory/mod.js +11 -0
- package/esm/memory/store.d.ts +73 -0
- package/esm/memory/store.d.ts.map +1 -0
- package/esm/memory/store.js +300 -0
- package/esm/mod.d.ts +36 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +31 -0
- package/esm/mongo/mod.d.ts +35 -0
- package/esm/mongo/mod.d.ts.map +1 -0
- package/esm/mongo/mod.js +8 -0
- package/esm/mongo/store.d.ts +42 -0
- package/esm/mongo/store.d.ts.map +1 -0
- package/esm/mongo/store.js +185 -0
- package/esm/package.json +3 -0
- package/esm/payload.d.ts +24 -0
- package/esm/payload.d.ts.map +1 -0
- package/esm/payload.js +30 -0
- package/esm/postgres/columns.d.ts +33 -0
- package/esm/postgres/columns.d.ts.map +1 -0
- package/esm/postgres/columns.js +51 -0
- package/esm/postgres/mod.d.ts +19 -0
- package/esm/postgres/mod.d.ts.map +1 -0
- package/esm/postgres/mod.js +9 -0
- package/esm/postgres/store.d.ts +75 -0
- package/esm/postgres/store.d.ts.map +1 -0
- package/esm/postgres/store.js +364 -0
- package/esm/read.d.ts +40 -0
- package/esm/read.d.ts.map +1 -0
- package/esm/read.js +67 -0
- package/esm/s3/mod.d.ts +23 -0
- package/esm/s3/mod.d.ts.map +1 -0
- package/esm/s3/mod.js +13 -0
- package/esm/s3/store.d.ts +46 -0
- package/esm/s3/store.d.ts.map +1 -0
- package/esm/s3/store.js +183 -0
- package/esm/sqlite/mod.d.ts +18 -0
- package/esm/sqlite/mod.d.ts.map +1 -0
- package/esm/sqlite/mod.js +8 -0
- package/esm/sqlite/schema.d.ts +10 -0
- package/esm/sqlite/schema.d.ts.map +1 -0
- package/esm/sqlite/schema.js +26 -0
- package/esm/sqlite/store.d.ts +42 -0
- package/esm/sqlite/store.d.ts.map +1 -0
- package/esm/sqlite/store.js +184 -0
- package/esm/types.d.ts +101 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +21 -0
- package/esm/url.d.ts +108 -0
- package/esm/url.d.ts.map +1 -0
- package/esm/url.js +151 -0
- package/package.json +93 -0
package/esm/fs/mod.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Store implementation backed by the local filesystem. Requires an
|
|
5
|
+
* injected FsExecutor so the package does not depend on a specific
|
|
6
|
+
* filesystem API. Files hold raw payload bytes — the store is opaque.
|
|
7
|
+
*
|
|
8
|
+
* The executor reads via `ReadableStream<Uint8Array>` to avoid forcing
|
|
9
|
+
* callers (or the executor) to materialize large files in memory.
|
|
10
|
+
* Writes accept the `StorePayload` union so existing buffered callers
|
|
11
|
+
* still work without conversion.
|
|
12
|
+
*/
|
|
13
|
+
import type { StorePayload } from "../types.js";
|
|
14
|
+
export interface FsExecutor {
|
|
15
|
+
readFile: (path: string) => Promise<ReadableStream<Uint8Array>>;
|
|
16
|
+
writeFile: (path: string, content: StorePayload) => Promise<void>;
|
|
17
|
+
removeFile: (path: string) => Promise<void>;
|
|
18
|
+
exists: (path: string) => Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* List the names of *files* that are direct children of `dir`.
|
|
21
|
+
* Subdirectories and entries deeper than one level MUST NOT be
|
|
22
|
+
* returned — `ls` / `count` are shallow contracts.
|
|
23
|
+
*/
|
|
24
|
+
listFiles: (dir: string) => Promise<string[]>;
|
|
25
|
+
cleanup?: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export { FsStore } from "./store.js";
|
|
28
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/fs/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IAChE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C;;;;OAIG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
package/esm/fs/mod.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Store implementation backed by the local filesystem. Requires an
|
|
5
|
+
* injected FsExecutor so the package does not depend on a specific
|
|
6
|
+
* filesystem API. Files hold raw payload bytes — the store is opaque.
|
|
7
|
+
*
|
|
8
|
+
* The executor reads via `ReadableStream<Uint8Array>` to avoid forcing
|
|
9
|
+
* callers (or the executor) to materialize large files in memory.
|
|
10
|
+
* Writes accept the `StorePayload` union so existing buffered callers
|
|
11
|
+
* still work without conversion.
|
|
12
|
+
*/
|
|
13
|
+
export { FsStore } from "./store.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FsStore — Filesystem implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. Writes one
|
|
5
|
+
* file per entry; the file body is the payload bytes verbatim. The
|
|
6
|
+
* store is opaque — it does not parse or transform contents.
|
|
7
|
+
*
|
|
8
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only: they list the
|
|
9
|
+
* `.bin` files directly inside the prefix's mapped directory, never
|
|
10
|
+
* recursing into subdirectories.
|
|
11
|
+
*/
|
|
12
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
|
|
13
|
+
import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
|
|
14
|
+
import type { EntityStore } from "../entity-store.js";
|
|
15
|
+
import type { EntityRecord, EntitySchema } from "../entity.js";
|
|
16
|
+
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
17
|
+
import type { FsExecutor } from "./mod.js";
|
|
18
|
+
export declare class FsStore implements EntityStore<BytesOnlyEntityMeta> {
|
|
19
|
+
private readonly rootDir;
|
|
20
|
+
private readonly executor;
|
|
21
|
+
constructor(rootDir: string, executor: FsExecutor);
|
|
22
|
+
entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
|
|
23
|
+
entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
|
|
24
|
+
provisionEntity(_meta: BytesOnlyEntityMeta): Promise<void>;
|
|
25
|
+
write(meta: BytesOnlyEntityMeta, entries: {
|
|
26
|
+
uri: string;
|
|
27
|
+
record: EntityRecord;
|
|
28
|
+
}[]): Promise<StoreWriteResult[]>;
|
|
29
|
+
read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
30
|
+
delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
31
|
+
private _writeBytes;
|
|
32
|
+
private _readBytes;
|
|
33
|
+
private _readOne;
|
|
34
|
+
/** Resolve the on-disk directory that holds the direct leaves of `prefixUri`. */
|
|
35
|
+
private _dirForPrefix;
|
|
36
|
+
/** List direct-child URIs under a prefix (no recursion). */
|
|
37
|
+
private _listChildUris;
|
|
38
|
+
private _ls;
|
|
39
|
+
private _count;
|
|
40
|
+
private _deleteBytes;
|
|
41
|
+
status(): Promise<StatusResult>;
|
|
42
|
+
capabilities(): StoreCapabilities;
|
|
43
|
+
private _resolvePath;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/fs/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAM/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AA2B3C,qBAAa,OAAQ,YAAW,WAAW,CAAC,mBAAmB,CAAC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;gBAE1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU;IAUjD,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,mBAAmB;IAIxD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAI1E,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,KAAK,CACH,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAS9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IASvB,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAW5D,WAAW;IAuBzB,OAAO,CAAC,UAAU;YAQJ,QAAQ;IAUtB,iFAAiF;IACjF,OAAO,CAAC,aAAa;IAQrB,4DAA4D;YAC9C,cAAc;YAgBd,GAAG;YA0BH,MAAM;YAON,YAAY;IAmBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAyBrC,YAAY,IAAI,iBAAiB;IAIjC,OAAO,CAAC,YAAY;CAGrB"}
|
package/esm/fs/store.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FsStore — Filesystem implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. Writes one
|
|
5
|
+
* file per entry; the file body is the payload bytes verbatim. The
|
|
6
|
+
* store is opaque — it does not parse or transform contents.
|
|
7
|
+
*
|
|
8
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only: they list the
|
|
9
|
+
* `.bin` files directly inside the prefix's mapped directory, never
|
|
10
|
+
* recursing into subdirectories.
|
|
11
|
+
*/
|
|
12
|
+
import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
|
|
13
|
+
import { dispatchRead } from "../dispatch.js";
|
|
14
|
+
import { storageFailure } from "../errors.js";
|
|
15
|
+
import { validateReadParams } from "../read.js";
|
|
16
|
+
const STORE_NAME = "FsStore";
|
|
17
|
+
const EXT = ".bin";
|
|
18
|
+
/**
|
|
19
|
+
* Convert a URI to a relative filesystem path.
|
|
20
|
+
* `protocol://host/path` becomes `protocol_host/path.bin`.
|
|
21
|
+
*
|
|
22
|
+
* Only the FIRST `://` is rewritten — embedded `://` (rare but legal
|
|
23
|
+
* in protocol-tunneling uris) stays in the path.
|
|
24
|
+
*/
|
|
25
|
+
function uriToRelPath(uri) {
|
|
26
|
+
return uri.replace("://", "_") + EXT;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert a relative filesystem path back to a URI.
|
|
30
|
+
* `protocol_host/path.bin` becomes `protocol://host/path`.
|
|
31
|
+
*/
|
|
32
|
+
function relPathToUri(relPath) {
|
|
33
|
+
const withoutExt = relPath.endsWith(EXT)
|
|
34
|
+
? relPath.slice(0, -EXT.length)
|
|
35
|
+
: relPath;
|
|
36
|
+
return withoutExt.replace("_", "://");
|
|
37
|
+
}
|
|
38
|
+
export class FsStore {
|
|
39
|
+
rootDir;
|
|
40
|
+
executor;
|
|
41
|
+
constructor(rootDir, executor) {
|
|
42
|
+
if (!rootDir)
|
|
43
|
+
throw new Error("rootDir is required");
|
|
44
|
+
if (!executor)
|
|
45
|
+
throw new Error("executor is required");
|
|
46
|
+
this.rootDir = rootDir.replace(/\/+$/, "");
|
|
47
|
+
this.executor = executor;
|
|
48
|
+
}
|
|
49
|
+
// ── EntityStore surface ──────────────────────────────────────────
|
|
50
|
+
entitySupport(schema) {
|
|
51
|
+
return bytesOnlyEntitySupport(schema);
|
|
52
|
+
}
|
|
53
|
+
entityStatus(meta) {
|
|
54
|
+
return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
|
|
55
|
+
}
|
|
56
|
+
provisionEntity(_meta) {
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
}
|
|
59
|
+
write(meta, entries) {
|
|
60
|
+
return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
|
|
61
|
+
}
|
|
62
|
+
read(meta, urls) {
|
|
63
|
+
return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
|
|
64
|
+
}
|
|
65
|
+
delete(meta, uris) {
|
|
66
|
+
return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
|
|
67
|
+
}
|
|
68
|
+
// ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
|
|
69
|
+
async _writeBytes(entries) {
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
try {
|
|
73
|
+
await this.executor.writeFile(this._resolvePath(entry.uri), entry.payload);
|
|
74
|
+
results.push({ success: true });
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
results.push({
|
|
78
|
+
success: false,
|
|
79
|
+
...storageFailure(err, "Write failed", entry.uri),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
_readBytes(urls) {
|
|
86
|
+
return dispatchRead(urls, STORE_NAME, {
|
|
87
|
+
read: (p) => this._readOne(p.uri),
|
|
88
|
+
ls: (p) => this._ls(p),
|
|
89
|
+
count: (p) => this._count(p),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async _readOne(uri) {
|
|
93
|
+
try {
|
|
94
|
+
return await this.executor.readFile(this._resolvePath(uri));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Resolve the on-disk directory that holds the direct leaves of `prefixUri`. */
|
|
101
|
+
_dirForPrefix(prefixUri) {
|
|
102
|
+
const rel = uriToRelPath(prefixUri).slice(0, -EXT.length).replace(/\/+$/, "");
|
|
103
|
+
return rel ? `${this.rootDir}/${rel}` : this.rootDir;
|
|
104
|
+
}
|
|
105
|
+
/** List direct-child URIs under a prefix (no recursion). */
|
|
106
|
+
async _listChildUris(prefixUri) {
|
|
107
|
+
let files;
|
|
108
|
+
try {
|
|
109
|
+
files = await this.executor.listFiles(this._dirForPrefix(prefixUri));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const relDir = uriToRelPath(prefixUri).slice(0, -EXT.length).replace(/\/+$/, "");
|
|
115
|
+
return files
|
|
116
|
+
.filter((f) => f.endsWith(EXT) && !f.includes("/"))
|
|
117
|
+
.map((f) => relPathToUri(`${relDir}/${f}`));
|
|
118
|
+
}
|
|
119
|
+
async _ls(parsed) {
|
|
120
|
+
validateReadParams(parsed.params, STORE_NAME);
|
|
121
|
+
const { params } = parsed;
|
|
122
|
+
const format = params.format ?? "full";
|
|
123
|
+
let uris = await this._listChildUris(parsed.uri);
|
|
124
|
+
if (params.sortBy === "uri") {
|
|
125
|
+
const dir = params.sortOrder === "desc" ? -1 : 1;
|
|
126
|
+
uris = [...uris].sort((a, b) => a.localeCompare(b) * dir);
|
|
127
|
+
}
|
|
128
|
+
if (params.limit !== undefined) {
|
|
129
|
+
const page = params.page ?? 1;
|
|
130
|
+
const start = (page - 1) * params.limit;
|
|
131
|
+
uris = uris.slice(start, start + params.limit);
|
|
132
|
+
}
|
|
133
|
+
if (format === "uris")
|
|
134
|
+
return uris;
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const uri of uris) {
|
|
137
|
+
out.push([uri, await this._readOne(uri)]);
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
async _count(parsed) {
|
|
142
|
+
if (parsed.params.pattern !== undefined) {
|
|
143
|
+
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
144
|
+
}
|
|
145
|
+
return (await this._listChildUris(parsed.uri)).length;
|
|
146
|
+
}
|
|
147
|
+
async _deleteBytes(uris) {
|
|
148
|
+
const results = [];
|
|
149
|
+
for (const uri of uris) {
|
|
150
|
+
try {
|
|
151
|
+
await this.executor.removeFile(this._resolvePath(uri));
|
|
152
|
+
results.push({ success: true });
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// File may not exist — succeed silently (matches the
|
|
156
|
+
// miss-is-not-an-error convention)
|
|
157
|
+
results.push({ success: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return results;
|
|
161
|
+
}
|
|
162
|
+
// ── Status ───────────────────────────────────────────────────────
|
|
163
|
+
async status() {
|
|
164
|
+
try {
|
|
165
|
+
const rootExists = await this.executor.exists(this.rootDir);
|
|
166
|
+
if (!rootExists) {
|
|
167
|
+
return {
|
|
168
|
+
status: "unhealthy",
|
|
169
|
+
message: `Root directory not found: ${this.rootDir}`,
|
|
170
|
+
fns: ["read", "ls", "count"],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
status: "healthy",
|
|
175
|
+
message: "Filesystem store is operational",
|
|
176
|
+
fns: ["read", "ls", "count"],
|
|
177
|
+
details: { rootDir: this.rootDir },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return {
|
|
182
|
+
status: "unhealthy",
|
|
183
|
+
message: error instanceof Error ? error.message : String(error),
|
|
184
|
+
fns: ["read", "ls", "count"],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
capabilities() {
|
|
189
|
+
return { atomicBatch: false };
|
|
190
|
+
}
|
|
191
|
+
_resolvePath(uri) {
|
|
192
|
+
return `${this.rootDir}/${uriToRelPath(uri)}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB backend for b3nd.
|
|
3
|
+
*
|
|
4
|
+
* Browser IndexedDB Store implementation. Pure mechanical storage
|
|
5
|
+
* with no protocol awareness — wrap with SaveClient
|
|
6
|
+
* for NodeProtocolInterface.
|
|
7
|
+
*/
|
|
8
|
+
export { IndexedDBStore } from "./store.js";
|
|
9
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/indexeddb/mod.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
|
|
2
|
+
import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
|
|
3
|
+
import type { EntityStore } from "../entity-store.js";
|
|
4
|
+
import type { EntityRecord, EntitySchema } from "../entity.js";
|
|
5
|
+
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
6
|
+
export declare class IndexedDBStore implements EntityStore<BytesOnlyEntityMeta> {
|
|
7
|
+
private readonly databaseName;
|
|
8
|
+
private readonly storeName;
|
|
9
|
+
private readonly version;
|
|
10
|
+
private readonly indexedDB;
|
|
11
|
+
private readonly keyRange;
|
|
12
|
+
private db;
|
|
13
|
+
constructor(config?: {
|
|
14
|
+
databaseName?: string;
|
|
15
|
+
storeName?: string;
|
|
16
|
+
version?: number;
|
|
17
|
+
indexedDB?: any;
|
|
18
|
+
IDBKeyRange?: any;
|
|
19
|
+
});
|
|
20
|
+
private initDB;
|
|
21
|
+
private getStore;
|
|
22
|
+
entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
|
|
23
|
+
entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
|
|
24
|
+
provisionEntity(_meta: BytesOnlyEntityMeta): Promise<void>;
|
|
25
|
+
write(meta: BytesOnlyEntityMeta, entries: {
|
|
26
|
+
uri: string;
|
|
27
|
+
record: EntityRecord;
|
|
28
|
+
}[]): Promise<StoreWriteResult[]>;
|
|
29
|
+
read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
30
|
+
delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
31
|
+
private _writeBytes;
|
|
32
|
+
private _readBytes;
|
|
33
|
+
private _readOne;
|
|
34
|
+
/**
|
|
35
|
+
* Open a cursor over the `uri_index` constrained to the prefix.
|
|
36
|
+
* Returns URIs (and optionally records) that are direct leaves
|
|
37
|
+
* under `prefixUri` — i.e. `prefixUri + <segment>` with no further
|
|
38
|
+
* `/`. Honours sortOrder and limit/page via cursor walking.
|
|
39
|
+
*/
|
|
40
|
+
private _walkLeaves;
|
|
41
|
+
private _ls;
|
|
42
|
+
private _count;
|
|
43
|
+
private _deleteBytes;
|
|
44
|
+
status(): Promise<StatusResult>;
|
|
45
|
+
capabilities(): StoreCapabilities;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/indexeddb/store.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAO/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAyErB,qBAAa,cAAe,YAAW,WAAW,CAAC,mBAAmB,CAAC;IACrE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO,CAAC,EAAE,CAA4B;gBAE1B,MAAM,GAAE;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB,SAAS,CAAC,EAAE,GAAG,CAAC;QAEhB,WAAW,CAAC,EAAE,GAAG,CAAC;KACd;IAcN,OAAO,CAAC,MAAM;YAuBA,QAAQ;IAStB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,mBAAmB;IAIxD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAI1E,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,KAAK,CACH,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAS9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IASvB,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAW5D,WAAW;IA2DzB,OAAO,CAAC,UAAU;YAQJ,QAAQ;IAgBtB;;;;;OAKG;YACW,WAAW;YAuEX,GAAG;YAcH,MAAM;YAYN,YAAY;IAmCpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAiBrC,YAAY,IAAI,iBAAiB;CAGlC"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDBStore — browser IndexedDB implementation of Store.
|
|
3
|
+
*
|
|
4
|
+
* Pure mechanical byte storage with no protocol awareness. Uses
|
|
5
|
+
* IndexedDB for large-scale persistent browser storage. IndexedDB's
|
|
6
|
+
* structured clone preserves `Uint8Array` natively.
|
|
7
|
+
*
|
|
8
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only: cursor over
|
|
9
|
+
* the `uri_index`, keep records whose URI is `prefix + <segment>`
|
|
10
|
+
* with no further `/`. The `format=uris` fast path and `count` only
|
|
11
|
+
* touch the index key — they never load the full record's payload.
|
|
12
|
+
*/
|
|
13
|
+
import * as dntShim from "../_dnt.shims.js";
|
|
14
|
+
import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
|
|
15
|
+
import { dispatchRead } from "../dispatch.js";
|
|
16
|
+
import { storageFailure } from "../errors.js";
|
|
17
|
+
import { toBytes } from "../payload.js";
|
|
18
|
+
import { validateReadParams } from "../read.js";
|
|
19
|
+
const STORE_NAME = "IndexedDBStore";
|
|
20
|
+
// deno-lint-ignore no-explicit-any
|
|
21
|
+
const idbKeyRange = dntShim.dntGlobalThis.IDBKeyRange;
|
|
22
|
+
export class IndexedDBStore {
|
|
23
|
+
databaseName;
|
|
24
|
+
storeName;
|
|
25
|
+
version;
|
|
26
|
+
indexedDB;
|
|
27
|
+
keyRange;
|
|
28
|
+
db = null;
|
|
29
|
+
constructor(config = {}) {
|
|
30
|
+
this.databaseName = config.databaseName || "b3nd";
|
|
31
|
+
this.storeName = config.storeName || "records";
|
|
32
|
+
this.version = config.version || 1;
|
|
33
|
+
this.indexedDB = config.indexedDB ||
|
|
34
|
+
// deno-lint-ignore no-explicit-any
|
|
35
|
+
(dntShim.dntGlobalThis.indexedDB ?? null);
|
|
36
|
+
this.keyRange = config.IDBKeyRange ?? idbKeyRange;
|
|
37
|
+
if (!this.indexedDB) {
|
|
38
|
+
throw new Error("IndexedDB is not available in this environment");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
initDB() {
|
|
42
|
+
if (this.db)
|
|
43
|
+
return Promise.resolve(this.db);
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const request = this.indexedDB.open(this.databaseName, this.version);
|
|
46
|
+
request.onerror = () => reject(new Error(`Failed to open IndexedDB: ${request.error}`));
|
|
47
|
+
request.onsuccess = () => {
|
|
48
|
+
this.db = request.result;
|
|
49
|
+
resolve(request.result);
|
|
50
|
+
};
|
|
51
|
+
request.onupgradeneeded = () => {
|
|
52
|
+
const db = request.result;
|
|
53
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
54
|
+
const store = db.createObjectStore(this.storeName, {
|
|
55
|
+
keyPath: "uri",
|
|
56
|
+
});
|
|
57
|
+
store.createIndex("uri_index", "uri");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async getStore(mode = "readonly") {
|
|
63
|
+
const db = await this.initDB();
|
|
64
|
+
return db.transaction([this.storeName], mode).objectStore(this.storeName);
|
|
65
|
+
}
|
|
66
|
+
// ── EntityStore surface ──────────────────────────────────────────
|
|
67
|
+
entitySupport(schema) {
|
|
68
|
+
return bytesOnlyEntitySupport(schema);
|
|
69
|
+
}
|
|
70
|
+
entityStatus(meta) {
|
|
71
|
+
return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
|
|
72
|
+
}
|
|
73
|
+
provisionEntity(_meta) {
|
|
74
|
+
return Promise.resolve();
|
|
75
|
+
}
|
|
76
|
+
write(meta, entries) {
|
|
77
|
+
return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
|
|
78
|
+
}
|
|
79
|
+
read(meta, urls) {
|
|
80
|
+
return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
|
|
81
|
+
}
|
|
82
|
+
delete(meta, uris) {
|
|
83
|
+
return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
|
|
84
|
+
}
|
|
85
|
+
// ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
|
|
86
|
+
async _writeBytes(entries) {
|
|
87
|
+
const results = [];
|
|
88
|
+
// Collect any streams to bytes BEFORE opening the IDB transaction.
|
|
89
|
+
// An IDB transaction stays alive only across IDB-callback
|
|
90
|
+
// microtasks; awaiting a non-IDB promise (like draining a
|
|
91
|
+
// ReadableStream) yields long enough for the transaction to
|
|
92
|
+
// auto-commit, after which subsequent put()s either throw
|
|
93
|
+
// `TransactionInactiveError` or silently no-op depending on the
|
|
94
|
+
// implementation. fake-indexeddb is permissive about this; real
|
|
95
|
+
// Chromium is not.
|
|
96
|
+
let prepared;
|
|
97
|
+
try {
|
|
98
|
+
prepared = await Promise.all(entries.map(async (e) => ({
|
|
99
|
+
uri: e.uri,
|
|
100
|
+
payload: await toBytes(e.payload),
|
|
101
|
+
})));
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const failure = storageFailure(err, "Write failed");
|
|
105
|
+
return entries.map(() => ({ success: false, ...failure }));
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const store = await this.getStore("readwrite");
|
|
109
|
+
for (const record of prepared) {
|
|
110
|
+
try {
|
|
111
|
+
await new Promise((resolve, reject) => {
|
|
112
|
+
const request = store.put(record);
|
|
113
|
+
request.onsuccess = () => resolve();
|
|
114
|
+
request.onerror = () => reject(new Error(`Failed to write ${record.uri}: ${request.error}`));
|
|
115
|
+
});
|
|
116
|
+
results.push({ success: true });
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
results.push({
|
|
120
|
+
success: false,
|
|
121
|
+
...storageFailure(err, "Write failed", record.uri),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
// Outer failure (e.g. db open) — every entry fails with the same
|
|
128
|
+
// root cause; no per-entry uri since the failure isn't entry-scoped.
|
|
129
|
+
const failure = storageFailure(err, "Write failed");
|
|
130
|
+
for (const _ of entries) {
|
|
131
|
+
results.push({ success: false, ...failure });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
_readBytes(urls) {
|
|
137
|
+
return dispatchRead(urls, STORE_NAME, {
|
|
138
|
+
read: (p) => this._readOne(p.uri),
|
|
139
|
+
ls: (p) => this._ls(p),
|
|
140
|
+
count: (p) => this._count(p),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async _readOne(uri) {
|
|
144
|
+
try {
|
|
145
|
+
const store = await this.getStore();
|
|
146
|
+
return await new Promise((resolve) => {
|
|
147
|
+
const request = store.get(uri);
|
|
148
|
+
request.onsuccess = () => {
|
|
149
|
+
const record = request.result;
|
|
150
|
+
resolve(record ? record.payload : undefined);
|
|
151
|
+
};
|
|
152
|
+
request.onerror = () => resolve(undefined);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Open a cursor over the `uri_index` constrained to the prefix.
|
|
161
|
+
* Returns URIs (and optionally records) that are direct leaves
|
|
162
|
+
* under `prefixUri` — i.e. `prefixUri + <segment>` with no further
|
|
163
|
+
* `/`. Honours sortOrder and limit/page via cursor walking.
|
|
164
|
+
*/
|
|
165
|
+
async _walkLeaves(parsed, onlyUris) {
|
|
166
|
+
const { uri: prefix, params } = parsed;
|
|
167
|
+
const desc = params.sortBy === "uri" && params.sortOrder === "desc";
|
|
168
|
+
const direction = desc ? "prev" : "next";
|
|
169
|
+
const limit = params.limit;
|
|
170
|
+
const offset = limit !== undefined ? ((params.page ?? 1) - 1) * limit : 0;
|
|
171
|
+
// Bound the cursor to `[prefix, prefix + )` — both ends
|
|
172
|
+
// inclusive of the prefix, exclusive of anything past the high
|
|
173
|
+
// surrogate. Fall back to a full scan if IDBKeyRange isn't
|
|
174
|
+
// available in this environment.
|
|
175
|
+
const range = this.keyRange
|
|
176
|
+
? this.keyRange.bound(prefix, prefix + "", false, false)
|
|
177
|
+
: undefined;
|
|
178
|
+
const store = await this.getStore();
|
|
179
|
+
const index = store.index("uri_index");
|
|
180
|
+
return await new Promise((resolve, reject) => {
|
|
181
|
+
const out = [];
|
|
182
|
+
let skipped = 0;
|
|
183
|
+
const request = onlyUris
|
|
184
|
+
? index.openKeyCursor(range, direction)
|
|
185
|
+
: index.openCursor(range, direction);
|
|
186
|
+
request.onsuccess = () => {
|
|
187
|
+
const cursor = request.result;
|
|
188
|
+
if (!cursor) {
|
|
189
|
+
resolve(out);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const key = (onlyUris ? cursor.key : cursor.value.uri);
|
|
193
|
+
if (!key.startsWith(prefix)) {
|
|
194
|
+
resolve(out);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const tail = key.slice(prefix.length);
|
|
198
|
+
if (tail === "" || tail.includes("/")) {
|
|
199
|
+
cursor.continue();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (skipped < offset) {
|
|
203
|
+
skipped++;
|
|
204
|
+
cursor.continue();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (onlyUris) {
|
|
208
|
+
out.push({ uri: key });
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
out.push({ uri: key, payload: cursor.value.payload });
|
|
212
|
+
}
|
|
213
|
+
if (limit !== undefined && out.length >= limit) {
|
|
214
|
+
resolve(out);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
cursor.continue();
|
|
218
|
+
};
|
|
219
|
+
request.onerror = () => reject(request.error ?? new Error("Cursor failed"));
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
async _ls(parsed) {
|
|
223
|
+
validateReadParams(parsed.params, STORE_NAME);
|
|
224
|
+
const format = parsed.params.format ?? "full";
|
|
225
|
+
const onlyUris = format === "uris";
|
|
226
|
+
try {
|
|
227
|
+
const entries = await this._walkLeaves(parsed, onlyUris);
|
|
228
|
+
if (onlyUris)
|
|
229
|
+
return entries.map((e) => e.uri);
|
|
230
|
+
return entries.map((e) => [e.uri, e.payload]);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async _count(parsed) {
|
|
237
|
+
if (parsed.params.pattern !== undefined) {
|
|
238
|
+
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const entries = await this._walkLeaves(parsed, true);
|
|
242
|
+
return entries.length;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async _deleteBytes(uris) {
|
|
249
|
+
const results = [];
|
|
250
|
+
try {
|
|
251
|
+
const store = await this.getStore("readwrite");
|
|
252
|
+
for (const uri of uris) {
|
|
253
|
+
try {
|
|
254
|
+
await new Promise((resolve, reject) => {
|
|
255
|
+
const request = store.delete(uri);
|
|
256
|
+
request.onsuccess = () => resolve();
|
|
257
|
+
request.onerror = () => reject(new Error(`Failed to delete ${uri}: ${request.error}`));
|
|
258
|
+
});
|
|
259
|
+
results.push({ success: true });
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
results.push({
|
|
263
|
+
success: false,
|
|
264
|
+
...storageFailure(err, "Delete failed", uri),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
// Outer failure — every uri fails with the same root cause.
|
|
271
|
+
const failure = storageFailure(err, "Delete failed");
|
|
272
|
+
for (const _ of uris) {
|
|
273
|
+
results.push({ success: false, ...failure });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return results;
|
|
277
|
+
}
|
|
278
|
+
// ── Status ───────────────────────────────────────────────────────
|
|
279
|
+
async status() {
|
|
280
|
+
try {
|
|
281
|
+
await this.initDB();
|
|
282
|
+
return {
|
|
283
|
+
status: "healthy",
|
|
284
|
+
schema: [],
|
|
285
|
+
fns: ["read", "ls", "count"],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return {
|
|
290
|
+
status: "unhealthy",
|
|
291
|
+
schema: [],
|
|
292
|
+
fns: ["read", "ls", "count"],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
capabilities() {
|
|
297
|
+
return { atomicBatch: false };
|
|
298
|
+
}
|
|
299
|
+
}
|