@bandeira-tech/b3nd-save 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/esm/_dnt.shims.d.ts +2 -0
  3. package/esm/_dnt.shims.d.ts.map +1 -0
  4. package/esm/_dnt.shims.js +57 -0
  5. package/esm/byte-entity-shim.d.ts +47 -0
  6. package/esm/byte-entity-shim.d.ts.map +1 -0
  7. package/esm/byte-entity-shim.js +138 -0
  8. package/esm/clients/mod.d.ts +15 -0
  9. package/esm/clients/mod.d.ts.map +1 -0
  10. package/esm/clients/mod.js +14 -0
  11. package/esm/clients/save-client.d.ts +87 -0
  12. package/esm/clients/save-client.d.ts.map +1 -0
  13. package/esm/clients/save-client.js +174 -0
  14. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts +40 -0
  15. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +1 -0
  16. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js +31 -0
  17. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts +30 -0
  18. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts.map +1 -0
  19. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.js +70 -0
  20. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts +16 -0
  21. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts.map +1 -0
  22. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.js +50 -0
  23. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts +235 -0
  24. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +1 -0
  25. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.js +619 -0
  26. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts +43 -0
  27. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts.map +1 -0
  28. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.js +54 -0
  29. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts +47 -0
  30. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts.map +1 -0
  31. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.js +134 -0
  32. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts +20 -0
  33. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts.map +1 -0
  34. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.js +34 -0
  35. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts +46 -0
  36. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts.map +1 -0
  37. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.js +131 -0
  38. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts +27 -0
  39. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts.map +1 -0
  40. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.js +25 -0
  41. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts +59 -0
  42. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts.map +1 -0
  43. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.js +188 -0
  44. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts +35 -0
  45. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +1 -0
  46. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.js +57 -0
  47. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts +117 -0
  48. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts.map +1 -0
  49. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.js +125 -0
  50. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts +112 -0
  51. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts.map +1 -0
  52. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.js +21 -0
  53. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts +35 -0
  54. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts.map +1 -0
  55. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.js +106 -0
  56. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts +88 -0
  57. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +1 -0
  58. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.js +86 -0
  59. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts +83 -0
  60. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts.map +1 -0
  61. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.js +115 -0
  62. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts +164 -0
  63. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts.map +1 -0
  64. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.js +67 -0
  65. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts +99 -0
  66. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts.map +1 -0
  67. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.js +190 -0
  68. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts +185 -0
  69. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts.map +1 -0
  70. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.js +103 -0
  71. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts +58 -0
  72. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts.map +1 -0
  73. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.js +64 -0
  74. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts +295 -0
  75. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +1 -0
  76. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.js +898 -0
  77. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts +167 -0
  78. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +1 -0
  79. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.js +5 -0
  80. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts +278 -0
  81. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +1 -0
  82. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js +89 -0
  83. package/esm/dispatch.d.ts +35 -0
  84. package/esm/dispatch.d.ts.map +1 -0
  85. package/esm/dispatch.js +40 -0
  86. package/esm/elasticsearch/mod.d.ts +31 -0
  87. package/esm/elasticsearch/mod.d.ts.map +1 -0
  88. package/esm/elasticsearch/mod.js +8 -0
  89. package/esm/elasticsearch/store.d.ts +44 -0
  90. package/esm/elasticsearch/store.d.ts.map +1 -0
  91. package/esm/elasticsearch/store.js +213 -0
  92. package/esm/entity-store.d.ts +106 -0
  93. package/esm/entity-store.d.ts.map +1 -0
  94. package/esm/entity-store.js +46 -0
  95. package/esm/entity.d.ts +125 -0
  96. package/esm/entity.d.ts.map +1 -0
  97. package/esm/entity.js +66 -0
  98. package/esm/errors.d.ts +23 -0
  99. package/esm/errors.d.ts.map +1 -0
  100. package/esm/errors.js +25 -0
  101. package/esm/fs/mod.d.ts +28 -0
  102. package/esm/fs/mod.d.ts.map +1 -0
  103. package/esm/fs/mod.js +13 -0
  104. package/esm/fs/store.d.ts +45 -0
  105. package/esm/fs/store.d.ts.map +1 -0
  106. package/esm/fs/store.js +194 -0
  107. package/esm/indexeddb/mod.d.ts +9 -0
  108. package/esm/indexeddb/mod.d.ts.map +1 -0
  109. package/esm/indexeddb/mod.js +8 -0
  110. package/esm/indexeddb/store.d.ts +47 -0
  111. package/esm/indexeddb/store.d.ts.map +1 -0
  112. package/esm/indexeddb/store.js +299 -0
  113. package/esm/ipfs/mod.d.ts +24 -0
  114. package/esm/ipfs/mod.d.ts.map +1 -0
  115. package/esm/ipfs/mod.js +13 -0
  116. package/esm/ipfs/store.d.ts +42 -0
  117. package/esm/ipfs/store.d.ts.map +1 -0
  118. package/esm/ipfs/store.js +197 -0
  119. package/esm/localstorage/mod.d.ts +9 -0
  120. package/esm/localstorage/mod.d.ts.map +1 -0
  121. package/esm/localstorage/mod.js +8 -0
  122. package/esm/localstorage/store.d.ts +45 -0
  123. package/esm/localstorage/store.d.ts.map +1 -0
  124. package/esm/localstorage/store.js +147 -0
  125. package/esm/memory/mod.d.ts +12 -0
  126. package/esm/memory/mod.d.ts.map +1 -0
  127. package/esm/memory/mod.js +11 -0
  128. package/esm/memory/store.d.ts +73 -0
  129. package/esm/memory/store.d.ts.map +1 -0
  130. package/esm/memory/store.js +300 -0
  131. package/esm/mod.d.ts +36 -0
  132. package/esm/mod.d.ts.map +1 -0
  133. package/esm/mod.js +31 -0
  134. package/esm/mongo/mod.d.ts +35 -0
  135. package/esm/mongo/mod.d.ts.map +1 -0
  136. package/esm/mongo/mod.js +8 -0
  137. package/esm/mongo/store.d.ts +42 -0
  138. package/esm/mongo/store.d.ts.map +1 -0
  139. package/esm/mongo/store.js +185 -0
  140. package/esm/package.json +3 -0
  141. package/esm/payload.d.ts +24 -0
  142. package/esm/payload.d.ts.map +1 -0
  143. package/esm/payload.js +30 -0
  144. package/esm/postgres/columns.d.ts +33 -0
  145. package/esm/postgres/columns.d.ts.map +1 -0
  146. package/esm/postgres/columns.js +51 -0
  147. package/esm/postgres/mod.d.ts +19 -0
  148. package/esm/postgres/mod.d.ts.map +1 -0
  149. package/esm/postgres/mod.js +9 -0
  150. package/esm/postgres/store.d.ts +75 -0
  151. package/esm/postgres/store.d.ts.map +1 -0
  152. package/esm/postgres/store.js +364 -0
  153. package/esm/read.d.ts +40 -0
  154. package/esm/read.d.ts.map +1 -0
  155. package/esm/read.js +67 -0
  156. package/esm/s3/mod.d.ts +23 -0
  157. package/esm/s3/mod.d.ts.map +1 -0
  158. package/esm/s3/mod.js +13 -0
  159. package/esm/s3/store.d.ts +46 -0
  160. package/esm/s3/store.d.ts.map +1 -0
  161. package/esm/s3/store.js +183 -0
  162. package/esm/sqlite/mod.d.ts +18 -0
  163. package/esm/sqlite/mod.d.ts.map +1 -0
  164. package/esm/sqlite/mod.js +8 -0
  165. package/esm/sqlite/schema.d.ts +10 -0
  166. package/esm/sqlite/schema.d.ts.map +1 -0
  167. package/esm/sqlite/schema.js +26 -0
  168. package/esm/sqlite/store.d.ts +42 -0
  169. package/esm/sqlite/store.d.ts.map +1 -0
  170. package/esm/sqlite/store.js +184 -0
  171. package/esm/types.d.ts +101 -0
  172. package/esm/types.d.ts.map +1 -0
  173. package/esm/types.js +21 -0
  174. package/esm/url.d.ts +108 -0
  175. package/esm/url.d.ts.map +1 -0
  176. package/esm/url.js +151 -0
  177. package/package.json +93 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * MemoryStore — in-memory reference implementation of `EntityStore`.
3
+ *
4
+ * One flat `Map<uri, EntityRecord>` per provisioned entity, keyed by
5
+ * the entity's name. `BYTES_ENTITY` is just another entity here — its
6
+ * records sit in the same map as any custom entity.
7
+ *
8
+ * ## Lifecycle
9
+ *
10
+ * `entitySupport(schema)` is pure: it walks the schema's fields,
11
+ * picks the first recognised `TYPE_TAGS` entry per field as the
12
+ * canonical tag, and returns the operational handle (declared field
13
+ * set, bytes-field set, signature for collision detection, support
14
+ * report). `provisionEntity(meta)` allocates the bucket and stores
15
+ * the meta's signature so future collisions can be detected.
16
+ * `entityStatus(meta)` returns `"live"` only when the cached
17
+ * signature matches `meta` exactly — a same-name-different-shape
18
+ * schema reports `"unprovisioned"`, where "shape" includes the
19
+ * canonical type tag of each field (so changing `["string"]` to
20
+ * `["bigint"]` on an existing field name is a collision, mirroring
21
+ * what SQL backends catch via column-type introspection).
22
+ *
23
+ * Writes/reads/deletes consume the meta directly: there is no
24
+ * per-call cache lookup gate. If `meta`'s bucket does not exist on
25
+ * this store, `write` and `delete` surface the medium's natural
26
+ * "not provisioned" storage failure per entry; `read` returns misses.
27
+ *
28
+ * ## Validation
29
+ *
30
+ * A record under `meta` may only contain keys declared in
31
+ * `meta.declared`. Extra keys produce a per-entry
32
+ * `StoreWriteResult` failure. The store does not coerce.
33
+ *
34
+ * `payload: ReadableStream` (and any other field whose type tag is
35
+ * `"bytes"`) is collected to `Uint8Array` via `toBytes` before the
36
+ * record lands in the bucket. That coercion runs for every entity
37
+ * that declares a bytes field; nothing about it is bytes-entity-
38
+ * specific.
39
+ */
40
+ import { parseUrl } from "../url.js";
41
+ import { storageFailure } from "../errors.js";
42
+ import { toBytes } from "../payload.js";
43
+ import { TYPE_TAGS, } from "../entity.js";
44
+ const STORE_NAME = "MemoryStore";
45
+ const KNOWN_TAGS = new Set(Object.values(TYPE_TAGS));
46
+ export class MemoryStore {
47
+ buckets = new Map();
48
+ // ── Lifecycle ────────────────────────────────────────────────────
49
+ entitySupport(schema) {
50
+ const supported = [];
51
+ const unsupported = [];
52
+ const bytesFields = new Set();
53
+ // Per-field canonical tag — the first recognised TYPE_TAGS entry in
54
+ // declaration order, matching how SQL backends pick a column type.
55
+ // Two schemas that resolve to different canonical tags for the same
56
+ // field name carry incompatible expectations and must collide.
57
+ const canonical = new Map();
58
+ for (const field of schema.fields) {
59
+ const first = field.type.find((t) => KNOWN_TAGS.has(t));
60
+ if (first === undefined) {
61
+ unsupported.push({
62
+ name: field.name,
63
+ reason: field.type.length === 0
64
+ ? "field declares no type tags"
65
+ : `no recognised tag in [${field.type.join(", ")}]`,
66
+ });
67
+ continue;
68
+ }
69
+ supported.push(field.name);
70
+ canonical.set(field.name, first);
71
+ if (first === TYPE_TAGS.BYTES)
72
+ bytesFields.add(field.name);
73
+ }
74
+ const support = {
75
+ entity: schema.name,
76
+ supported,
77
+ unsupported,
78
+ };
79
+ return {
80
+ support,
81
+ declared: new Set(supported),
82
+ bytesFields,
83
+ signature: computeSignature(schema.name, canonical),
84
+ };
85
+ }
86
+ // deno-lint-ignore require-await
87
+ async entityStatus(meta) {
88
+ const bucket = this.buckets.get(meta.support.entity);
89
+ if (!bucket)
90
+ return "unprovisioned";
91
+ return bucket.signature === meta.signature ? "live" : "unprovisioned";
92
+ }
93
+ // deno-lint-ignore require-await
94
+ async provisionEntity(meta) {
95
+ const existing = this.buckets.get(meta.support.entity);
96
+ if (existing) {
97
+ if (existing.signature !== meta.signature) {
98
+ throw new Error(`${STORE_NAME}: entity '${meta.support.entity}' is already ` +
99
+ `provisioned with a different shape`);
100
+ }
101
+ return;
102
+ }
103
+ this.buckets.set(meta.support.entity, {
104
+ signature: meta.signature,
105
+ records: new Map(),
106
+ });
107
+ }
108
+ // ── Write ────────────────────────────────────────────────────────
109
+ async write(meta, entries) {
110
+ const bucket = this.buckets.get(meta.support.entity);
111
+ const results = [];
112
+ for (const { uri, record } of entries) {
113
+ if (!bucket) {
114
+ results.push({
115
+ success: false,
116
+ ...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
117
+ });
118
+ continue;
119
+ }
120
+ const extras = Object.keys(record).filter((k) => !meta.declared.has(k));
121
+ if (extras.length > 0) {
122
+ results.push({
123
+ success: false,
124
+ ...storageFailure(new Error(`record contains keys not declared in schema '${meta.support.entity}': ${extras.join(", ")}`), "Schema mismatch", uri),
125
+ });
126
+ continue;
127
+ }
128
+ try {
129
+ const normalised = needsBytesNormalisation(record, meta.bytesFields)
130
+ ? await normaliseBytesFields(record, meta.bytesFields)
131
+ : { ...record };
132
+ bucket.records.set(uri, normalised);
133
+ results.push({ success: true });
134
+ }
135
+ catch (err) {
136
+ results.push({
137
+ success: false,
138
+ ...storageFailure(err, "Write failed", uri),
139
+ });
140
+ }
141
+ }
142
+ return results;
143
+ }
144
+ // ── Read ─────────────────────────────────────────────────────────
145
+ // deno-lint-ignore require-await
146
+ async read(meta, urls) {
147
+ const bucket = this.buckets.get(meta.support.entity);
148
+ return urls.map((url) => {
149
+ const parsed = parseUrl(url);
150
+ switch (parsed.fn) {
151
+ case "read":
152
+ return [url, bucket?.records.get(parsed.uri)];
153
+ case "ls":
154
+ return [url, this._list(bucket?.records, parsed)];
155
+ case "count":
156
+ return [url, this._count(bucket?.records, parsed)];
157
+ default:
158
+ throw new Error(`${STORE_NAME}: unsupported fn '${parsed.fn}'`);
159
+ }
160
+ });
161
+ }
162
+ _walk(bucket, uri) {
163
+ if (!bucket)
164
+ return [];
165
+ const prefix = uri.endsWith("/") ? uri : `${uri}/`;
166
+ const out = [];
167
+ for (const [k, rec] of bucket) {
168
+ if (!k.startsWith(prefix))
169
+ continue;
170
+ const rest = k.slice(prefix.length);
171
+ if (rest.length === 0 || rest.includes("/"))
172
+ continue;
173
+ out.push([k, rec]);
174
+ }
175
+ return out;
176
+ }
177
+ _list(bucket, parsed) {
178
+ const { params } = parsed;
179
+ if (params.pattern !== undefined) {
180
+ throw new Error(`${STORE_NAME}: pattern filter not supported`);
181
+ }
182
+ if (params.sortBy !== undefined && params.sortBy !== "uri") {
183
+ throw new Error(`${STORE_NAME}: unsupported sortBy: ${params.sortBy}`);
184
+ }
185
+ const format = params.format ?? "full";
186
+ if (format !== "full" && format !== "uris") {
187
+ throw new Error(`${STORE_NAME}: unsupported format: ${format}`);
188
+ }
189
+ let entries = this._walk(bucket, parsed.uri);
190
+ if (params.sortBy === "uri") {
191
+ const dir = params.sortOrder === "desc" ? -1 : 1;
192
+ entries = [...entries].sort(([a], [b]) => a.localeCompare(b) * dir);
193
+ }
194
+ if (params.limit !== undefined) {
195
+ const page = params.page ?? 1;
196
+ const start = (page - 1) * params.limit;
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`);
206
+ }
207
+ return this._walk(bucket, parsed.uri).length;
208
+ }
209
+ // ── Delete ───────────────────────────────────────────────────────
210
+ delete(meta, uris) {
211
+ const bucket = this.buckets.get(meta.support.entity);
212
+ const results = [];
213
+ for (const uri of uris) {
214
+ if (!bucket) {
215
+ results.push({
216
+ success: false,
217
+ ...storageFailure(new Error(`${STORE_NAME}: entity '${meta.support.entity}' is not provisioned`), "Entity not provisioned", uri),
218
+ });
219
+ continue;
220
+ }
221
+ try {
222
+ bucket.records.delete(uri);
223
+ results.push({ success: true });
224
+ }
225
+ catch (err) {
226
+ results.push({
227
+ success: false,
228
+ ...storageFailure(err, "Delete failed", uri),
229
+ });
230
+ }
231
+ }
232
+ return Promise.resolve(results);
233
+ }
234
+ // ── Status / capabilities ────────────────────────────────────────
235
+ status() {
236
+ return Promise.resolve({
237
+ status: "healthy",
238
+ schema: [...this.buckets.keys()].map((n) => `entity:${n}`),
239
+ fns: ["read", "ls", "count"],
240
+ });
241
+ }
242
+ capabilities() {
243
+ return { atomicBatch: false };
244
+ }
245
+ }
246
+ /**
247
+ * Canonical signature for collision detection. Two metas produced
248
+ * from semantically-identical schemas (same name, same set of
249
+ * supported fields, same canonical type tag per field) yield the same
250
+ * string; any shape difference — including a field whose tag changed
251
+ * from `"string"` to `"bigint"` — yields a different one.
252
+ *
253
+ * Matches the granularity SQL backends get for free via column-type
254
+ * introspection: a tag swap on an existing field is a collision even
255
+ * though the field name is unchanged.
256
+ */
257
+ function computeSignature(name, canonical) {
258
+ const fields = [...canonical.entries()]
259
+ .sort(([a], [b]) => a.localeCompare(b));
260
+ return JSON.stringify({ name, fields });
261
+ }
262
+ /**
263
+ * Quick predicate: does the record have a `ReadableStream` (or a
264
+ * non-bytes value that needs to be rejected) on any of its bytes
265
+ * fields? Lets the write hot path skip the async normalisation step
266
+ * when every value is already a `Uint8Array`.
267
+ */
268
+ function needsBytesNormalisation(record, bytesFields) {
269
+ for (const name of bytesFields) {
270
+ const v = record[name];
271
+ if (v === undefined || v === null)
272
+ continue;
273
+ if (v instanceof Uint8Array)
274
+ continue;
275
+ return true;
276
+ }
277
+ return false;
278
+ }
279
+ /**
280
+ * Collect any `ReadableStream` values on `bytes`-tagged fields into
281
+ * `Uint8Array` and return a shallow-copied record. Non-stream values
282
+ * pass through; this is the same coercion every backend with a
283
+ * non-streaming write path performs, generalised across fields.
284
+ */
285
+ async function normaliseBytesFields(record, bytesFields) {
286
+ const out = { ...record };
287
+ for (const name of bytesFields) {
288
+ const v = out[name];
289
+ if (v === undefined || v === null)
290
+ continue;
291
+ if (v instanceof Uint8Array)
292
+ continue;
293
+ if (v instanceof ReadableStream) {
294
+ out[name] = await toBytes(v);
295
+ continue;
296
+ }
297
+ throw new Error(`field '${name}' must be Uint8Array or ReadableStream, got ${typeof v}`);
298
+ }
299
+ return out;
300
+ }
package/esm/mod.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @module
3
+ * b3nd-save — the data-saving layer for b3nd.
4
+ *
5
+ * This root barrel re-exports every public subpath as a namespace.
6
+ * Use it for convenience or discoverability; for the smallest
7
+ * footprint, import the subpaths directly (e.g.
8
+ * `@bandeira-tech/b3nd-save/postgres`).
9
+ *
10
+ * Namespaces:
11
+ * clients — EntityStore → ProtocolInterfaceNode adapters
12
+ * memory — In-memory reference backend
13
+ * postgres, mongo, sqlite, fs, ipfs, s3, elasticsearch,
14
+ * localstorage, indexeddb — EntityStore backend implementations
15
+ *
16
+ * The entity surface (`EntityStore`, `EntitySchema`, `BYTES_ENTITY`,
17
+ * `TYPE_TAGS`, …) is re-exported from the root for convenience.
18
+ */
19
+ export type { StoreCapabilities, StoreWriteResult } from "./types.js";
20
+ export type { ParsedUrl, ReadParams } from "./url.js";
21
+ export { buildUrl, parseUrl, uriOf } from "./url.js";
22
+ export type { EntityField, EntityMeta, EntityRecord, EntitySchema, EntitySupport, } from "./entity.js";
23
+ export { BYTES_ENTITY, TYPE_TAGS } from "./entity.js";
24
+ export type { EntityStore } from "./entity-store.js";
25
+ export * as clients from "./clients/mod.js";
26
+ export * as memory from "./memory/mod.js";
27
+ export * as postgres from "./postgres/mod.js";
28
+ export * as mongo from "./mongo/mod.js";
29
+ export * as sqlite from "./sqlite/mod.js";
30
+ export * as fs from "./fs/mod.js";
31
+ export * as ipfs from "./ipfs/mod.js";
32
+ export * as s3 from "./s3/mod.js";
33
+ export * as elasticsearch from "./elasticsearch/mod.js";
34
+ export * as localstorage from "./localstorage/mod.js";
35
+ export * as indexeddb from "./indexeddb/mod.js";
36
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +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"}
package/esm/mod.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module
3
+ * b3nd-save — the data-saving layer for b3nd.
4
+ *
5
+ * This root barrel re-exports every public subpath as a namespace.
6
+ * Use it for convenience or discoverability; for the smallest
7
+ * footprint, import the subpaths directly (e.g.
8
+ * `@bandeira-tech/b3nd-save/postgres`).
9
+ *
10
+ * Namespaces:
11
+ * clients — EntityStore → ProtocolInterfaceNode adapters
12
+ * memory — In-memory reference backend
13
+ * postgres, mongo, sqlite, fs, ipfs, s3, elasticsearch,
14
+ * localstorage, indexeddb — EntityStore backend implementations
15
+ *
16
+ * The entity surface (`EntityStore`, `EntitySchema`, `BYTES_ENTITY`,
17
+ * `TYPE_TAGS`, …) is re-exported from the root for convenience.
18
+ */
19
+ export { buildUrl, parseUrl, uriOf } from "./url.js";
20
+ export { BYTES_ENTITY, TYPE_TAGS } from "./entity.js";
21
+ export * as clients from "./clients/mod.js";
22
+ export * as memory from "./memory/mod.js";
23
+ export * as postgres from "./postgres/mod.js";
24
+ export * as mongo from "./mongo/mod.js";
25
+ export * as sqlite from "./sqlite/mod.js";
26
+ export * as fs from "./fs/mod.js";
27
+ export * as ipfs from "./ipfs/mod.js";
28
+ export * as s3 from "./s3/mod.js";
29
+ export * as elasticsearch from "./elasticsearch/mod.js";
30
+ export * as localstorage from "./localstorage/mod.js";
31
+ export * as indexeddb from "./indexeddb/mod.js";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * MongoDB backend for b3nd.
3
+ *
4
+ * Store implementation backed by MongoDB. Requires an injected
5
+ * MongoExecutor so the package does not depend on a specific MongoDB
6
+ * driver.
7
+ */
8
+ export interface MongoFindManyOptions {
9
+ sort?: Record<string, 1 | -1>;
10
+ skip?: number;
11
+ limit?: number;
12
+ /** Mongo projection map (1 = include, 0 = exclude). */
13
+ projection?: Record<string, 0 | 1>;
14
+ }
15
+ export interface MongoExecutor {
16
+ insertOne(doc: Record<string, unknown>): Promise<{
17
+ acknowledged?: boolean;
18
+ }>;
19
+ updateOne(filter: Record<string, unknown>, update: Record<string, unknown>, options?: Record<string, unknown>): Promise<{
20
+ matchedCount?: number;
21
+ modifiedCount?: number;
22
+ upsertedId?: unknown;
23
+ }>;
24
+ findOne(filter: Record<string, unknown>): Promise<Record<string, unknown> | null>;
25
+ findMany(filter: Record<string, unknown>, options?: MongoFindManyOptions): Promise<Record<string, unknown>[]>;
26
+ countDocuments(filter: Record<string, unknown>): Promise<number>;
27
+ deleteOne(filter: Record<string, unknown>): Promise<{
28
+ deletedCount?: number;
29
+ }>;
30
+ ping(): Promise<boolean>;
31
+ transaction?: <T>(fn: (executor: MongoExecutor) => Promise<T>) => Promise<T>;
32
+ cleanup?: () => Promise<void>;
33
+ }
34
+ export { MongoStore } from "./store.js";
35
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/mongo/mod.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,SAAS,CACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CACR;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CACxE,CAAC;IACF,OAAO,CACL,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3C,QAAQ,CACN,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACtC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE,SAAS,CACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * MongoDB backend for b3nd.
3
+ *
4
+ * Store implementation backed by MongoDB. Requires an injected
5
+ * MongoExecutor so the package does not depend on a specific MongoDB
6
+ * driver.
7
+ */
8
+ export { MongoStore } from "./store.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * MongoStore — MongoDB implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. Uses an
5
+ * injected MongoExecutor so the package does not depend on a specific
6
+ * MongoDB driver. Payload is stored as a BSON `Binary` value (the
7
+ * `mongodb` driver maps `Uint8Array` to it transparently).
8
+ *
9
+ * `fn=ls`/`fn=count` push down to a regex prefix query that enforces
10
+ * the shallow-direct-leaves contract: `^<prefix>[^/]+$`.
11
+ */
12
+ import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
13
+ import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
14
+ import type { EntityStore } from "../entity-store.js";
15
+ import type { EntityRecord, EntitySchema } from "../entity.js";
16
+ import type { StoreCapabilities, StoreWriteResult } from "../types.js";
17
+ import type { MongoExecutor } from "./mod.js";
18
+ export declare class MongoStore implements EntityStore<BytesOnlyEntityMeta> {
19
+ private readonly collectionName;
20
+ private readonly executor;
21
+ constructor(collectionName: string, executor: MongoExecutor);
22
+ entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
23
+ entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
24
+ provisionEntity(_meta: BytesOnlyEntityMeta): Promise<void>;
25
+ write(meta: BytesOnlyEntityMeta, entries: {
26
+ uri: string;
27
+ record: EntityRecord;
28
+ }[]): Promise<StoreWriteResult[]>;
29
+ read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
30
+ delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
31
+ private _writeBytes;
32
+ private _readBytes;
33
+ private _readOne;
34
+ /** Build the shallow-direct-leaves regex filter for a prefix. */
35
+ private _leafFilter;
36
+ private _ls;
37
+ private _count;
38
+ private _deleteBytes;
39
+ status(): Promise<StatusResult>;
40
+ capabilities(): StoreCapabilities;
41
+ }
42
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/mongo/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAO/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiC9C,qBAAa,UAAW,YAAW,WAAW,CAAC,mBAAmB,CAAC;IACjE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;gBAE7B,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa;IAU3D,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,mBAAmB;IAIxD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAI1E,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,KAAK,CACH,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAS9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IASvB,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAW5D,WAAW;IA+BzB,OAAO,CAAC,UAAU;YAQJ,QAAQ;IAMtB,iEAAiE;IACjE,OAAO,CAAC,WAAW;YAIL,GAAG;YA2BH,MAAM;YAON,YAAY;IAoBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAyBrC,YAAY,IAAI,iBAAiB;CAGlC"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * MongoStore — MongoDB implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. Uses an
5
+ * injected MongoExecutor so the package does not depend on a specific
6
+ * MongoDB driver. Payload is stored as a BSON `Binary` value (the
7
+ * `mongodb` driver maps `Uint8Array` to it transparently).
8
+ *
9
+ * `fn=ls`/`fn=count` push down to a regex prefix query that enforces
10
+ * the shallow-direct-leaves contract: `^<prefix>[^/]+$`.
11
+ */
12
+ import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
13
+ import { dispatchRead } from "../dispatch.js";
14
+ import { storageFailure } from "../errors.js";
15
+ import { toBytes } from "../payload.js";
16
+ import { validateReadParams } from "../read.js";
17
+ const STORE_NAME = "MongoStore";
18
+ /** Escape special regex characters for safe use in a RegExp pattern. */
19
+ function escapeRegex(input) {
20
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21
+ }
22
+ /**
23
+ * Normalize a Mongo document field into a `Uint8Array`. The driver may
24
+ * surface BSON `Binary` (which exposes `.buffer`) or a raw
25
+ * `Uint8Array` depending on options.
26
+ */
27
+ function docToBytes(value) {
28
+ if (value instanceof Uint8Array)
29
+ return value;
30
+ if (value && typeof value === "object" &&
31
+ "buffer" in value &&
32
+ value.buffer instanceof Uint8Array) {
33
+ return value.buffer;
34
+ }
35
+ if (value && typeof value === "object" &&
36
+ "buffer" in value &&
37
+ value.buffer instanceof ArrayBuffer) {
38
+ return new Uint8Array(value.buffer);
39
+ }
40
+ throw new Error(`MongoStore: unexpected payload type ${typeof value}`);
41
+ }
42
+ export class MongoStore {
43
+ collectionName;
44
+ executor;
45
+ constructor(collectionName, executor) {
46
+ if (!collectionName)
47
+ throw new Error("collectionName is required");
48
+ if (!executor)
49
+ throw new Error("executor is required");
50
+ this.collectionName = collectionName;
51
+ this.executor = executor;
52
+ }
53
+ // ── EntityStore surface ──────────────────────────────────────────
54
+ entitySupport(schema) {
55
+ return bytesOnlyEntitySupport(schema);
56
+ }
57
+ entityStatus(meta) {
58
+ return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
59
+ }
60
+ provisionEntity(_meta) {
61
+ return Promise.resolve();
62
+ }
63
+ write(meta, entries) {
64
+ return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
65
+ }
66
+ read(meta, urls) {
67
+ return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
68
+ }
69
+ delete(meta, uris) {
70
+ return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
71
+ }
72
+ // ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
73
+ async _writeBytes(entries) {
74
+ const results = [];
75
+ for (const entry of entries) {
76
+ try {
77
+ const bytes = await toBytes(entry.payload);
78
+ await this.executor.updateOne({ uri: entry.uri }, {
79
+ $set: {
80
+ uri: entry.uri,
81
+ payload: bytes,
82
+ updatedAt: new Date(),
83
+ },
84
+ }, { upsert: true });
85
+ results.push({ success: true });
86
+ }
87
+ catch (err) {
88
+ results.push({
89
+ success: false,
90
+ ...storageFailure(err, "Write failed", entry.uri),
91
+ });
92
+ }
93
+ }
94
+ return results;
95
+ }
96
+ _readBytes(urls) {
97
+ return dispatchRead(urls, STORE_NAME, {
98
+ read: (p) => this._readOne(p.uri),
99
+ ls: (p) => this._ls(p),
100
+ count: (p) => this._count(p),
101
+ });
102
+ }
103
+ async _readOne(uri) {
104
+ const doc = await this.executor.findOne({ uri });
105
+ if (!doc)
106
+ return undefined;
107
+ return docToBytes(doc.payload);
108
+ }
109
+ /** Build the shallow-direct-leaves regex filter for a prefix. */
110
+ _leafFilter(prefixUri) {
111
+ return { uri: { $regex: `^${escapeRegex(prefixUri)}[^/]+$` } };
112
+ }
113
+ async _ls(parsed) {
114
+ validateReadParams(parsed.params, STORE_NAME);
115
+ const { params } = parsed;
116
+ const format = params.format ?? "full";
117
+ const options = {};
118
+ if (params.sortBy === "uri") {
119
+ options.sort = { uri: params.sortOrder === "desc" ? -1 : 1 };
120
+ }
121
+ if (params.limit !== undefined) {
122
+ const page = params.page ?? 1;
123
+ options.limit = params.limit;
124
+ options.skip = (page - 1) * params.limit;
125
+ }
126
+ if (format === "uris") {
127
+ options.projection = { uri: 1, _id: 0 };
128
+ }
129
+ const docs = await this.executor.findMany(this._leafFilter(parsed.uri), options);
130
+ if (format === "uris")
131
+ return docs.map((d) => d.uri);
132
+ return docs.map((d) => [d.uri, docToBytes(d.payload)]);
133
+ }
134
+ async _count(parsed) {
135
+ if (parsed.params.pattern !== undefined) {
136
+ throw new Error(`${STORE_NAME}: pattern filter not supported`);
137
+ }
138
+ return await this.executor.countDocuments(this._leafFilter(parsed.uri));
139
+ }
140
+ async _deleteBytes(uris) {
141
+ const results = [];
142
+ for (const uri of uris) {
143
+ try {
144
+ await this.executor.deleteOne({ uri });
145
+ results.push({ success: true });
146
+ }
147
+ catch (err) {
148
+ results.push({
149
+ success: false,
150
+ ...storageFailure(err, "Delete failed", uri),
151
+ });
152
+ }
153
+ }
154
+ return results;
155
+ }
156
+ // ── Status ───────────────────────────────────────────────────────
157
+ async status() {
158
+ try {
159
+ const ok = await this.executor.ping();
160
+ if (!ok) {
161
+ return {
162
+ status: "unhealthy",
163
+ message: "MongoDB ping failed",
164
+ fns: ["read", "ls", "count"],
165
+ };
166
+ }
167
+ return {
168
+ status: "healthy",
169
+ message: "MongoDB store is operational",
170
+ fns: ["read", "ls", "count"],
171
+ details: { collectionName: this.collectionName },
172
+ };
173
+ }
174
+ catch (error) {
175
+ return {
176
+ status: "unhealthy",
177
+ message: error instanceof Error ? error.message : String(error),
178
+ fns: ["read", "ls", "count"],
179
+ };
180
+ }
181
+ }
182
+ capabilities() {
183
+ return { atomicBatch: false };
184
+ }
185
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }