@bandeira-tech/b3nd-save 0.8.1 → 0.13.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/esm/clients/save-client.d.ts +63 -29
- package/esm/clients/save-client.d.ts.map +1 -1
- package/esm/clients/save-client.js +148 -68
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/mod.d.ts +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encrypt/mod.d.ts +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encrypt/mod.js +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.d.ts +6 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.js +12 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/path-vector.d.ts +3 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/network/policies/path-vector.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/path-vector.js +3 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/connection.d.ts +13 -5
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/connection.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/connection.js +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.d.ts.map +1 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/rig.d.ts +3 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/rig.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/rig.js +46 -4
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/types.d.ts +4 -4
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/types/types.d.ts +119 -80
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.d.ts.map +1 -0
- package/esm/dispatch.d.ts +116 -3
- package/esm/dispatch.d.ts.map +1 -1
- package/esm/dispatch.js +381 -5
- package/esm/elasticsearch/store.d.ts +124 -25
- package/esm/elasticsearch/store.d.ts.map +1 -1
- package/esm/elasticsearch/store.js +451 -92
- package/esm/entity-store.d.ts +1 -1
- package/esm/errors.d.ts +1 -1
- package/esm/errors.js +1 -1
- package/esm/fs/mod.d.ts +26 -0
- package/esm/fs/mod.d.ts.map +1 -1
- package/esm/fs/store.d.ts +100 -23
- package/esm/fs/store.d.ts.map +1 -1
- package/esm/fs/store.js +250 -66
- package/esm/glob.d.ts +114 -0
- package/esm/glob.d.ts.map +1 -0
- package/esm/glob.js +352 -0
- package/esm/identifiers.d.ts +18 -0
- package/esm/identifiers.d.ts.map +1 -0
- package/esm/identifiers.js +19 -0
- package/esm/indexeddb/fields.d.ts +32 -0
- package/esm/indexeddb/fields.d.ts.map +1 -0
- package/esm/indexeddb/fields.js +51 -0
- package/esm/indexeddb/store.d.ts +72 -17
- package/esm/indexeddb/store.d.ts.map +1 -1
- package/esm/indexeddb/store.js +362 -106
- package/esm/ipfs/mod.d.ts +19 -0
- package/esm/ipfs/mod.d.ts.map +1 -1
- package/esm/ipfs/mod.js +19 -0
- package/esm/ipfs/store.d.ts +82 -21
- package/esm/ipfs/store.d.ts.map +1 -1
- package/esm/ipfs/store.js +269 -68
- package/esm/json-fields.d.ts +61 -0
- package/esm/json-fields.d.ts.map +1 -0
- package/esm/json-fields.js +121 -0
- package/esm/localstorage/store.d.ts +89 -21
- package/esm/localstorage/store.d.ts.map +1 -1
- package/esm/localstorage/store.js +247 -54
- package/esm/memory/store.d.ts +35 -4
- package/esm/memory/store.d.ts.map +1 -1
- package/esm/memory/store.js +69 -47
- package/esm/mod.d.ts +2 -0
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +1 -0
- package/esm/mongo/fields.d.ts +53 -0
- package/esm/mongo/fields.d.ts.map +1 -0
- package/esm/mongo/fields.js +71 -0
- package/esm/mongo/mod.d.ts +19 -4
- package/esm/mongo/mod.d.ts.map +1 -1
- package/esm/mongo/mod.js +4 -2
- package/esm/mongo/store.d.ts +114 -25
- package/esm/mongo/store.d.ts.map +1 -1
- package/esm/mongo/store.js +355 -102
- package/esm/postgres/store.d.ts +50 -1
- package/esm/postgres/store.d.ts.map +1 -1
- package/esm/postgres/store.js +133 -13
- package/esm/read.d.ts +54 -7
- package/esm/read.d.ts.map +1 -1
- package/esm/read.js +141 -16
- package/esm/s3/store.d.ts +125 -23
- package/esm/s3/store.d.ts.map +1 -1
- package/esm/s3/store.js +313 -57
- package/esm/sqlite/columns.d.ts +33 -0
- package/esm/sqlite/columns.d.ts.map +1 -0
- package/esm/sqlite/columns.js +51 -0
- package/esm/sqlite/store.d.ts +73 -25
- package/esm/sqlite/store.d.ts.map +1 -1
- package/esm/sqlite/store.js +411 -107
- package/esm/types.d.ts +1 -1
- package/esm/url.d.ts +122 -19
- package/esm/url.d.ts.map +1 -1
- package/esm/url.js +218 -24
- package/esm/walk-via-ls.d.ts +122 -0
- package/esm/walk-via-ls.d.ts.map +1 -0
- package/esm/walk-via-ls.js +178 -0
- package/package.json +1 -1
- package/esm/byte-entity-shim.d.ts +0 -47
- package/esm/byte-entity-shim.d.ts.map +0 -1
- package/esm/byte-entity-shim.js +0 -138
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +0 -1
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +0 -1
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/mod.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.d.ts +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/types.js +0 -0
- /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/types/types.js +0 -0
package/esm/indexeddb/store.js
CHANGED
|
@@ -1,24 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IndexedDBStore — browser IndexedDB implementation of
|
|
2
|
+
* IndexedDBStore — browser IndexedDB implementation of `EntityStore`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two layouts share one object store, separated by storage-key prefix.
|
|
5
|
+
* IDB's structured clone preserves typed values natively
|
|
6
|
+
* (`Uint8Array`, `Date`, `BigInt`, plain JSON), so records are stored
|
|
7
|
+
* verbatim — no JSON encoding step on the way in or out.
|
|
7
8
|
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - `BYTES_ENTITY` → record `{ uri, payload }` keyed by `uri` (the
|
|
10
|
+
* original byte-store layout — existing deployments keep working).
|
|
11
|
+
* - any other schema → record `{ uri, record }` keyed by
|
|
12
|
+
* `__entities__/{entityName}/{originalUri}`. The `__entities__/`
|
|
13
|
+
* prefix isolates entity slots from byte slots in the cursor's
|
|
14
|
+
* ordered key space.
|
|
15
|
+
*
|
|
16
|
+
* ## Lifecycle
|
|
17
|
+
*
|
|
18
|
+
* `entitySupport(schema)` is pure — it plans the field set and computes
|
|
19
|
+
* a collision signature. `provisionEntity(meta)` writes a tiny meta
|
|
20
|
+
* record (`__meta__/{name}`) holding the signature; subsequent
|
|
21
|
+
* `entityStatus(meta)` reads it back. Same-name different-shape
|
|
22
|
+
* provisioning throws.
|
|
23
|
+
*
|
|
24
|
+
* IDB needs an `onupgradeneeded` to create object stores, but since we
|
|
25
|
+
* use a single store for everything, schema migrations are not required
|
|
26
|
+
* to grow new entities.
|
|
27
|
+
*
|
|
28
|
+
* ## Read params
|
|
29
|
+
*
|
|
30
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only: cursor over the
|
|
31
|
+
* `uri_index` bounded by `[storageKey, storageKey + )`, keep records
|
|
32
|
+
* whose remainder under the prefix has no further `/`. Standard params
|
|
33
|
+
* (`limit`/`page`/`sortBy=uri`/`sortOrder`/`format=uris`) are honoured
|
|
34
|
+
* via cursor walking — `format=uris` and `count` skip loading payloads
|
|
35
|
+
* via `openKeyCursor`.
|
|
36
|
+
*
|
|
37
|
+
* `fn=find` (v2 §3.5) is the deep counterpart: same cursor, same
|
|
38
|
+
* range, no `rest.includes("/")` tail cutoff. With `pushDownFind:
|
|
39
|
+
* false` the handler returns every descendant under the prefix and
|
|
40
|
+
* dispatch applies the glob, sort, cursor, limit, format, and appends
|
|
41
|
+
* the cursor-as-trailing-slot. IDB has no engine for glob/regex
|
|
42
|
+
* push-down — its cursor only honours key-range and direction — so
|
|
43
|
+
* dispatch's post-filter path is the honest fit (same shape as memory
|
|
44
|
+
* PR #84).
|
|
12
45
|
*/
|
|
13
46
|
import * as dntShim from "../_dnt.shims.js";
|
|
14
|
-
import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
|
|
15
47
|
import { dispatchRead } from "../dispatch.js";
|
|
16
48
|
import { storageFailure } from "../errors.js";
|
|
17
49
|
import { toBytes } from "../payload.js";
|
|
18
50
|
import { validateReadParams } from "../read.js";
|
|
51
|
+
import { BYTES_ENTITY, } from "../entity.js";
|
|
52
|
+
import { IDENTIFIER_PATTERN, isValidIdentifier } from "../identifiers.js";
|
|
53
|
+
import { computeSignature, planFields } from "./fields.js";
|
|
19
54
|
const STORE_NAME = "IndexedDBStore";
|
|
55
|
+
const ENTITY_PREFIX = "__entities__/";
|
|
56
|
+
const META_PREFIX = "__meta__/";
|
|
20
57
|
// deno-lint-ignore no-explicit-any
|
|
21
58
|
const idbKeyRange = dntShim.dntGlobalThis.IDBKeyRange;
|
|
59
|
+
function isBytesSchema(schema) {
|
|
60
|
+
return schema.name === BYTES_ENTITY.name;
|
|
61
|
+
}
|
|
22
62
|
export class IndexedDBStore {
|
|
23
63
|
databaseName;
|
|
24
64
|
storeName;
|
|
@@ -63,93 +103,185 @@ export class IndexedDBStore {
|
|
|
63
103
|
const db = await this.initDB();
|
|
64
104
|
return db.transaction([this.storeName], mode).objectStore(this.storeName);
|
|
65
105
|
}
|
|
66
|
-
// ──
|
|
106
|
+
// ── Lifecycle ────────────────────────────────────────────────────
|
|
67
107
|
entitySupport(schema) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
if (isBytesSchema(schema)) {
|
|
109
|
+
const fields = [{ name: "payload", tag: "bytes" }];
|
|
110
|
+
return {
|
|
111
|
+
support: {
|
|
112
|
+
entity: schema.name,
|
|
113
|
+
supported: ["payload"],
|
|
114
|
+
unsupported: [],
|
|
115
|
+
},
|
|
116
|
+
storageRoot: "",
|
|
117
|
+
isBytes: true,
|
|
118
|
+
fields,
|
|
119
|
+
declared: new Set(["payload"]),
|
|
120
|
+
bytesFields: new Set(["payload"]),
|
|
121
|
+
signature: computeSignature(schema.name, fields),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (!isValidIdentifier(schema.name)) {
|
|
125
|
+
throw new Error(`${STORE_NAME}: entity name '${schema.name}' must match ${IDENTIFIER_PATTERN.source}`);
|
|
126
|
+
}
|
|
127
|
+
const { fields, unsupported } = planFields(schema.fields);
|
|
128
|
+
const declared = new Set(fields.map((f) => f.name));
|
|
129
|
+
const bytesFields = new Set(fields.filter((f) => f.tag === "bytes").map((f) => f.name));
|
|
130
|
+
return {
|
|
131
|
+
support: {
|
|
132
|
+
entity: schema.name,
|
|
133
|
+
supported: fields.map((f) => f.name),
|
|
134
|
+
unsupported,
|
|
135
|
+
},
|
|
136
|
+
storageRoot: `${ENTITY_PREFIX}${schema.name}/`,
|
|
137
|
+
isBytes: false,
|
|
138
|
+
fields,
|
|
139
|
+
declared,
|
|
140
|
+
bytesFields,
|
|
141
|
+
signature: computeSignature(schema.name, fields),
|
|
142
|
+
};
|
|
78
143
|
}
|
|
79
|
-
|
|
80
|
-
|
|
144
|
+
async entityStatus(meta) {
|
|
145
|
+
const recorded = await this._readMetaSignature(meta.support.entity);
|
|
146
|
+
if (recorded === null)
|
|
147
|
+
return "unprovisioned";
|
|
148
|
+
return recorded === meta.signature ? "live" : "unprovisioned";
|
|
81
149
|
}
|
|
82
|
-
|
|
83
|
-
|
|
150
|
+
async provisionEntity(meta) {
|
|
151
|
+
const recorded = await this._readMetaSignature(meta.support.entity);
|
|
152
|
+
if (recorded !== null) {
|
|
153
|
+
if (recorded !== meta.signature) {
|
|
154
|
+
throw new Error(`${STORE_NAME}: entity '${meta.support.entity}' is already ` +
|
|
155
|
+
`provisioned with a different shape`);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const doc = {
|
|
160
|
+
uri: `${META_PREFIX}${meta.support.entity}`,
|
|
161
|
+
signature: meta.signature,
|
|
162
|
+
};
|
|
163
|
+
const store = await this.getStore("readwrite");
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
const req = store.put(doc);
|
|
166
|
+
req.onsuccess = () => resolve();
|
|
167
|
+
req.onerror = () => reject(new Error(`Failed to write entity meta: ${req.error}`));
|
|
168
|
+
});
|
|
84
169
|
}
|
|
85
|
-
// ──
|
|
86
|
-
async
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
})));
|
|
170
|
+
// ── Write ────────────────────────────────────────────────────────
|
|
171
|
+
async write(meta, entries) {
|
|
172
|
+
if (entries.length === 0)
|
|
173
|
+
return [];
|
|
174
|
+
if ((await this._readMetaSignature(meta.support.entity)) !== meta.signature) {
|
|
175
|
+
return entries.map(({ uri }) => ({
|
|
176
|
+
success: false,
|
|
177
|
+
...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
|
|
178
|
+
}));
|
|
102
179
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
180
|
+
// Validate + collect streams BEFORE opening the IDB transaction.
|
|
181
|
+
// IDB transactions stay alive only across IDB-callback microtasks;
|
|
182
|
+
// awaiting a non-IDB promise (like draining a ReadableStream)
|
|
183
|
+
// yields long enough for the transaction to auto-commit.
|
|
184
|
+
const out = new Array(entries.length);
|
|
185
|
+
const prepared = [];
|
|
186
|
+
for (let i = 0; i < entries.length; i++) {
|
|
187
|
+
const { uri, record } = entries[i];
|
|
188
|
+
const extras = Object.keys(record).filter((k) => !meta.declared.has(k));
|
|
189
|
+
if (extras.length > 0) {
|
|
190
|
+
out[i] = {
|
|
191
|
+
success: false,
|
|
192
|
+
...storageFailure(new Error(`record contains keys not declared in schema '${meta.support.entity}': ${extras.join(", ")}`), "Schema mismatch", uri),
|
|
193
|
+
};
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
if (meta.isBytes) {
|
|
198
|
+
const payload = record.payload;
|
|
199
|
+
if (!(payload instanceof Uint8Array) &&
|
|
200
|
+
!(payload instanceof ReadableStream)) {
|
|
201
|
+
throw new Error("BYTES_ENTITY record.payload must be Uint8Array or ReadableStream");
|
|
202
|
+
}
|
|
203
|
+
const bytes = await toBytes(payload);
|
|
204
|
+
prepared.push({ idx: i, doc: { uri, payload: bytes } });
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const normalised = await this._normaliseBytesFields(record, meta.bytesFields);
|
|
208
|
+
prepared.push({
|
|
209
|
+
idx: i,
|
|
210
|
+
doc: { uri: `${meta.storageRoot}${uri}`, record: normalised },
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
out[i] = {
|
|
216
|
+
success: false,
|
|
217
|
+
...storageFailure(err, "Write failed", uri),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
106
220
|
}
|
|
221
|
+
if (prepared.length === 0)
|
|
222
|
+
return out;
|
|
107
223
|
try {
|
|
108
224
|
const store = await this.getStore("readwrite");
|
|
109
|
-
for (const
|
|
225
|
+
for (const { idx, doc } of prepared) {
|
|
110
226
|
try {
|
|
111
227
|
await new Promise((resolve, reject) => {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
228
|
+
const req = store.put(doc);
|
|
229
|
+
req.onsuccess = () => resolve();
|
|
230
|
+
req.onerror = () => reject(new Error(`Failed to write ${doc.uri}: ${req.error}`));
|
|
115
231
|
});
|
|
116
|
-
|
|
232
|
+
out[idx] = { success: true };
|
|
117
233
|
}
|
|
118
234
|
catch (err) {
|
|
119
|
-
|
|
235
|
+
out[idx] = {
|
|
120
236
|
success: false,
|
|
121
|
-
...storageFailure(err, "Write failed",
|
|
122
|
-
}
|
|
237
|
+
...storageFailure(err, "Write failed", entries[idx].uri),
|
|
238
|
+
};
|
|
123
239
|
}
|
|
124
240
|
}
|
|
125
241
|
}
|
|
126
242
|
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
243
|
const failure = storageFailure(err, "Write failed");
|
|
130
|
-
for (const
|
|
131
|
-
|
|
244
|
+
for (const { idx } of prepared) {
|
|
245
|
+
out[idx] = { success: false, ...failure };
|
|
132
246
|
}
|
|
133
247
|
}
|
|
134
|
-
return
|
|
248
|
+
return out;
|
|
135
249
|
}
|
|
136
|
-
|
|
250
|
+
// ── Read ─────────────────────────────────────────────────────────
|
|
251
|
+
read(meta, urls) {
|
|
137
252
|
return dispatchRead(urls, STORE_NAME, {
|
|
138
|
-
read: (p) => this._readOne(p.uri),
|
|
139
|
-
ls: (p) => this._ls(p),
|
|
140
|
-
count: (p) => this._count(p),
|
|
253
|
+
read: (p) => this._readOne(meta, p.uri),
|
|
254
|
+
ls: (p) => this._ls(meta, p),
|
|
255
|
+
count: (p) => this._count(meta, p),
|
|
256
|
+
// v2 §3.5: find walks every descendant under the prefix (no
|
|
257
|
+
// shallow `/` cutoff). With `pushDownFind: false`, dispatch
|
|
258
|
+
// strips pattern/cursor/limit/page before calling, then applies
|
|
259
|
+
// the glob, sort, cursor, limit, format, and appends the cursor-
|
|
260
|
+
// as-trailing-slot on the returned rows. IDB has no engine for
|
|
261
|
+
// glob/regex push-down — only key-range + direction — so the
|
|
262
|
+
// honest shape mirrors memory PR #84: handler returns the raw
|
|
263
|
+
// deep walk, dispatch does the rest.
|
|
264
|
+
find: (p) => this._find(meta, p),
|
|
141
265
|
});
|
|
142
266
|
}
|
|
143
|
-
async _readOne(uri) {
|
|
267
|
+
async _readOne(meta, uri) {
|
|
144
268
|
try {
|
|
145
269
|
const store = await this.getStore();
|
|
270
|
+
const storageKey = meta.isBytes ? uri : `${meta.storageRoot}${uri}`;
|
|
146
271
|
return await new Promise((resolve) => {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
272
|
+
const req = store.get(storageKey);
|
|
273
|
+
req.onsuccess = () => {
|
|
274
|
+
const doc = req.result;
|
|
275
|
+
if (!doc)
|
|
276
|
+
return resolve(undefined);
|
|
277
|
+
if (meta.isBytes) {
|
|
278
|
+
resolve({ payload: doc.payload });
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
resolve(doc.record);
|
|
282
|
+
}
|
|
151
283
|
};
|
|
152
|
-
|
|
284
|
+
req.onerror = () => resolve(undefined);
|
|
153
285
|
});
|
|
154
286
|
}
|
|
155
287
|
catch {
|
|
@@ -157,30 +289,37 @@ export class IndexedDBStore {
|
|
|
157
289
|
}
|
|
158
290
|
}
|
|
159
291
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
292
|
+
* Cursor over `uri_index` constrained to `[meta.storageRoot+prefix,
|
|
293
|
+
* meta.storageRoot+prefix+)`. Honours `sortOrder=desc` via `prev`
|
|
294
|
+
* direction and `limit`/`page` via cursor walking + skipping.
|
|
295
|
+
*
|
|
296
|
+
* `shallow` selects the walk shape:
|
|
297
|
+
* - `true` → only direct leaves (the `rest.includes("/")` tail
|
|
298
|
+
* cutoff). Powers `fn=ls` and shallow `fn=count`.
|
|
299
|
+
* - `false` → every descendant under the prefix, any depth. Powers
|
|
300
|
+
* `fn=find` (v2 §3.5). Dispatch applies the glob, sort, cursor,
|
|
301
|
+
* limit, format on top.
|
|
302
|
+
*
|
|
303
|
+
* Either way the exact-prefix match (`tail === ""`) is skipped — the
|
|
304
|
+
* URL identifies the parent, not a child of itself.
|
|
164
305
|
*/
|
|
165
|
-
async _walkLeaves(parsed,
|
|
306
|
+
async _walkLeaves(meta, parsed, onlyKeys, shallow) {
|
|
166
307
|
const { uri: prefix, params } = parsed;
|
|
167
308
|
const desc = params.sortBy === "uri" && params.sortOrder === "desc";
|
|
168
309
|
const direction = desc ? "prev" : "next";
|
|
169
310
|
const limit = params.limit;
|
|
170
311
|
const offset = limit !== undefined ? ((params.page ?? 1) - 1) * limit : 0;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// surrogate. Fall back to a full scan if IDBKeyRange isn't
|
|
174
|
-
// available in this environment.
|
|
312
|
+
const lower = `${meta.storageRoot}${prefix}`;
|
|
313
|
+
const upper = `${lower}`;
|
|
175
314
|
const range = this.keyRange
|
|
176
|
-
? this.keyRange.bound(
|
|
315
|
+
? this.keyRange.bound(lower, upper, false, false)
|
|
177
316
|
: undefined;
|
|
178
317
|
const store = await this.getStore();
|
|
179
318
|
const index = store.index("uri_index");
|
|
180
319
|
return await new Promise((resolve, reject) => {
|
|
181
320
|
const out = [];
|
|
182
321
|
let skipped = 0;
|
|
183
|
-
const request =
|
|
322
|
+
const request = onlyKeys
|
|
184
323
|
? index.openKeyCursor(range, direction)
|
|
185
324
|
: index.openCursor(range, direction);
|
|
186
325
|
request.onsuccess = () => {
|
|
@@ -189,13 +328,13 @@ export class IndexedDBStore {
|
|
|
189
328
|
resolve(out);
|
|
190
329
|
return;
|
|
191
330
|
}
|
|
192
|
-
const
|
|
193
|
-
if (!
|
|
331
|
+
const storedKey = (onlyKeys ? cursor.key : cursor.value.uri);
|
|
332
|
+
if (!storedKey.startsWith(lower)) {
|
|
194
333
|
resolve(out);
|
|
195
334
|
return;
|
|
196
335
|
}
|
|
197
|
-
const tail =
|
|
198
|
-
if (tail === "" || tail.includes("/")) {
|
|
336
|
+
const tail = storedKey.slice(lower.length);
|
|
337
|
+
if (tail === "" || (shallow && tail.includes("/"))) {
|
|
199
338
|
cursor.continue();
|
|
200
339
|
return;
|
|
201
340
|
}
|
|
@@ -204,12 +343,10 @@ export class IndexedDBStore {
|
|
|
204
343
|
cursor.continue();
|
|
205
344
|
return;
|
|
206
345
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
out.push({ uri: key, payload: cursor.value.payload });
|
|
212
|
-
}
|
|
346
|
+
const originalUri = `${prefix}${tail}`;
|
|
347
|
+
out.push(onlyKeys
|
|
348
|
+
? { uri: originalUri }
|
|
349
|
+
: { uri: originalUri, doc: cursor.value });
|
|
213
350
|
if (limit !== undefined && out.length >= limit) {
|
|
214
351
|
resolve(out);
|
|
215
352
|
return;
|
|
@@ -219,42 +356,90 @@ export class IndexedDBStore {
|
|
|
219
356
|
request.onerror = () => reject(request.error ?? new Error("Cursor failed"));
|
|
220
357
|
});
|
|
221
358
|
}
|
|
222
|
-
async _ls(parsed) {
|
|
359
|
+
async _ls(meta, parsed) {
|
|
223
360
|
validateReadParams(parsed.params, STORE_NAME);
|
|
224
361
|
const format = parsed.params.format ?? "full";
|
|
225
|
-
const
|
|
362
|
+
const onlyKeys = format === "uris";
|
|
226
363
|
try {
|
|
227
|
-
const entries = await this._walkLeaves(parsed,
|
|
228
|
-
if (
|
|
364
|
+
const entries = await this._walkLeaves(meta, parsed, onlyKeys, true);
|
|
365
|
+
if (onlyKeys)
|
|
229
366
|
return entries.map((e) => e.uri);
|
|
230
|
-
return entries.map((e) =>
|
|
367
|
+
return entries.map((e) => {
|
|
368
|
+
if (!e.doc)
|
|
369
|
+
return [e.uri, undefined];
|
|
370
|
+
if (meta.isBytes) {
|
|
371
|
+
return [e.uri, { payload: e.doc.payload }];
|
|
372
|
+
}
|
|
373
|
+
return [e.uri, e.doc.record];
|
|
374
|
+
});
|
|
231
375
|
}
|
|
232
376
|
catch {
|
|
233
377
|
return [];
|
|
234
378
|
}
|
|
235
379
|
}
|
|
236
|
-
|
|
380
|
+
/**
|
|
381
|
+
* v2 §3.5: recursive walk under the prefix. Same cursor as `_ls` with
|
|
382
|
+
* `shallow=false` so the `tail.includes("/")` cutoff is dropped —
|
|
383
|
+
* every descendant flows through, any depth. By the time dispatch
|
|
384
|
+
* calls us (with `pushDownFind: false`), it has stripped
|
|
385
|
+
* pattern/cursor/limit/page from `parsed.params`; sortBy is forced to
|
|
386
|
+
* `"uri"`; format may be either `"full"` or `"uris"`. The handler
|
|
387
|
+
* returns the raw deep walk in that format; dispatch handles glob
|
|
388
|
+
* post-filter, sort, cursor, limit, and the cursor-as-trailing-slot.
|
|
389
|
+
*/
|
|
390
|
+
async _find(meta, parsed) {
|
|
391
|
+
validateReadParams(parsed.params, STORE_NAME);
|
|
392
|
+
const format = parsed.params.format ?? "full";
|
|
393
|
+
const onlyKeys = format === "uris";
|
|
394
|
+
try {
|
|
395
|
+
const entries = await this._walkLeaves(meta, parsed, onlyKeys, false);
|
|
396
|
+
if (onlyKeys)
|
|
397
|
+
return entries.map((e) => e.uri);
|
|
398
|
+
return entries.map((e) => {
|
|
399
|
+
if (!e.doc)
|
|
400
|
+
return [e.uri, undefined];
|
|
401
|
+
if (meta.isBytes) {
|
|
402
|
+
return [e.uri, { payload: e.doc.payload }];
|
|
403
|
+
}
|
|
404
|
+
return [e.uri, e.doc.record];
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async _count(meta, parsed) {
|
|
237
412
|
if (parsed.params.pattern !== undefined) {
|
|
238
413
|
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
239
414
|
}
|
|
240
415
|
try {
|
|
241
|
-
const entries = await this._walkLeaves(parsed, true);
|
|
416
|
+
const entries = await this._walkLeaves(meta, parsed, true, true);
|
|
242
417
|
return entries.length;
|
|
243
418
|
}
|
|
244
419
|
catch {
|
|
245
420
|
return 0;
|
|
246
421
|
}
|
|
247
422
|
}
|
|
248
|
-
|
|
423
|
+
// ── Delete ───────────────────────────────────────────────────────
|
|
424
|
+
async delete(meta, uris) {
|
|
425
|
+
if (uris.length === 0)
|
|
426
|
+
return [];
|
|
427
|
+
if ((await this._readMetaSignature(meta.support.entity)) !== meta.signature) {
|
|
428
|
+
return uris.map((uri) => ({
|
|
429
|
+
success: false,
|
|
430
|
+
...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
249
433
|
const results = [];
|
|
250
434
|
try {
|
|
251
435
|
const store = await this.getStore("readwrite");
|
|
252
436
|
for (const uri of uris) {
|
|
253
437
|
try {
|
|
438
|
+
const storageKey = meta.isBytes ? uri : `${meta.storageRoot}${uri}`;
|
|
254
439
|
await new Promise((resolve, reject) => {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
440
|
+
const req = store.delete(storageKey);
|
|
441
|
+
req.onsuccess = () => resolve();
|
|
442
|
+
req.onerror = () => reject(new Error(`Failed to delete ${uri}: ${req.error}`));
|
|
258
443
|
});
|
|
259
444
|
results.push({ success: true });
|
|
260
445
|
}
|
|
@@ -267,33 +452,104 @@ export class IndexedDBStore {
|
|
|
267
452
|
}
|
|
268
453
|
}
|
|
269
454
|
catch (err) {
|
|
270
|
-
// Outer failure — every uri fails with the same root cause.
|
|
271
455
|
const failure = storageFailure(err, "Delete failed");
|
|
272
|
-
for (const _ of uris)
|
|
456
|
+
for (const _ of uris)
|
|
273
457
|
results.push({ success: false, ...failure });
|
|
274
|
-
}
|
|
275
458
|
}
|
|
276
459
|
return results;
|
|
277
460
|
}
|
|
278
|
-
// ── Status
|
|
461
|
+
// ── Status / capabilities ────────────────────────────────────────
|
|
279
462
|
async status() {
|
|
280
463
|
try {
|
|
281
464
|
await this.initDB();
|
|
465
|
+
const schema = await this._listProvisionedEntities();
|
|
282
466
|
return {
|
|
283
467
|
status: "healthy",
|
|
284
|
-
schema:
|
|
285
|
-
fns: ["read", "ls", "count"],
|
|
468
|
+
schema: schema.map((s) => `entity:${s}`),
|
|
469
|
+
fns: ["read", "ls", "count", "find"],
|
|
286
470
|
};
|
|
287
471
|
}
|
|
288
472
|
catch {
|
|
289
473
|
return {
|
|
290
474
|
status: "unhealthy",
|
|
291
475
|
schema: [],
|
|
292
|
-
fns: ["read", "ls", "count"],
|
|
476
|
+
fns: ["read", "ls", "count", "find"],
|
|
293
477
|
};
|
|
294
478
|
}
|
|
295
479
|
}
|
|
296
480
|
capabilities() {
|
|
297
481
|
return { atomicBatch: false };
|
|
298
482
|
}
|
|
483
|
+
// ── Internals ────────────────────────────────────────────────────
|
|
484
|
+
/**
|
|
485
|
+
* Read the persisted signature for an entity's meta record. Returns
|
|
486
|
+
* `null` if no provisioning record exists. The meta record lives at
|
|
487
|
+
* `__meta__/{name}` in the same object store as data records, so a
|
|
488
|
+
* single object-store cursor sees both layouts without needing a
|
|
489
|
+
* separate IDB store.
|
|
490
|
+
*/
|
|
491
|
+
async _readMetaSignature(entityName) {
|
|
492
|
+
try {
|
|
493
|
+
const store = await this.getStore();
|
|
494
|
+
return await new Promise((resolve) => {
|
|
495
|
+
const req = store.get(`${META_PREFIX}${entityName}`);
|
|
496
|
+
req.onsuccess = () => {
|
|
497
|
+
const doc = req.result;
|
|
498
|
+
resolve(doc ? doc.signature : null);
|
|
499
|
+
};
|
|
500
|
+
req.onerror = () => resolve(null);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Scan the meta keyspace for every provisioned entity name. Used by
|
|
509
|
+
* `status()` to advertise the entities a peer can talk to.
|
|
510
|
+
*/
|
|
511
|
+
async _listProvisionedEntities() {
|
|
512
|
+
if (!this.keyRange)
|
|
513
|
+
return [];
|
|
514
|
+
const range = this.keyRange.bound(META_PREFIX, `${META_PREFIX}`);
|
|
515
|
+
const store = await this.getStore();
|
|
516
|
+
const index = store.index("uri_index");
|
|
517
|
+
return await new Promise((resolve, reject) => {
|
|
518
|
+
const out = [];
|
|
519
|
+
const req = index.openKeyCursor(range, "next");
|
|
520
|
+
req.onsuccess = () => {
|
|
521
|
+
const cursor = req.result;
|
|
522
|
+
if (!cursor)
|
|
523
|
+
return resolve(out);
|
|
524
|
+
const key = cursor.key;
|
|
525
|
+
if (!key.startsWith(META_PREFIX))
|
|
526
|
+
return resolve(out);
|
|
527
|
+
out.push(key.slice(META_PREFIX.length));
|
|
528
|
+
cursor.continue();
|
|
529
|
+
};
|
|
530
|
+
req.onerror = () => reject(req.error ?? new Error("Meta cursor failed"));
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Collect any `ReadableStream` values on `bytes`-tagged fields into
|
|
535
|
+
* `Uint8Array` and return a shallow-copied record. Non-stream values
|
|
536
|
+
* pass through; matches the normalisation pattern used by Memory,
|
|
537
|
+
* Postgres, and LocalStorage.
|
|
538
|
+
*/
|
|
539
|
+
async _normaliseBytesFields(record, bytesFields) {
|
|
540
|
+
const out = { ...record };
|
|
541
|
+
for (const name of bytesFields) {
|
|
542
|
+
const v = out[name];
|
|
543
|
+
if (v === undefined || v === null)
|
|
544
|
+
continue;
|
|
545
|
+
if (v instanceof Uint8Array)
|
|
546
|
+
continue;
|
|
547
|
+
if (v instanceof ReadableStream) {
|
|
548
|
+
out[name] = await toBytes(v);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
throw new Error(`field '${name}' must be Uint8Array or ReadableStream, got ${typeof v}`);
|
|
552
|
+
}
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
299
555
|
}
|
package/esm/ipfs/mod.d.ts
CHANGED
|
@@ -9,6 +9,25 @@
|
|
|
9
9
|
* and streaming callers; `cat` returns a `ReadableStream<Uint8Array>`
|
|
10
10
|
* to avoid materializing the block in memory unless the caller wants
|
|
11
11
|
* it.
|
|
12
|
+
*
|
|
13
|
+
* ## Durability warning — session-scoped index
|
|
14
|
+
*
|
|
15
|
+
* IPFS blocks are pinned and durable, but the URI → CID index and
|
|
16
|
+
* provisioning bookkeeping live only in process memory (a `Map`
|
|
17
|
+
* inside the `IpfsStore` instance). After a process restart:
|
|
18
|
+
*
|
|
19
|
+
* - Previously-written blocks are unreachable: the store cannot map
|
|
20
|
+
* any URI back to its CID.
|
|
21
|
+
* - `entityStatus(meta)` returns `"unprovisioned"` for every entity
|
|
22
|
+
* until `provisionEntity` is called again.
|
|
23
|
+
* - `read` returns misses for all URIs that were valid before the
|
|
24
|
+
* restart.
|
|
25
|
+
*
|
|
26
|
+
* `IpfsStore` is effectively session-scoped as an `EntityStore`.
|
|
27
|
+
* Persisting the index (e.g. writing a bucket-root IPFS block on
|
|
28
|
+
* each write and handing the root CID to the caller for rehydration)
|
|
29
|
+
* is deferred — callers that need durable addressing across restarts
|
|
30
|
+
* should track CIDs externally or use a different backend.
|
|
12
31
|
*/
|
|
13
32
|
import type { StorePayload } from "../types.js";
|
|
14
33
|
export interface IpfsExecutor {
|
package/esm/ipfs/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/ipfs/mod.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/ipfs/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1D,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|