@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
|
@@ -1,17 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LocalStorageStore — browser localStorage implementation of
|
|
2
|
+
* LocalStorageStore — browser localStorage implementation of `EntityStore`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* One backend, many entities. Layouts:
|
|
5
|
+
*
|
|
6
|
+
* - `BYTES_ENTITY` → key `{keyPrefix}{uri}` with a base64-encoded
|
|
7
|
+
* payload string. Matches the original bytes-only layout, so
|
|
8
|
+
* existing deployments keep working without migration.
|
|
9
|
+
* - any other schema → key `{keyPrefix}entities/{entityName}/{uri}`
|
|
10
|
+
* with a JSON-encoded record string. Per-field canonical tags
|
|
11
|
+
* round-trip the JSON boundary (bytes → base64, bigint → string,
|
|
12
|
+
* timestamp → ISO-8601 — see `./fields.ts`).
|
|
13
|
+
*
|
|
14
|
+
* ## Lifecycle
|
|
15
|
+
*
|
|
16
|
+
* `entitySupport(schema)` is pure — it plans the field set and computes
|
|
17
|
+
* a signature for collision detection. `provisionEntity(meta)` writes a
|
|
18
|
+
* tiny bookkeeping key (`{keyPrefix}__meta__/entities/{name}`) holding
|
|
19
|
+
* the signature; subsequent `entityStatus(meta)` reads it back and
|
|
20
|
+
* compares. Same-name different-shape provisioning throws.
|
|
21
|
+
*
|
|
22
|
+
* Writes/reads/deletes consume the meta directly: there is no per-call
|
|
23
|
+
* cache lookup gate. If `meta`'s bookkeeping is missing on the medium,
|
|
24
|
+
* `write` and `delete` surface a `"not provisioned"` storage failure
|
|
25
|
+
* per entry; `read` returns misses.
|
|
26
|
+
*
|
|
27
|
+
* ## Read params
|
|
28
|
+
*
|
|
29
|
+
* `fn=ls` / `fn=count` are shallow direct-leaves only. localStorage has
|
|
30
|
+
* no indexable cursor, so `_directLeaves` walks all keys once under the
|
|
31
|
+
* entity's `keyRoot` and standard params (`limit`/`page`/`sortBy`
|
|
32
|
+
* `=uri`/`sortOrder`/`format`) are applied in memory via
|
|
33
|
+
* `applyReadParams` from `../read.ts`.
|
|
34
|
+
*
|
|
35
|
+
* `fn=find` (v2 §3.5) walks every key under the entity's `keyRoot`
|
|
36
|
+
* matching the URI prefix at any depth — `_walkDeep` mirrors
|
|
37
|
+
* `_directLeaves` minus the `rest.includes("/")` cutoff. With
|
|
38
|
+
* `pushDownFind: false`, dispatch applies the embedded glob, sort,
|
|
39
|
+
* cursor, limit, format, and appends the cursor-as-trailing-slot.
|
|
40
|
+
* localStorage has no engine to leverage for push-down — iteration IS
|
|
41
|
+
* the work — so the symmetric shape (ls = shallow walk, find = deep
|
|
42
|
+
* walk; dispatch post-processes both) keeps the code minimal.
|
|
43
|
+
*
|
|
44
|
+
* `status().fns` advertises `["read", "ls", "count", "find"]`.
|
|
7
45
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js";
|
|
46
|
+
import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/mod.js";
|
|
10
47
|
import { dispatchRead } from "../dispatch.js";
|
|
11
48
|
import { storageFailure } from "../errors.js";
|
|
12
49
|
import { toBytes } from "../payload.js";
|
|
13
50
|
import { applyReadParams } from "../read.js";
|
|
51
|
+
import { BYTES_ENTITY, } from "../entity.js";
|
|
52
|
+
import { IDENTIFIER_PATTERN, isValidIdentifier } from "../identifiers.js";
|
|
53
|
+
import { computeSignature, decodeRecord, encodeRecord, planFields, } from "../json-fields.js";
|
|
14
54
|
const STORE_NAME = "LocalStorageStore";
|
|
55
|
+
const ENTITY_PREFIX = "entities/";
|
|
56
|
+
const META_PREFIX = "__meta__/entities/";
|
|
57
|
+
function isBytesSchema(schema) {
|
|
58
|
+
return schema.name === BYTES_ENTITY.name;
|
|
59
|
+
}
|
|
15
60
|
export class LocalStorageStore {
|
|
16
61
|
keyPrefix;
|
|
17
62
|
storage;
|
|
@@ -23,66 +68,145 @@ export class LocalStorageStore {
|
|
|
23
68
|
throw new Error("localStorage is not available in this environment");
|
|
24
69
|
}
|
|
25
70
|
}
|
|
26
|
-
|
|
27
|
-
return `${this.keyPrefix}${uri}`;
|
|
28
|
-
}
|
|
29
|
-
// ── EntityStore surface ──────────────────────────────────────────
|
|
71
|
+
// ── Lifecycle ────────────────────────────────────────────────────
|
|
30
72
|
entitySupport(schema) {
|
|
31
|
-
|
|
73
|
+
if (isBytesSchema(schema)) {
|
|
74
|
+
const fields = [{ name: "payload", tag: "bytes" }];
|
|
75
|
+
return {
|
|
76
|
+
support: {
|
|
77
|
+
entity: schema.name,
|
|
78
|
+
supported: ["payload"],
|
|
79
|
+
unsupported: [],
|
|
80
|
+
},
|
|
81
|
+
keyRoot: this.keyPrefix,
|
|
82
|
+
isBytes: true,
|
|
83
|
+
fields,
|
|
84
|
+
declared: new Set(["payload"]),
|
|
85
|
+
bytesFields: new Set(["payload"]),
|
|
86
|
+
signature: computeSignature(schema.name, fields),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!isValidIdentifier(schema.name)) {
|
|
90
|
+
throw new Error(`${STORE_NAME}: entity name '${schema.name}' must match ${IDENTIFIER_PATTERN.source}`);
|
|
91
|
+
}
|
|
92
|
+
const { fields, unsupported } = planFields(schema.fields);
|
|
93
|
+
const declared = new Set(fields.map((f) => f.name));
|
|
94
|
+
const bytesFields = new Set(fields.filter((f) => f.tag === "bytes").map((f) => f.name));
|
|
95
|
+
return {
|
|
96
|
+
support: {
|
|
97
|
+
entity: schema.name,
|
|
98
|
+
supported: fields.map((f) => f.name),
|
|
99
|
+
unsupported,
|
|
100
|
+
},
|
|
101
|
+
keyRoot: `${this.keyPrefix}${ENTITY_PREFIX}${schema.name}/`,
|
|
102
|
+
isBytes: false,
|
|
103
|
+
fields,
|
|
104
|
+
declared,
|
|
105
|
+
bytesFields,
|
|
106
|
+
signature: computeSignature(schema.name, fields),
|
|
107
|
+
};
|
|
32
108
|
}
|
|
33
109
|
entityStatus(meta) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return Promise.resolve();
|
|
110
|
+
const recorded = this._readMetaSignature(meta.support.entity);
|
|
111
|
+
if (recorded === null)
|
|
112
|
+
return Promise.resolve("unprovisioned");
|
|
113
|
+
return Promise.resolve(recorded === meta.signature ? "live" : "unprovisioned");
|
|
38
114
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
115
|
+
// deno-lint-ignore require-await
|
|
116
|
+
async provisionEntity(meta) {
|
|
117
|
+
const recorded = this._readMetaSignature(meta.support.entity);
|
|
118
|
+
if (recorded !== null) {
|
|
119
|
+
if (recorded !== meta.signature) {
|
|
120
|
+
throw new Error(`${STORE_NAME}: entity '${meta.support.entity}' is already ` +
|
|
121
|
+
`provisioned with a different shape`);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.storage.setItem(`${this.keyPrefix}${META_PREFIX}${meta.support.entity}`, meta.signature);
|
|
47
126
|
}
|
|
48
|
-
// ──
|
|
49
|
-
async
|
|
127
|
+
// ── Write ────────────────────────────────────────────────────────
|
|
128
|
+
async write(meta, entries) {
|
|
129
|
+
if (entries.length === 0)
|
|
130
|
+
return [];
|
|
131
|
+
if (this._readMetaSignature(meta.support.entity) !== meta.signature) {
|
|
132
|
+
return entries.map(({ uri }) => ({
|
|
133
|
+
success: false,
|
|
134
|
+
...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
50
137
|
const results = [];
|
|
51
|
-
for (const
|
|
138
|
+
for (const { uri, record } of entries) {
|
|
139
|
+
const extras = Object.keys(record).filter((k) => !meta.declared.has(k));
|
|
140
|
+
if (extras.length > 0) {
|
|
141
|
+
results.push({
|
|
142
|
+
success: false,
|
|
143
|
+
...storageFailure(new Error(`record contains keys not declared in schema '${meta.support.entity}': ${extras.join(", ")}`), "Schema mismatch", uri),
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
52
147
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
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
|
+
const bytes = await toBytes(payload);
|
|
155
|
+
this.storage.setItem(`${meta.keyRoot}${uri}`, encodeBase64(bytes));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const normalised = await this._normaliseBytesFields(record, meta.bytesFields);
|
|
159
|
+
const encoded = encodeRecord(meta.fields, normalised);
|
|
160
|
+
this.storage.setItem(`${meta.keyRoot}${uri}`, JSON.stringify(encoded));
|
|
161
|
+
}
|
|
55
162
|
results.push({ success: true });
|
|
56
163
|
}
|
|
57
164
|
catch (err) {
|
|
58
165
|
results.push({
|
|
59
166
|
success: false,
|
|
60
|
-
...storageFailure(err, "Write failed",
|
|
167
|
+
...storageFailure(err, "Write failed", uri),
|
|
61
168
|
});
|
|
62
169
|
}
|
|
63
170
|
}
|
|
64
171
|
return results;
|
|
65
172
|
}
|
|
66
|
-
|
|
173
|
+
// ── Read ─────────────────────────────────────────────────────────
|
|
174
|
+
read(meta, urls) {
|
|
67
175
|
return dispatchRead(urls, STORE_NAME, {
|
|
68
|
-
read: (p) => this._readOne(p.uri),
|
|
69
|
-
ls: (p) => this._ls(p),
|
|
70
|
-
count: (p) => this._count(p),
|
|
176
|
+
read: (p) => Promise.resolve(this._readOne(meta, p.uri)),
|
|
177
|
+
ls: (p) => Promise.resolve(this._ls(meta, p)),
|
|
178
|
+
count: (p) => Promise.resolve(this._count(meta, p)),
|
|
179
|
+
// v2 §3.5: find walks every key under the URI prefix (no shallow
|
|
180
|
+
// `/` cutoff). With `pushDownFind: false`, dispatch applies the
|
|
181
|
+
// glob, sort, cursor, limit, format, and appends the cursor-as-
|
|
182
|
+
// trailing-slot. The handler's only job is to return every
|
|
183
|
+
// Output under the prefix — routed through `applyReadParams` so
|
|
184
|
+
// `format=uris` returns `string[]` (dispatch's post-filter path
|
|
185
|
+
// inspects rows differently depending on format). By the time
|
|
186
|
+
// the handler runs, dispatch has stripped pattern, cursor,
|
|
187
|
+
// limit, page, and fields; `applyReadParams` ends up as a thin
|
|
188
|
+
// format + uri-sort pass. localStorage has no engine to leverage
|
|
189
|
+
// for push-down — iteration IS the work — so the symmetric shape
|
|
190
|
+
// (ls = shallow walk, find = deep walk; dispatch post-processes
|
|
191
|
+
// both) keeps the code minimal.
|
|
192
|
+
find: (p) => Promise.resolve(applyReadParams(this._walkDeep(meta, p.uri), p.params, STORE_NAME)),
|
|
71
193
|
});
|
|
72
194
|
}
|
|
73
|
-
_readOne(uri) {
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
195
|
+
_readOne(meta, uri) {
|
|
196
|
+
const stored = this.storage.getItem(`${meta.keyRoot}${uri}`);
|
|
197
|
+
if (stored === null)
|
|
76
198
|
return undefined;
|
|
77
|
-
|
|
199
|
+
if (meta.isBytes)
|
|
200
|
+
return { payload: decodeBase64(stored) };
|
|
201
|
+
return decodeRecord(meta.fields, JSON.parse(stored));
|
|
78
202
|
}
|
|
79
203
|
/**
|
|
80
|
-
* Walk localStorage once, returning `
|
|
81
|
-
* whose
|
|
82
|
-
* paths are excluded by the slash check.
|
|
204
|
+
* Walk localStorage once, returning `Output<EntityRecord>` for every
|
|
205
|
+
* key under `meta.keyRoot` whose remainder is `uri + <segment>` with
|
|
206
|
+
* no further `/`. Subtree-only paths are excluded by the slash check.
|
|
83
207
|
*/
|
|
84
|
-
_directLeaves(
|
|
85
|
-
const prefixKey =
|
|
208
|
+
_directLeaves(meta, uri) {
|
|
209
|
+
const prefixKey = `${meta.keyRoot}${uri}`;
|
|
86
210
|
const out = [];
|
|
87
211
|
for (let i = 0; i < this.storage.length; i++) {
|
|
88
212
|
const key = this.storage.key(i);
|
|
@@ -91,25 +215,60 @@ export class LocalStorageStore {
|
|
|
91
215
|
const rest = key.substring(prefixKey.length);
|
|
92
216
|
if (rest === "" || rest.includes("/"))
|
|
93
217
|
continue;
|
|
94
|
-
const childUri = `${
|
|
95
|
-
|
|
218
|
+
const childUri = `${uri}${rest}`;
|
|
219
|
+
const record = this._readOne(meta, childUri);
|
|
220
|
+
if (record !== undefined)
|
|
221
|
+
out.push([childUri, record]);
|
|
96
222
|
}
|
|
97
223
|
return out;
|
|
98
224
|
}
|
|
99
|
-
|
|
100
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Walk localStorage once, returning `Output<EntityRecord>` for every
|
|
227
|
+
* key under `meta.keyRoot` whose remainder starts with `uri` — at
|
|
228
|
+
* any depth. Powers `fn=find`. Unlike `_directLeaves`, there is no
|
|
229
|
+
* `/` cutoff: keys with further slashes past the prefix are kept.
|
|
230
|
+
* Dispatch applies the glob (and sort/cursor/limit/format) on top.
|
|
231
|
+
*/
|
|
232
|
+
_walkDeep(meta, uri) {
|
|
233
|
+
const prefixKey = `${meta.keyRoot}${uri}`;
|
|
234
|
+
const out = [];
|
|
235
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
236
|
+
const key = this.storage.key(i);
|
|
237
|
+
if (!key || !key.startsWith(prefixKey))
|
|
238
|
+
continue;
|
|
239
|
+
const rest = key.substring(prefixKey.length);
|
|
240
|
+
if (rest === "")
|
|
241
|
+
continue;
|
|
242
|
+
const childUri = `${uri}${rest}`;
|
|
243
|
+
const record = this._readOne(meta, childUri);
|
|
244
|
+
if (record !== undefined)
|
|
245
|
+
out.push([childUri, record]);
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
_ls(meta, parsed) {
|
|
250
|
+
return applyReadParams(this._directLeaves(meta, parsed.uri), parsed.params, STORE_NAME);
|
|
101
251
|
}
|
|
102
|
-
_count(parsed) {
|
|
252
|
+
_count(meta, parsed) {
|
|
103
253
|
if (parsed.params.pattern !== undefined) {
|
|
104
254
|
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
105
255
|
}
|
|
106
|
-
return this._directLeaves(parsed.uri).length;
|
|
256
|
+
return this._directLeaves(meta, parsed.uri).length;
|
|
107
257
|
}
|
|
108
|
-
|
|
258
|
+
// ── Delete ───────────────────────────────────────────────────────
|
|
259
|
+
delete(meta, uris) {
|
|
260
|
+
if (uris.length === 0)
|
|
261
|
+
return Promise.resolve([]);
|
|
262
|
+
if (this._readMetaSignature(meta.support.entity) !== meta.signature) {
|
|
263
|
+
return Promise.resolve(uris.map((uri) => ({
|
|
264
|
+
success: false,
|
|
265
|
+
...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
|
|
266
|
+
})));
|
|
267
|
+
}
|
|
109
268
|
const results = [];
|
|
110
269
|
for (const uri of uris) {
|
|
111
270
|
try {
|
|
112
|
-
this.storage.removeItem(
|
|
271
|
+
this.storage.removeItem(`${meta.keyRoot}${uri}`);
|
|
113
272
|
results.push({ success: true });
|
|
114
273
|
}
|
|
115
274
|
catch (err) {
|
|
@@ -121,27 +280,61 @@ export class LocalStorageStore {
|
|
|
121
280
|
}
|
|
122
281
|
return Promise.resolve(results);
|
|
123
282
|
}
|
|
124
|
-
// ── Status
|
|
283
|
+
// ── Status / capabilities ────────────────────────────────────────
|
|
125
284
|
status() {
|
|
126
285
|
try {
|
|
127
286
|
const testKey = `${this.keyPrefix}__health_check__`;
|
|
128
287
|
this.storage.setItem(testKey, "ok");
|
|
129
288
|
this.storage.removeItem(testKey);
|
|
289
|
+
const schema = [];
|
|
290
|
+
const metaPrefix = `${this.keyPrefix}${META_PREFIX}`;
|
|
291
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
292
|
+
const k = this.storage.key(i);
|
|
293
|
+
if (!k || !k.startsWith(metaPrefix))
|
|
294
|
+
continue;
|
|
295
|
+
schema.push(`entity:${k.slice(metaPrefix.length)}`);
|
|
296
|
+
}
|
|
130
297
|
return Promise.resolve({
|
|
131
298
|
status: "healthy",
|
|
132
|
-
schema
|
|
133
|
-
fns: ["read", "ls", "count"],
|
|
299
|
+
schema,
|
|
300
|
+
fns: ["read", "ls", "count", "find"],
|
|
134
301
|
});
|
|
135
302
|
}
|
|
136
303
|
catch {
|
|
137
304
|
return Promise.resolve({
|
|
138
305
|
status: "unhealthy",
|
|
139
306
|
schema: [],
|
|
140
|
-
fns: ["read", "ls", "count"],
|
|
307
|
+
fns: ["read", "ls", "count", "find"],
|
|
141
308
|
});
|
|
142
309
|
}
|
|
143
310
|
}
|
|
144
311
|
capabilities() {
|
|
145
312
|
return { atomicBatch: false };
|
|
146
313
|
}
|
|
314
|
+
// ── Internals ────────────────────────────────────────────────────
|
|
315
|
+
_readMetaSignature(entityName) {
|
|
316
|
+
return this.storage.getItem(`${this.keyPrefix}${META_PREFIX}${entityName}`);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Collect any `ReadableStream` values on `bytes`-tagged fields into
|
|
320
|
+
* `Uint8Array` and return a shallow-copied record. Non-stream values
|
|
321
|
+
* pass through; matches the normalisation pattern used by Memory and
|
|
322
|
+
* Postgres.
|
|
323
|
+
*/
|
|
324
|
+
async _normaliseBytesFields(record, bytesFields) {
|
|
325
|
+
const out = { ...record };
|
|
326
|
+
for (const name of bytesFields) {
|
|
327
|
+
const v = out[name];
|
|
328
|
+
if (v === undefined || v === null)
|
|
329
|
+
continue;
|
|
330
|
+
if (v instanceof Uint8Array)
|
|
331
|
+
continue;
|
|
332
|
+
if (v instanceof ReadableStream) {
|
|
333
|
+
out[name] = await toBytes(v);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
throw new Error(`field '${name}' must be Uint8Array or ReadableStream, got ${typeof v}`);
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
147
340
|
}
|
package/esm/memory/store.d.ts
CHANGED
|
@@ -36,8 +36,27 @@
|
|
|
36
36
|
* record lands in the bucket. That coercion runs for every entity
|
|
37
37
|
* that declares a bytes field; nothing about it is bytes-entity-
|
|
38
38
|
* specific.
|
|
39
|
+
*
|
|
40
|
+
* ## Read pipeline
|
|
41
|
+
*
|
|
42
|
+
* `read()` routes through `dispatchRead` (same shell every other store
|
|
43
|
+
* uses) with four handlers — `read`, `ls`, `count`, `find`. The
|
|
44
|
+
* handlers are simple iterators over the bucket:
|
|
45
|
+
* - `ls` / `count`: shallow walk (direct leaves only — the
|
|
46
|
+
* `rest.includes("/")` cutoff in `_walkShallow`).
|
|
47
|
+
* - `find` (v2 §3.5): deep walk (every key under the prefix, any
|
|
48
|
+
* depth — `_walkRecursive`).
|
|
49
|
+
*
|
|
50
|
+
* `pushDownFind` is left **false**: memory has no engine to leverage
|
|
51
|
+
* for push-down, so the handler returns the raw deep walk and dispatch
|
|
52
|
+
* applies the glob, sort, cursor, limit, format, and appends the
|
|
53
|
+
* cursor-as-trailing-slot. This keeps the ls/find shape symmetric
|
|
54
|
+
* (shallow walk vs deep walk; dispatch post-processes both) and avoids
|
|
55
|
+
* duplicating `applyReadParams` plumbing inside the find handler.
|
|
56
|
+
*
|
|
57
|
+
* `status().fns` advertises `["read", "ls", "count", "find"]`.
|
|
39
58
|
*/
|
|
40
|
-
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.
|
|
59
|
+
import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.js";
|
|
41
60
|
import type { StoreCapabilities, StoreWriteResult } from "../types.js";
|
|
42
61
|
import type { EntityStore } from "../entity-store.js";
|
|
43
62
|
import { type EntityMeta, type EntityRecord, type EntitySchema } from "../entity.js";
|
|
@@ -63,9 +82,21 @@ export declare class MemoryStore implements EntityStore<MemoryEntityMeta> {
|
|
|
63
82
|
record: EntityRecord;
|
|
64
83
|
}[]): Promise<StoreWriteResult[]>;
|
|
65
84
|
read<T = EntityRecord | undefined>(meta: MemoryEntityMeta, urls: string[]): Promise<Output<T>[]>;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Walk the bucket once and return every direct leaf under `uri` (the
|
|
87
|
+
* key has no further `/` past the prefix). Powers `ls` and `count`.
|
|
88
|
+
* The shallow `rest.includes("/")` cutoff is what makes this an `ls`
|
|
89
|
+
* walk; `_walkRecursive` is the matching deep walk for `find`.
|
|
90
|
+
*/
|
|
91
|
+
private _walkShallow;
|
|
92
|
+
/**
|
|
93
|
+
* Walk the bucket once and return every record whose URI starts with
|
|
94
|
+
* the prefix — at any depth. Powers `fn=find`. Unlike `_walkShallow`,
|
|
95
|
+
* there is no `/` cutoff: keys with further slashes past the prefix
|
|
96
|
+
* are kept. Dispatch applies the glob (and sort/cursor/limit/format)
|
|
97
|
+
* on top.
|
|
98
|
+
*/
|
|
99
|
+
private _walkRecursive;
|
|
69
100
|
delete(meta: MemoryEntityMeta, uris: string[]): Promise<DeleteResult[]>;
|
|
70
101
|
status(): Promise<StatusResult>;
|
|
71
102
|
capabilities(): StoreCapabilities;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAK3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAGlB,MAAM,cAAc,CAAC;AAKtB;;;;;;GAMG;AACH,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,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;AAOD,qBAAa,WAAY,YAAW,WAAW,CAAC,gBAAgB,CAAC;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAIrD,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB;IAwC/C,YAAY,CAChB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAO9B,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBtD,KAAK,CACT,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqD9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAiCvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAgBpB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAoBtB,MAAM,CACJ,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,YAAY,EAAE,CAAC;IAgC1B,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAQ/B,YAAY,IAAI,iBAAiB;CAGlC"}
|
package/esm/memory/store.js
CHANGED
|
@@ -36,8 +36,28 @@
|
|
|
36
36
|
* record lands in the bucket. That coercion runs for every entity
|
|
37
37
|
* that declares a bytes field; nothing about it is bytes-entity-
|
|
38
38
|
* specific.
|
|
39
|
+
*
|
|
40
|
+
* ## Read pipeline
|
|
41
|
+
*
|
|
42
|
+
* `read()` routes through `dispatchRead` (same shell every other store
|
|
43
|
+
* uses) with four handlers — `read`, `ls`, `count`, `find`. The
|
|
44
|
+
* handlers are simple iterators over the bucket:
|
|
45
|
+
* - `ls` / `count`: shallow walk (direct leaves only — the
|
|
46
|
+
* `rest.includes("/")` cutoff in `_walkShallow`).
|
|
47
|
+
* - `find` (v2 §3.5): deep walk (every key under the prefix, any
|
|
48
|
+
* depth — `_walkRecursive`).
|
|
49
|
+
*
|
|
50
|
+
* `pushDownFind` is left **false**: memory has no engine to leverage
|
|
51
|
+
* for push-down, so the handler returns the raw deep walk and dispatch
|
|
52
|
+
* applies the glob, sort, cursor, limit, format, and appends the
|
|
53
|
+
* cursor-as-trailing-slot. This keeps the ls/find shape symmetric
|
|
54
|
+
* (shallow walk vs deep walk; dispatch post-processes both) and avoids
|
|
55
|
+
* duplicating `applyReadParams` plumbing inside the find handler.
|
|
56
|
+
*
|
|
57
|
+
* `status().fns` advertises `["read", "ls", "count", "find"]`.
|
|
39
58
|
*/
|
|
40
|
-
import {
|
|
59
|
+
import { dispatchRead } from "../dispatch.js";
|
|
60
|
+
import { applyReadParams } from "../read.js";
|
|
41
61
|
import { storageFailure } from "../errors.js";
|
|
42
62
|
import { toBytes } from "../payload.js";
|
|
43
63
|
import { TYPE_TAGS, } from "../entity.js";
|
|
@@ -142,24 +162,35 @@ export class MemoryStore {
|
|
|
142
162
|
return results;
|
|
143
163
|
}
|
|
144
164
|
// ── Read ─────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
async read(meta, urls) {
|
|
165
|
+
read(meta, urls) {
|
|
147
166
|
const bucket = this.buckets.get(meta.support.entity);
|
|
148
|
-
return urls
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
return dispatchRead(urls, STORE_NAME, {
|
|
168
|
+
read: (p) => bucket?.records.get(p.uri),
|
|
169
|
+
ls: (p) => applyReadParams(this._walkShallow(bucket?.records, p.uri), p.params, STORE_NAME),
|
|
170
|
+
count: (p) => this._walkShallow(bucket?.records, p.uri).length,
|
|
171
|
+
// v2 §3.5: find walks the entire bucket under the URI prefix
|
|
172
|
+
// (no shallow `/` cutoff). With `pushDownFind: false`, dispatch
|
|
173
|
+
// applies the glob, sort, cursor, limit, format, and appends the
|
|
174
|
+
// cursor-as-trailing-slot. The handler's only job is to return
|
|
175
|
+
// every Output under the prefix — routed through `applyReadParams`
|
|
176
|
+
// so `format=uris` returns `string[]` (dispatch's post-filter
|
|
177
|
+
// path inspects rows differently depending on format). By the
|
|
178
|
+
// time the handler runs, dispatch has stripped pattern, cursor,
|
|
179
|
+
// limit, page, and fields; `applyReadParams` ends up as a thin
|
|
180
|
+
// format + uri-sort pass. Memory has no engine to leverage for
|
|
181
|
+
// push-down — iteration IS the work — so the symmetric shape
|
|
182
|
+
// (ls = shallow walk, find = deep walk; dispatch post-processes
|
|
183
|
+
// both) keeps the code minimal.
|
|
184
|
+
find: (p) => applyReadParams(this._walkRecursive(bucket?.records, p.uri), p.params, STORE_NAME),
|
|
160
185
|
});
|
|
161
186
|
}
|
|
162
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Walk the bucket once and return every direct leaf under `uri` (the
|
|
189
|
+
* key has no further `/` past the prefix). Powers `ls` and `count`.
|
|
190
|
+
* The shallow `rest.includes("/")` cutoff is what makes this an `ls`
|
|
191
|
+
* walk; `_walkRecursive` is the matching deep walk for `find`.
|
|
192
|
+
*/
|
|
193
|
+
_walkShallow(bucket, uri) {
|
|
163
194
|
if (!bucket)
|
|
164
195
|
return [];
|
|
165
196
|
const prefix = uri.endsWith("/") ? uri : `${uri}/`;
|
|
@@ -174,37 +205,28 @@ export class MemoryStore {
|
|
|
174
205
|
}
|
|
175
206
|
return out;
|
|
176
207
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
entries = entries.slice(start, start + params.limit);
|
|
198
|
-
}
|
|
199
|
-
if (format === "uris")
|
|
200
|
-
return entries.map(([uri]) => uri);
|
|
201
|
-
return entries;
|
|
202
|
-
}
|
|
203
|
-
_count(bucket, parsed) {
|
|
204
|
-
if (parsed.params.pattern !== undefined) {
|
|
205
|
-
throw new Error(`${STORE_NAME}: pattern filter not supported`);
|
|
208
|
+
/**
|
|
209
|
+
* Walk the bucket once and return every record whose URI starts with
|
|
210
|
+
* the prefix — at any depth. Powers `fn=find`. Unlike `_walkShallow`,
|
|
211
|
+
* there is no `/` cutoff: keys with further slashes past the prefix
|
|
212
|
+
* are kept. Dispatch applies the glob (and sort/cursor/limit/format)
|
|
213
|
+
* on top.
|
|
214
|
+
*/
|
|
215
|
+
_walkRecursive(bucket, uri) {
|
|
216
|
+
if (!bucket)
|
|
217
|
+
return [];
|
|
218
|
+
// For find the prefix is whatever the parser produced. When the
|
|
219
|
+
// caller wrote `mutable://room/**` the parser gives us
|
|
220
|
+
// `mutable://room/` (trailing `/` from `splitLocatorGlob`); when
|
|
221
|
+
// they wrote `mutable://room/alice/**` we get `mutable://room/alice/`.
|
|
222
|
+
// Either way we include every key whose prefix matches, no further
|
|
223
|
+
// truncation — that's what makes this the "deep" walk.
|
|
224
|
+
const out = [];
|
|
225
|
+
for (const [k, rec] of bucket) {
|
|
226
|
+
if (k.startsWith(uri) && k !== uri)
|
|
227
|
+
out.push([k, rec]);
|
|
206
228
|
}
|
|
207
|
-
return
|
|
229
|
+
return out;
|
|
208
230
|
}
|
|
209
231
|
// ── Delete ───────────────────────────────────────────────────────
|
|
210
232
|
delete(meta, uris) {
|
|
@@ -236,7 +258,7 @@ export class MemoryStore {
|
|
|
236
258
|
return Promise.resolve({
|
|
237
259
|
status: "healthy",
|
|
238
260
|
schema: [...this.buckets.keys()].map((n) => `entity:${n}`),
|
|
239
|
-
fns: ["read", "ls", "count"],
|
|
261
|
+
fns: ["read", "ls", "count", "find"],
|
|
240
262
|
});
|
|
241
263
|
}
|
|
242
264
|
capabilities() {
|
package/esm/mod.d.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
export type { StoreCapabilities, StoreWriteResult } from "./types.js";
|
|
20
20
|
export type { ParsedUrl, ReadParams } from "./url.js";
|
|
21
21
|
export { buildUrl, parseUrl, uriOf } from "./url.js";
|
|
22
|
+
export type { WalkViaLsOptions } from "./walk-via-ls.js";
|
|
23
|
+
export { walkViaLs } from "./walk-via-ls.js";
|
|
22
24
|
export type { EntityField, EntityMeta, EntityRecord, EntitySchema, EntitySupport, } from "./entity.js";
|
|
23
25
|
export { BYTES_ENTITY, TYPE_TAGS } from "./entity.js";
|
|
24
26
|
export type { EntityStore } from "./entity-store.js";
|
package/esm/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAErD,YAAY,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,YAAY,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAErD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,YAAY,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,YAAY,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC"}
|
package/esm/mod.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* `TYPE_TAGS`, …) is re-exported from the root for convenience.
|
|
18
18
|
*/
|
|
19
19
|
export { buildUrl, parseUrl, uriOf } from "./url.js";
|
|
20
|
+
export { walkViaLs } from "./walk-via-ls.js";
|
|
20
21
|
export { BYTES_ENTITY, TYPE_TAGS } from "./entity.js";
|
|
21
22
|
export * as clients from "./clients/mod.js";
|
|
22
23
|
export * as memory from "./memory/mod.js";
|