@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/ipfs/mod.js
CHANGED
|
@@ -9,5 +9,24 @@
|
|
|
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
|
export { IpfsStore } from "./store.js";
|
package/esm/ipfs/store.d.ts
CHANGED
|
@@ -1,42 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IpfsStore — IPFS implementation of
|
|
2
|
+
* IpfsStore — IPFS implementation of `EntityStore`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* One backend, many entities. Layouts:
|
|
5
|
+
*
|
|
6
|
+
* - `BYTES_ENTITY` → block holding the payload bytes verbatim. URI →
|
|
7
|
+
* CID is tracked in an in-memory index under the entity bucket
|
|
8
|
+
* `"bytes"`.
|
|
9
|
+
* - any other schema → block holding a JSON-encoded record. URI → CID
|
|
10
|
+
* is tracked in a separate in-memory index under the entity name.
|
|
11
|
+
* Per-field canonical `TYPE_TAGS` round-trip the JSON boundary
|
|
12
|
+
* (bytes → base64, bigint → string, timestamp → ISO-8601 — see
|
|
13
|
+
* `./fields.ts`).
|
|
14
|
+
*
|
|
15
|
+
* ## Lifecycle
|
|
16
|
+
*
|
|
17
|
+
* `entitySupport(schema)` is pure — it plans the field set and computes
|
|
18
|
+
* a collision signature. `provisionEntity(meta)` allocates the
|
|
19
|
+
* in-memory bucket and records the signature; subsequent
|
|
20
|
+
* `entityStatus(meta)` returns `"live"` only when the bucket's
|
|
21
|
+
* signature matches the meta exactly. Same-name different-shape
|
|
22
|
+
* provisioning throws.
|
|
23
|
+
*
|
|
24
|
+
* Note: IPFS itself has no concept of provisioning — the bucket lives
|
|
25
|
+
* only in memory inside this store instance. A fresh process must
|
|
26
|
+
* re-call `provisionEntity` before write/delete (read returns misses
|
|
27
|
+
* naturally), mirroring how Memory works for an in-process store.
|
|
28
|
+
*
|
|
29
|
+
* ## Read params
|
|
7
30
|
*
|
|
8
31
|
* `fn=ls` / `fn=count` are shallow direct-leaves only: scan the
|
|
9
|
-
* in-memory index, keep URIs whose remainder under
|
|
10
|
-
* further `/`.
|
|
32
|
+
* entity's in-memory URI → CID index, keep URIs whose remainder under
|
|
33
|
+
* the prefix has no further `/`. Standard params (`limit`/`page`/
|
|
34
|
+
* `sortBy=uri`/`sortOrder`/`format`) are applied in memory;
|
|
35
|
+
* `format=uris` and `count` skip per-leaf `cat`.
|
|
36
|
+
*
|
|
37
|
+
* `fn=find` (v2 §3.5): deep walk over the entity's in-memory URI →
|
|
38
|
+
* CID index (no shallow `/` cutoff). `pushDownFind` is left **false**
|
|
39
|
+
* — the index is just a `Map<uri, cid>`, structurally identical to
|
|
40
|
+
* memory's bucket; there is no IPFS-side query engine to leverage
|
|
41
|
+
* (CIDs are content-addressed blobs, not a queryable graph). Iteration
|
|
42
|
+
* IS the work, so dispatch's post-filter path (glob, sort, cursor,
|
|
43
|
+
* limit, format, cursor-as-trailing-slot) carries the load. Mirrors
|
|
44
|
+
* memory exactly (see `b3nd-save/src/memory/store.ts`).
|
|
45
|
+
*
|
|
46
|
+
* `status().fns` advertises `["read", "ls", "count", "find"]`.
|
|
11
47
|
*/
|
|
12
|
-
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.
|
|
13
|
-
import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
|
|
48
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.js";
|
|
14
49
|
import type { EntityStore } from "../entity-store.js";
|
|
15
|
-
import type
|
|
50
|
+
import { type EntityMeta, type EntityRecord, type EntitySchema } from "../entity.js";
|
|
16
51
|
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
52
|
+
import { type FieldPlan } from "../json-fields.js";
|
|
17
53
|
import type { IpfsExecutor } from "./mod.js";
|
|
18
|
-
|
|
54
|
+
/**
|
|
55
|
+
* IpfsStore-specific entity handle.
|
|
56
|
+
*
|
|
57
|
+
* `isBytes` selects the raw-payload block layout vs the JSON-encoded
|
|
58
|
+
* record block layout. `fields`/`declared`/`bytesFields` drive
|
|
59
|
+
* write-time validation and encoding; `signature` is used for
|
|
60
|
+
* collision detection in `entityStatus` / `provisionEntity`.
|
|
61
|
+
*/
|
|
62
|
+
export interface IpfsEntityMeta extends EntityMeta {
|
|
63
|
+
readonly isBytes: boolean;
|
|
64
|
+
readonly fields: readonly FieldPlan[];
|
|
65
|
+
readonly declared: ReadonlySet<string>;
|
|
66
|
+
readonly bytesFields: ReadonlySet<string>;
|
|
67
|
+
readonly signature: string;
|
|
68
|
+
}
|
|
69
|
+
export declare class IpfsStore implements EntityStore<IpfsEntityMeta> {
|
|
19
70
|
private readonly executor;
|
|
20
|
-
private readonly
|
|
71
|
+
private readonly buckets;
|
|
21
72
|
constructor(executor: IpfsExecutor);
|
|
22
|
-
entitySupport(schema: EntitySchema):
|
|
23
|
-
entityStatus(meta:
|
|
24
|
-
provisionEntity(
|
|
25
|
-
write(meta:
|
|
73
|
+
entitySupport(schema: EntitySchema): IpfsEntityMeta;
|
|
74
|
+
entityStatus(meta: IpfsEntityMeta): Promise<"live" | "unprovisioned">;
|
|
75
|
+
provisionEntity(meta: IpfsEntityMeta): Promise<void>;
|
|
76
|
+
write(meta: IpfsEntityMeta, entries: {
|
|
26
77
|
uri: string;
|
|
27
78
|
record: EntityRecord;
|
|
28
79
|
}[]): Promise<StoreWriteResult[]>;
|
|
29
|
-
read<T = EntityRecord | undefined>(meta:
|
|
30
|
-
delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
31
|
-
private _writeBytes;
|
|
32
|
-
private _readBytes;
|
|
80
|
+
read<T = EntityRecord | undefined>(meta: IpfsEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
33
81
|
private _readOne;
|
|
34
|
-
/** Shallow direct-leaves from the in-memory URI index. */
|
|
82
|
+
/** Shallow direct-leaves from the entity's in-memory URI index. */
|
|
35
83
|
private _directLeafUris;
|
|
36
84
|
private _ls;
|
|
37
85
|
private _count;
|
|
38
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Deep walk: every URI under the prefix at any depth. Powers
|
|
88
|
+
* `fn=find`. Unlike `_directLeafUris`, there is no `/` cutoff — keys
|
|
89
|
+
* with further slashes past the prefix are kept. Dispatch applies the
|
|
90
|
+
* glob (and sort/cursor/limit/format) on top.
|
|
91
|
+
*/
|
|
92
|
+
private _allUris;
|
|
93
|
+
private _find;
|
|
94
|
+
delete(meta: IpfsEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
39
95
|
status(): Promise<StatusResult>;
|
|
40
96
|
capabilities(): StoreCapabilities;
|
|
97
|
+
/**
|
|
98
|
+
* Collect any `ReadableStream` values on `bytes`-tagged fields into
|
|
99
|
+
* `Uint8Array` and return a shallow-copied record.
|
|
100
|
+
*/
|
|
101
|
+
private _normaliseBytesFields;
|
|
41
102
|
}
|
|
42
103
|
//# sourceMappingURL=store.d.ts.map
|
package/esm/ipfs/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/ipfs/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/ipfs/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAM3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAa7C;;;;;;;GAOG;AACH,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAMD,qBAAa,SAAU,YAAW,WAAW,CAAC,cAAc,CAAC;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAE/C,QAAQ,EAAE,YAAY;IAOlC,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc;IAyC7C,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAOrE,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBpD,KAAK,CACT,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA+E9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAmBT,QAAQ;IAmBtB,mEAAmE;IACnE,OAAO,CAAC,eAAe;YAaT,GAAG;IA4BjB,OAAO,CAAC,MAAM;IAOd;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;YAUF,KAAK;IA6Bb,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAwCrE,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IA8BrC,YAAY,IAAI,iBAAiB;IAMjC;;;OAGG;YACW,qBAAqB;CAmBpC"}
|
package/esm/ipfs/store.js
CHANGED
|
@@ -1,97 +1,231 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IpfsStore — IPFS implementation of
|
|
2
|
+
* IpfsStore — IPFS implementation of `EntityStore`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* One backend, many entities. Layouts:
|
|
5
|
+
*
|
|
6
|
+
* - `BYTES_ENTITY` → block holding the payload bytes verbatim. URI →
|
|
7
|
+
* CID is tracked in an in-memory index under the entity bucket
|
|
8
|
+
* `"bytes"`.
|
|
9
|
+
* - any other schema → block holding a JSON-encoded record. URI → CID
|
|
10
|
+
* is tracked in a separate in-memory index under the entity name.
|
|
11
|
+
* Per-field canonical `TYPE_TAGS` round-trip the JSON boundary
|
|
12
|
+
* (bytes → base64, bigint → string, timestamp → ISO-8601 — see
|
|
13
|
+
* `./fields.ts`).
|
|
14
|
+
*
|
|
15
|
+
* ## Lifecycle
|
|
16
|
+
*
|
|
17
|
+
* `entitySupport(schema)` is pure — it plans the field set and computes
|
|
18
|
+
* a collision signature. `provisionEntity(meta)` allocates the
|
|
19
|
+
* in-memory bucket and records the signature; subsequent
|
|
20
|
+
* `entityStatus(meta)` returns `"live"` only when the bucket's
|
|
21
|
+
* signature matches the meta exactly. Same-name different-shape
|
|
22
|
+
* provisioning throws.
|
|
23
|
+
*
|
|
24
|
+
* Note: IPFS itself has no concept of provisioning — the bucket lives
|
|
25
|
+
* only in memory inside this store instance. A fresh process must
|
|
26
|
+
* re-call `provisionEntity` before write/delete (read returns misses
|
|
27
|
+
* naturally), mirroring how Memory works for an in-process store.
|
|
28
|
+
*
|
|
29
|
+
* ## Read params
|
|
7
30
|
*
|
|
8
31
|
* `fn=ls` / `fn=count` are shallow direct-leaves only: scan the
|
|
9
|
-
* in-memory index, keep URIs whose remainder under
|
|
10
|
-
* further `/`.
|
|
32
|
+
* entity's in-memory URI → CID index, keep URIs whose remainder under
|
|
33
|
+
* the prefix has no further `/`. Standard params (`limit`/`page`/
|
|
34
|
+
* `sortBy=uri`/`sortOrder`/`format`) are applied in memory;
|
|
35
|
+
* `format=uris` and `count` skip per-leaf `cat`.
|
|
36
|
+
*
|
|
37
|
+
* `fn=find` (v2 §3.5): deep walk over the entity's in-memory URI →
|
|
38
|
+
* CID index (no shallow `/` cutoff). `pushDownFind` is left **false**
|
|
39
|
+
* — the index is just a `Map<uri, cid>`, structurally identical to
|
|
40
|
+
* memory's bucket; there is no IPFS-side query engine to leverage
|
|
41
|
+
* (CIDs are content-addressed blobs, not a queryable graph). Iteration
|
|
42
|
+
* IS the work, so dispatch's post-filter path (glob, sort, cursor,
|
|
43
|
+
* limit, format, cursor-as-trailing-slot) carries the load. Mirrors
|
|
44
|
+
* memory exactly (see `b3nd-save/src/memory/store.ts`).
|
|
45
|
+
*
|
|
46
|
+
* `status().fns` advertises `["read", "ls", "count", "find"]`.
|
|
11
47
|
*/
|
|
12
|
-
import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
|
|
13
48
|
import { dispatchRead } from "../dispatch.js";
|
|
14
49
|
import { storageFailure } from "../errors.js";
|
|
15
|
-
import {
|
|
50
|
+
import { toBytes } from "../payload.js";
|
|
51
|
+
import { applyReadParams, validateReadParams } from "../read.js";
|
|
52
|
+
import { BYTES_ENTITY, } from "../entity.js";
|
|
53
|
+
import { IDENTIFIER_PATTERN, isValidIdentifier } from "../identifiers.js";
|
|
54
|
+
import { computeSignature, decodeRecord, encodeRecord, planFields, } from "../json-fields.js";
|
|
16
55
|
const STORE_NAME = "IpfsStore";
|
|
56
|
+
function isBytesSchema(schema) {
|
|
57
|
+
return schema.name === BYTES_ENTITY.name;
|
|
58
|
+
}
|
|
17
59
|
export class IpfsStore {
|
|
18
60
|
executor;
|
|
19
|
-
|
|
61
|
+
buckets = new Map();
|
|
20
62
|
constructor(executor) {
|
|
21
63
|
if (!executor)
|
|
22
64
|
throw new Error("executor is required");
|
|
23
65
|
this.executor = executor;
|
|
24
66
|
}
|
|
25
|
-
// ──
|
|
67
|
+
// ── Lifecycle ────────────────────────────────────────────────────
|
|
26
68
|
entitySupport(schema) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
if (isBytesSchema(schema)) {
|
|
70
|
+
const fields = [{ name: "payload", tag: "bytes" }];
|
|
71
|
+
return {
|
|
72
|
+
support: {
|
|
73
|
+
entity: schema.name,
|
|
74
|
+
supported: ["payload"],
|
|
75
|
+
unsupported: [],
|
|
76
|
+
},
|
|
77
|
+
isBytes: true,
|
|
78
|
+
fields,
|
|
79
|
+
declared: new Set(["payload"]),
|
|
80
|
+
bytesFields: new Set(["payload"]),
|
|
81
|
+
signature: computeSignature(schema.name, fields),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (!isValidIdentifier(schema.name)) {
|
|
85
|
+
throw new Error(`${STORE_NAME}: entity name '${schema.name}' must match ${IDENTIFIER_PATTERN.source}`);
|
|
86
|
+
}
|
|
87
|
+
const { fields, unsupported } = planFields(schema.fields);
|
|
88
|
+
const declared = new Set(fields.map((f) => f.name));
|
|
89
|
+
const bytesFields = new Set(fields.filter((f) => f.tag === "bytes").map((f) => f.name));
|
|
90
|
+
return {
|
|
91
|
+
support: {
|
|
92
|
+
entity: schema.name,
|
|
93
|
+
supported: fields.map((f) => f.name),
|
|
94
|
+
unsupported,
|
|
95
|
+
},
|
|
96
|
+
isBytes: false,
|
|
97
|
+
fields,
|
|
98
|
+
declared,
|
|
99
|
+
bytesFields,
|
|
100
|
+
signature: computeSignature(schema.name, fields),
|
|
101
|
+
};
|
|
37
102
|
}
|
|
38
|
-
|
|
39
|
-
|
|
103
|
+
// deno-lint-ignore require-await
|
|
104
|
+
async entityStatus(meta) {
|
|
105
|
+
const bucket = this.buckets.get(meta.support.entity);
|
|
106
|
+
if (!bucket)
|
|
107
|
+
return "unprovisioned";
|
|
108
|
+
return bucket.signature === meta.signature ? "live" : "unprovisioned";
|
|
40
109
|
}
|
|
41
|
-
|
|
42
|
-
|
|
110
|
+
// deno-lint-ignore require-await
|
|
111
|
+
async provisionEntity(meta) {
|
|
112
|
+
const existing = this.buckets.get(meta.support.entity);
|
|
113
|
+
if (existing) {
|
|
114
|
+
if (existing.signature !== meta.signature) {
|
|
115
|
+
throw new Error(`${STORE_NAME}: entity '${meta.support.entity}' is already ` +
|
|
116
|
+
`provisioned with a different shape`);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.buckets.set(meta.support.entity, {
|
|
121
|
+
signature: meta.signature,
|
|
122
|
+
index: new Map(),
|
|
123
|
+
});
|
|
43
124
|
}
|
|
44
|
-
// ──
|
|
45
|
-
async
|
|
125
|
+
// ── Write ────────────────────────────────────────────────────────
|
|
126
|
+
async write(meta, entries) {
|
|
127
|
+
if (entries.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
const bucket = this.buckets.get(meta.support.entity);
|
|
130
|
+
if (!bucket || bucket.signature !== meta.signature) {
|
|
131
|
+
return entries.map(({ uri }) => ({
|
|
132
|
+
success: false,
|
|
133
|
+
...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
46
136
|
const results = [];
|
|
47
|
-
for (const
|
|
137
|
+
for (const { uri, record } of entries) {
|
|
138
|
+
const extras = Object.keys(record).filter((k) => !meta.declared.has(k));
|
|
139
|
+
if (extras.length > 0) {
|
|
140
|
+
results.push({
|
|
141
|
+
success: false,
|
|
142
|
+
...storageFailure(new Error(`record contains keys not declared in schema '${meta.support.entity}': ${extras.join(", ")}`), "Schema mismatch", uri),
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
48
146
|
try {
|
|
49
|
-
|
|
147
|
+
let body;
|
|
148
|
+
if (meta.isBytes) {
|
|
149
|
+
const payload = record.payload;
|
|
150
|
+
if (!(payload instanceof Uint8Array) &&
|
|
151
|
+
!(payload instanceof ReadableStream)) {
|
|
152
|
+
throw new Error("BYTES_ENTITY record.payload must be Uint8Array or ReadableStream");
|
|
153
|
+
}
|
|
154
|
+
body = payload;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const normalised = await this._normaliseBytesFields(record, meta.bytesFields);
|
|
158
|
+
const json = JSON.stringify(encodeRecord(meta.fields, normalised));
|
|
159
|
+
body = new TextEncoder().encode(json);
|
|
160
|
+
}
|
|
161
|
+
const cid = await this.executor.add(body);
|
|
50
162
|
await this.executor.pin(cid);
|
|
51
|
-
|
|
52
|
-
const existing = this.index.get(entry.uri);
|
|
163
|
+
const existing = bucket.index.get(uri);
|
|
53
164
|
if (existing) {
|
|
54
165
|
try {
|
|
55
166
|
await this.executor.unpin(existing.cid);
|
|
56
167
|
}
|
|
57
168
|
catch {
|
|
58
|
-
// Old CID may already be unpinned — ignore
|
|
169
|
+
// Old CID may already be unpinned — ignore.
|
|
59
170
|
}
|
|
60
171
|
}
|
|
61
|
-
|
|
172
|
+
bucket.index.set(uri, { cid });
|
|
62
173
|
results.push({ success: true });
|
|
63
174
|
}
|
|
64
175
|
catch (err) {
|
|
65
176
|
results.push({
|
|
66
177
|
success: false,
|
|
67
|
-
...storageFailure(err, "Write failed",
|
|
178
|
+
...storageFailure(err, "Write failed", uri),
|
|
68
179
|
});
|
|
69
180
|
}
|
|
70
181
|
}
|
|
71
182
|
return results;
|
|
72
183
|
}
|
|
73
|
-
|
|
184
|
+
// ── Read ─────────────────────────────────────────────────────────
|
|
185
|
+
read(meta, urls) {
|
|
74
186
|
return dispatchRead(urls, STORE_NAME, {
|
|
75
|
-
read: (p) => this._readOne(p.uri),
|
|
76
|
-
ls: (p) => this._ls(p),
|
|
77
|
-
count: (p) => this._count(p),
|
|
187
|
+
read: (p) => this._readOne(meta, p.uri),
|
|
188
|
+
ls: (p) => this._ls(meta, p),
|
|
189
|
+
count: (p) => Promise.resolve(this._count(meta, p)),
|
|
190
|
+
// v2 §3.5: find walks the entire in-memory CID index under the
|
|
191
|
+
// URI prefix (no shallow `/` cutoff). With `pushDownFind: false`,
|
|
192
|
+
// dispatch applies the glob, sort, cursor, limit, format, and
|
|
193
|
+
// appends the cursor-as-trailing-slot. By the time the handler
|
|
194
|
+
// runs, dispatch has stripped pattern, cursor, limit, page, and
|
|
195
|
+
// fields; `applyReadParams` ends up as a thin format + uri-sort
|
|
196
|
+
// pass. IPFS has no queryable engine — CIDs are content-addressed
|
|
197
|
+
// blobs — so iteration IS the work; the symmetric shape with
|
|
198
|
+
// memory (ls = shallow walk, find = deep walk; dispatch
|
|
199
|
+
// post-processes both) keeps the code minimal.
|
|
200
|
+
find: (p) => this._find(meta, p),
|
|
78
201
|
});
|
|
79
202
|
}
|
|
80
|
-
async _readOne(uri) {
|
|
81
|
-
const
|
|
203
|
+
async _readOne(meta, uri) {
|
|
204
|
+
const bucket = this.buckets.get(meta.support.entity);
|
|
205
|
+
if (!bucket)
|
|
206
|
+
return undefined;
|
|
207
|
+
const entry = bucket.index.get(uri);
|
|
82
208
|
if (!entry)
|
|
83
209
|
return undefined;
|
|
84
210
|
try {
|
|
85
|
-
|
|
211
|
+
const stream = await this.executor.cat(entry.cid);
|
|
212
|
+
if (meta.isBytes)
|
|
213
|
+
return { payload: stream };
|
|
214
|
+
const bytes = await new Response(stream).bytes();
|
|
215
|
+
const text = new TextDecoder().decode(bytes);
|
|
216
|
+
return decodeRecord(meta.fields, JSON.parse(text));
|
|
86
217
|
}
|
|
87
218
|
catch {
|
|
88
219
|
return undefined;
|
|
89
220
|
}
|
|
90
221
|
}
|
|
91
|
-
/** Shallow direct-leaves from the in-memory URI index. */
|
|
92
|
-
_directLeafUris(prefixUri) {
|
|
222
|
+
/** Shallow direct-leaves from the entity's in-memory URI index. */
|
|
223
|
+
_directLeafUris(meta, prefixUri) {
|
|
224
|
+
const bucket = this.buckets.get(meta.support.entity);
|
|
225
|
+
if (!bucket)
|
|
226
|
+
return [];
|
|
93
227
|
const out = [];
|
|
94
|
-
for (const uri of
|
|
228
|
+
for (const uri of bucket.index.keys()) {
|
|
95
229
|
if (!uri.startsWith(prefixUri))
|
|
96
230
|
continue;
|
|
97
231
|
const tail = uri.slice(prefixUri.length);
|
|
@@ -101,11 +235,11 @@ export class IpfsStore {
|
|
|
101
235
|
}
|
|
102
236
|
return out;
|
|
103
237
|
}
|
|
104
|
-
async _ls(parsed) {
|
|
238
|
+
async _ls(meta, parsed) {
|
|
105
239
|
validateReadParams(parsed.params, STORE_NAME);
|
|
106
240
|
const { params } = parsed;
|
|
107
241
|
const format = params.format ?? "full";
|
|
108
|
-
let uris = this._directLeafUris(parsed.uri);
|
|
242
|
+
let uris = this._directLeafUris(meta, parsed.uri);
|
|
109
243
|
if (params.sortBy === "uri") {
|
|
110
244
|
const dir = params.sortOrder === "desc" ? -1 : 1;
|
|
111
245
|
uris = [...uris].sort((a, b) => a.localeCompare(b) * dir);
|
|
@@ -119,29 +253,82 @@ export class IpfsStore {
|
|
|
119
253
|
return uris;
|
|
120
254
|
const out = [];
|
|
121
255
|
for (const uri of uris) {
|
|
122
|
-
|
|
256
|
+
const rec = await this._readOne(meta, uri);
|
|
257
|
+
if (rec !== undefined)
|
|
258
|
+
out.push([uri, rec]);
|
|
123
259
|
}
|
|
124
260
|
return out;
|
|
125
261
|
}
|
|
126
|
-
_count(parsed) {
|
|
262
|
+
_count(meta, parsed) {
|
|
127
263
|
if (parsed.params.pattern !== undefined) {
|
|
128
264
|
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
129
265
|
}
|
|
130
|
-
return this._directLeafUris(parsed.uri).length;
|
|
266
|
+
return this._directLeafUris(meta, parsed.uri).length;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Deep walk: every URI under the prefix at any depth. Powers
|
|
270
|
+
* `fn=find`. Unlike `_directLeafUris`, there is no `/` cutoff — keys
|
|
271
|
+
* with further slashes past the prefix are kept. Dispatch applies the
|
|
272
|
+
* glob (and sort/cursor/limit/format) on top.
|
|
273
|
+
*/
|
|
274
|
+
_allUris(meta, prefixUri) {
|
|
275
|
+
const bucket = this.buckets.get(meta.support.entity);
|
|
276
|
+
if (!bucket)
|
|
277
|
+
return [];
|
|
278
|
+
const out = [];
|
|
279
|
+
for (const uri of bucket.index.keys()) {
|
|
280
|
+
if (uri.startsWith(prefixUri) && uri !== prefixUri)
|
|
281
|
+
out.push(uri);
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
async _find(meta, parsed) {
|
|
286
|
+
validateReadParams(parsed.params, STORE_NAME);
|
|
287
|
+
const uris = this._allUris(meta, parsed.uri);
|
|
288
|
+
// Collect the rows once; let `applyReadParams` honour format /
|
|
289
|
+
// sort / cursor / limit. `format=uris` skips per-leaf `cat` since
|
|
290
|
+
// the bucket already holds the URIs. Dispatch has stripped the
|
|
291
|
+
// glob + cursor + limit by the time we run (pushDownFind: false),
|
|
292
|
+
// so this path is effectively a thin format/sort pass.
|
|
293
|
+
const format = parsed.params.format ?? "full";
|
|
294
|
+
if (format === "uris") {
|
|
295
|
+
// Build placeholder rows so applyReadParams's sort/cursor/limit
|
|
296
|
+
// can operate, then it projects to `string[]`. We never need the
|
|
297
|
+
// record values on this branch.
|
|
298
|
+
const rows = uris.map((u) => [u, {}]);
|
|
299
|
+
return applyReadParams(rows, parsed.params, STORE_NAME);
|
|
300
|
+
}
|
|
301
|
+
const rows = [];
|
|
302
|
+
for (const uri of uris) {
|
|
303
|
+
const rec = await this._readOne(meta, uri);
|
|
304
|
+
if (rec !== undefined)
|
|
305
|
+
rows.push([uri, rec]);
|
|
306
|
+
}
|
|
307
|
+
return applyReadParams(rows, parsed.params, STORE_NAME);
|
|
131
308
|
}
|
|
132
|
-
|
|
309
|
+
// ── Delete ───────────────────────────────────────────────────────
|
|
310
|
+
async delete(meta, uris) {
|
|
311
|
+
if (uris.length === 0)
|
|
312
|
+
return [];
|
|
313
|
+
const bucket = this.buckets.get(meta.support.entity);
|
|
314
|
+
if (!bucket || bucket.signature !== meta.signature) {
|
|
315
|
+
return uris.map((uri) => ({
|
|
316
|
+
success: false,
|
|
317
|
+
...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
133
320
|
const results = [];
|
|
134
321
|
for (const uri of uris) {
|
|
135
322
|
try {
|
|
136
|
-
const entry =
|
|
323
|
+
const entry = bucket.index.get(uri);
|
|
137
324
|
if (entry) {
|
|
138
325
|
try {
|
|
139
326
|
await this.executor.unpin(entry.cid);
|
|
140
327
|
}
|
|
141
328
|
catch {
|
|
142
|
-
// CID may already be unpinned — ignore
|
|
329
|
+
// CID may already be unpinned — ignore.
|
|
143
330
|
}
|
|
144
|
-
|
|
331
|
+
bucket.index.delete(uri);
|
|
145
332
|
}
|
|
146
333
|
results.push({ success: true });
|
|
147
334
|
}
|
|
@@ -154,7 +341,7 @@ export class IpfsStore {
|
|
|
154
341
|
}
|
|
155
342
|
return results;
|
|
156
343
|
}
|
|
157
|
-
// ── Status
|
|
344
|
+
// ── Status / capabilities ────────────────────────────────────────
|
|
158
345
|
async status() {
|
|
159
346
|
try {
|
|
160
347
|
const online = await this.executor.isOnline();
|
|
@@ -162,36 +349,50 @@ export class IpfsStore {
|
|
|
162
349
|
return {
|
|
163
350
|
status: "unhealthy",
|
|
164
351
|
message: "IPFS node is not reachable",
|
|
165
|
-
fns: ["read", "ls", "count"],
|
|
352
|
+
fns: ["read", "ls", "count", "find"],
|
|
166
353
|
};
|
|
167
354
|
}
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const url = new URL(uri);
|
|
173
|
-
programs.add(`${url.protocol}//${url.hostname}`);
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
// skip malformed URIs
|
|
177
|
-
}
|
|
355
|
+
let indexedUris = 0;
|
|
356
|
+
for (const bucket of this.buckets.values()) {
|
|
357
|
+
indexedUris += bucket.index.size;
|
|
178
358
|
}
|
|
179
359
|
return {
|
|
180
360
|
status: "healthy",
|
|
181
|
-
schema: [...
|
|
182
|
-
fns: ["read", "ls", "count"],
|
|
183
|
-
details: { indexedUris
|
|
361
|
+
schema: [...this.buckets.keys()].map((n) => `entity:${n}`),
|
|
362
|
+
fns: ["read", "ls", "count", "find"],
|
|
363
|
+
details: { indexedUris },
|
|
184
364
|
};
|
|
185
365
|
}
|
|
186
366
|
catch (error) {
|
|
187
367
|
return {
|
|
188
368
|
status: "unhealthy",
|
|
189
369
|
message: error instanceof Error ? error.message : String(error),
|
|
190
|
-
fns: ["read", "ls", "count"],
|
|
370
|
+
fns: ["read", "ls", "count", "find"],
|
|
191
371
|
};
|
|
192
372
|
}
|
|
193
373
|
}
|
|
194
374
|
capabilities() {
|
|
195
375
|
return { atomicBatch: false };
|
|
196
376
|
}
|
|
377
|
+
// ── Internals ────────────────────────────────────────────────────
|
|
378
|
+
/**
|
|
379
|
+
* Collect any `ReadableStream` values on `bytes`-tagged fields into
|
|
380
|
+
* `Uint8Array` and return a shallow-copied record.
|
|
381
|
+
*/
|
|
382
|
+
async _normaliseBytesFields(record, bytesFields) {
|
|
383
|
+
const out = { ...record };
|
|
384
|
+
for (const name of bytesFields) {
|
|
385
|
+
const v = out[name];
|
|
386
|
+
if (v === undefined || v === null)
|
|
387
|
+
continue;
|
|
388
|
+
if (v instanceof Uint8Array)
|
|
389
|
+
continue;
|
|
390
|
+
if (v instanceof ReadableStream) {
|
|
391
|
+
out[name] = await toBytes(v);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
throw new Error(`field '${name}' must be Uint8Array or ReadableStream, got ${typeof v}`);
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
197
398
|
}
|