@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
package/esm/read.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * In-process post-processing for `fn=ls` results.
3
+ *
4
+ * Stores that cannot push `sortBy`/`limit`/`page`/`format` down to
5
+ * their backend collect the raw `Output[]` rows under a prefix and
6
+ * pipe them through `applyReadParams`. Stores that CAN push these
7
+ * down (postgres, mongo, elasticsearch, s3) should skip this helper
8
+ * and handle the params in their query.
9
+ *
10
+ * Throws on unsupported params — programmer errors are not silent
11
+ * "misses." See project decisions in `project_core_upgrade.md`.
12
+ */
13
+ /**
14
+ * Validate standard ReadParams and throw on anything we cannot honor.
15
+ *
16
+ * Push-down stores (postgres, mongo, ES, S3) call this first, then
17
+ * translate the surviving params into their backend query language.
18
+ * Stores that post-process in memory call `applyReadParams` instead,
19
+ * which validates and applies in one go.
20
+ *
21
+ * Project-wide baseline: `pattern` and `cursor` are unsupported
22
+ * everywhere; `sortBy` only accepts `"uri"`; `format` only accepts
23
+ * `"full"` (default) or `"uris"`. Per-store relaxations should be
24
+ * added explicitly as features land.
25
+ */
26
+ export function validateReadParams(params, storeName) {
27
+ if (params.pattern !== undefined) {
28
+ throw new Error(`${storeName}: pattern filter not supported`);
29
+ }
30
+ if (params.cursor !== undefined) {
31
+ throw new Error(`${storeName}: cursor not supported`);
32
+ }
33
+ if (params.sortBy !== undefined && params.sortBy !== "uri") {
34
+ throw new Error(`${storeName}: unsupported sortBy: ${params.sortBy}`);
35
+ }
36
+ const format = params.format ?? "full";
37
+ if (format !== "full" && format !== "uris") {
38
+ throw new Error(`${storeName}: unsupported format: ${format}`);
39
+ }
40
+ }
41
+ /**
42
+ * Apply standard ReadParams to a list of rows.
43
+ *
44
+ * @param rows raw `[uri, payload]` entries collected from the backend
45
+ * @param params parsed read params (from `parseUrl(...).params`)
46
+ * @param storeName label used in thrown error messages
47
+ *
48
+ * Returns `Output[]` when `format` is `"full"` (default) or `string[]`
49
+ * when `format` is `"uris"`.
50
+ */
51
+ export function applyReadParams(rows, params, storeName) {
52
+ validateReadParams(params, storeName);
53
+ const format = params.format ?? "full";
54
+ let out = rows;
55
+ if (params.sortBy === "uri") {
56
+ const dir = params.sortOrder === "desc" ? -1 : 1;
57
+ out = [...out].sort(([a], [b]) => a.localeCompare(b) * dir);
58
+ }
59
+ if (params.limit !== undefined) {
60
+ const page = params.page ?? 1;
61
+ const start = (page - 1) * params.limit;
62
+ out = out.slice(start, start + params.limit);
63
+ }
64
+ if (format === "uris")
65
+ return out.map(([uri]) => uri);
66
+ return out;
67
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * S3 backend for b3nd.
3
+ *
4
+ * Store implementation backed by Amazon S3. Requires an injected
5
+ * S3Executor so the package does not depend on a specific S3 library.
6
+ * Object bodies hold raw payload bytes — the store is opaque.
7
+ *
8
+ * `putObject` accepts the `StorePayload` union (fetch handles either
9
+ * bytes or a `ReadableStream` as a request body); `getObject` returns
10
+ * a `ReadableStream<Uint8Array>` so callers don't have to materialize
11
+ * the whole object in memory unless they want to.
12
+ */
13
+ import type { StorePayload } from "../types.js";
14
+ export interface S3Executor {
15
+ putObject: (key: string, body: StorePayload, contentType: string) => Promise<void>;
16
+ getObject: (key: string) => Promise<ReadableStream<Uint8Array> | null>;
17
+ deleteObject: (key: string) => Promise<void>;
18
+ listObjects: (prefix: string) => Promise<string[]>;
19
+ headBucket: () => Promise<boolean>;
20
+ cleanup?: () => Promise<void>;
21
+ }
22
+ export { S3Store } from "./store.js";
23
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/s3/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,CACT,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IACvE,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
package/esm/s3/mod.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * S3 backend for b3nd.
3
+ *
4
+ * Store implementation backed by Amazon S3. Requires an injected
5
+ * S3Executor so the package does not depend on a specific S3 library.
6
+ * Object bodies hold raw payload bytes — the store is opaque.
7
+ *
8
+ * `putObject` accepts the `StorePayload` union (fetch handles either
9
+ * bytes or a `ReadableStream` as a request body); `getObject` returns
10
+ * a `ReadableStream<Uint8Array>` so callers don't have to materialize
11
+ * the whole object in memory unless they want to.
12
+ */
13
+ export { S3Store } from "./store.js";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * S3Store — Amazon S3 implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. One S3
5
+ * object per entry. Object body is the payload bytes verbatim; the
6
+ * store is opaque.
7
+ *
8
+ * `fn=ls` / `fn=count` are shallow direct-leaves only: list objects
9
+ * under the URI's key prefix, then filter to keys whose remainder
10
+ * contains no further `/`. The `format=uris` fast path returns the
11
+ * URIs without issuing `getObject`; `format=full` only fetches the
12
+ * selected page.
13
+ */
14
+ import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
15
+ import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
16
+ import type { EntityStore } from "../entity-store.js";
17
+ import type { EntityRecord, EntitySchema } from "../entity.js";
18
+ import type { StoreCapabilities, StoreWriteResult } from "../types.js";
19
+ import type { S3Executor } from "./mod.js";
20
+ export declare class S3Store implements EntityStore<BytesOnlyEntityMeta> {
21
+ private readonly bucket;
22
+ private readonly executor;
23
+ private readonly prefix;
24
+ constructor(bucket: string, executor: S3Executor, prefix?: string);
25
+ entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
26
+ entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
27
+ provisionEntity(_meta: BytesOnlyEntityMeta): Promise<void>;
28
+ write(meta: BytesOnlyEntityMeta, entries: {
29
+ uri: string;
30
+ record: EntityRecord;
31
+ }[]): Promise<StoreWriteResult[]>;
32
+ read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
33
+ delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
34
+ private _writeBytes;
35
+ private _readBytes;
36
+ private _readOne;
37
+ /** List direct-child URIs under a prefix (no recursion into sub-prefixes). */
38
+ private _listChildUris;
39
+ private _ls;
40
+ private _count;
41
+ private _deleteBytes;
42
+ status(): Promise<StatusResult>;
43
+ capabilities(): StoreCapabilities;
44
+ private _resolveKey;
45
+ }
46
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/s3/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAM/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAgB3C,qBAAa,OAAQ,YAAW,WAAW,CAAC,mBAAmB,CAAC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM;IAWjE,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;IAwBzB,OAAO,CAAC,UAAU;YAQJ,QAAQ;IAOtB,8EAA8E;YAChE,cAAc;YAad,GAAG;YA0BH,MAAM;YAON,YAAY;IAoBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IA4BrC,YAAY,IAAI,iBAAiB;IAIjC,OAAO,CAAC,WAAW;CAGpB"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * S3Store — Amazon S3 implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. One S3
5
+ * object per entry. Object body is the payload bytes verbatim; the
6
+ * store is opaque.
7
+ *
8
+ * `fn=ls` / `fn=count` are shallow direct-leaves only: list objects
9
+ * under the URI's key prefix, then filter to keys whose remainder
10
+ * contains no further `/`. The `format=uris` fast path returns the
11
+ * URIs without issuing `getObject`; `format=full` only fetches the
12
+ * selected page.
13
+ */
14
+ import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
15
+ import { dispatchRead } from "../dispatch.js";
16
+ import { storageFailure } from "../errors.js";
17
+ import { validateReadParams } from "../read.js";
18
+ const STORE_NAME = "S3Store";
19
+ const EXT = ".bin";
20
+ /** `protocol://host/path` → `protocol_host/path`. */
21
+ function uriToKey(uri) {
22
+ return uri.replace("://", "_").replace(/^\//, "");
23
+ }
24
+ /** Inverse of `uriToKey` with `.bin` suffix stripping. */
25
+ function keyTailToUri(tail) {
26
+ const noExt = tail.endsWith(EXT) ? tail.slice(0, -EXT.length) : tail;
27
+ return noExt.replace("_", "://");
28
+ }
29
+ export class S3Store {
30
+ bucket;
31
+ executor;
32
+ prefix;
33
+ constructor(bucket, executor, prefix) {
34
+ if (!bucket)
35
+ throw new Error("bucket is required");
36
+ if (!executor)
37
+ throw new Error("executor is required");
38
+ this.bucket = bucket;
39
+ this.executor = executor;
40
+ this.prefix = prefix ?? "";
41
+ }
42
+ // ── EntityStore surface ──────────────────────────────────────────
43
+ entitySupport(schema) {
44
+ return bytesOnlyEntitySupport(schema);
45
+ }
46
+ entityStatus(meta) {
47
+ return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
48
+ }
49
+ provisionEntity(_meta) {
50
+ return Promise.resolve();
51
+ }
52
+ write(meta, entries) {
53
+ return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
54
+ }
55
+ read(meta, urls) {
56
+ return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
57
+ }
58
+ delete(meta, uris) {
59
+ return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
60
+ }
61
+ // ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
62
+ async _writeBytes(entries) {
63
+ const results = [];
64
+ for (const entry of entries) {
65
+ try {
66
+ await this.executor.putObject(this._resolveKey(entry.uri), entry.payload, "application/octet-stream");
67
+ results.push({ success: true });
68
+ }
69
+ catch (err) {
70
+ results.push({
71
+ success: false,
72
+ ...storageFailure(err, "Write failed", entry.uri),
73
+ });
74
+ }
75
+ }
76
+ return results;
77
+ }
78
+ _readBytes(urls) {
79
+ return dispatchRead(urls, STORE_NAME, {
80
+ read: (p) => this._readOne(p.uri),
81
+ ls: (p) => this._ls(p),
82
+ count: (p) => this._count(p),
83
+ });
84
+ }
85
+ async _readOne(uri) {
86
+ const content = await this.executor.getObject(this._resolveKey(uri));
87
+ return content ?? undefined;
88
+ }
89
+ /** List direct-child URIs under a prefix (no recursion into sub-prefixes). */
90
+ async _listChildUris(prefixUri) {
91
+ const keyPrefix = `${this.prefix}${uriToKey(prefixUri)}`;
92
+ const keys = await this.executor.listObjects(keyPrefix);
93
+ const uris = [];
94
+ for (const key of keys) {
95
+ if (!key.endsWith(EXT))
96
+ continue;
97
+ const tail = key.slice(keyPrefix.length);
98
+ if (tail.includes("/"))
99
+ continue; // nested, not a direct leaf
100
+ uris.push(`${prefixUri}${keyTailToUri(tail)}`);
101
+ }
102
+ return uris;
103
+ }
104
+ async _ls(parsed) {
105
+ validateReadParams(parsed.params, STORE_NAME);
106
+ const { params } = parsed;
107
+ const format = params.format ?? "full";
108
+ let uris = await this._listChildUris(parsed.uri);
109
+ if (params.sortBy === "uri") {
110
+ const dir = params.sortOrder === "desc" ? -1 : 1;
111
+ uris = [...uris].sort((a, b) => a.localeCompare(b) * dir);
112
+ }
113
+ if (params.limit !== undefined) {
114
+ const page = params.page ?? 1;
115
+ const start = (page - 1) * params.limit;
116
+ uris = uris.slice(start, start + params.limit);
117
+ }
118
+ if (format === "uris")
119
+ return uris;
120
+ const out = [];
121
+ for (const uri of uris) {
122
+ out.push([uri, await this._readOne(uri)]);
123
+ }
124
+ return out;
125
+ }
126
+ async _count(parsed) {
127
+ if (parsed.params.pattern !== undefined) {
128
+ throw new Error(`${STORE_NAME}: pattern filter not supported`);
129
+ }
130
+ return (await this._listChildUris(parsed.uri)).length;
131
+ }
132
+ async _deleteBytes(uris) {
133
+ const results = [];
134
+ for (const uri of uris) {
135
+ try {
136
+ await this.executor.deleteObject(this._resolveKey(uri));
137
+ results.push({ success: true });
138
+ }
139
+ catch (err) {
140
+ results.push({
141
+ success: false,
142
+ ...storageFailure(err, "Delete failed", uri),
143
+ });
144
+ }
145
+ }
146
+ return results;
147
+ }
148
+ // ── Status ───────────────────────────────────────────────────────
149
+ async status() {
150
+ try {
151
+ const ok = await this.executor.headBucket();
152
+ if (!ok) {
153
+ return {
154
+ status: "unhealthy",
155
+ message: `Bucket not accessible: ${this.bucket}`,
156
+ fns: ["read", "ls", "count"],
157
+ };
158
+ }
159
+ return {
160
+ status: "healthy",
161
+ message: "S3 store is operational",
162
+ fns: ["read", "ls", "count"],
163
+ details: {
164
+ bucket: this.bucket,
165
+ prefix: this.prefix || "(none)",
166
+ },
167
+ };
168
+ }
169
+ catch (error) {
170
+ return {
171
+ status: "unhealthy",
172
+ message: error instanceof Error ? error.message : String(error),
173
+ fns: ["read", "ls", "count"],
174
+ };
175
+ }
176
+ }
177
+ capabilities() {
178
+ return { atomicBatch: false };
179
+ }
180
+ _resolveKey(uri) {
181
+ return `${this.prefix}${uriToKey(uri)}${EXT}`;
182
+ }
183
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * SQLite backend for b3nd.
3
+ *
4
+ * Store implementation backed by SQLite. Requires an injected SqliteExecutor
5
+ * so the SDK does not depend on a specific SQLite driver.
6
+ */
7
+ export interface SqliteExecutorResult {
8
+ rows: Record<string, unknown>[];
9
+ rowCount?: number;
10
+ }
11
+ export interface SqliteExecutor {
12
+ query: (sql: string, args?: unknown[]) => SqliteExecutorResult;
13
+ transaction: <T>(fn: (tx: SqliteExecutor) => T) => T;
14
+ cleanup?: () => void;
15
+ }
16
+ export { SqliteStore } from "./store.js";
17
+ export { generateSqliteSchema } from "./schema.js";
18
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/sqlite/mod.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,oBAAoB,CAAC;IAC/D,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SQLite backend for b3nd.
3
+ *
4
+ * Store implementation backed by SQLite. Requires an injected SqliteExecutor
5
+ * so the SDK does not depend on a specific SQLite driver.
6
+ */
7
+ export { SqliteStore } from "./store.js";
8
+ export { generateSqliteSchema } from "./schema.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SQLite schema utilities for b3nd.
3
+ *
4
+ * Generates SQLite-compatible DDL. The schema is intentionally
5
+ * minimal: `uri` (PK) + `payload` (BLOB) + timestamps. The store is
6
+ * opaque — `payload` is raw bytes, not JSON; higher layers own
7
+ * serialization.
8
+ */
9
+ export declare function generateSqliteSchema(tablePrefix: string): string;
10
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/sqlite/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAqBhE"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SQLite schema utilities for b3nd.
3
+ *
4
+ * Generates SQLite-compatible DDL. The schema is intentionally
5
+ * minimal: `uri` (PK) + `payload` (BLOB) + timestamps. The store is
6
+ * opaque — `payload` is raw bytes, not JSON; higher layers own
7
+ * serialization.
8
+ */
9
+ export function generateSqliteSchema(tablePrefix) {
10
+ if (!tablePrefix) {
11
+ throw new Error("tablePrefix is required and cannot be empty");
12
+ }
13
+ if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(tablePrefix)) {
14
+ throw new Error("tablePrefix must start with a letter and contain only letters, numbers, and underscores");
15
+ }
16
+ return `-- SQLite schema for b3nd storage
17
+ -- Table prefix: ${tablePrefix}
18
+
19
+ CREATE TABLE IF NOT EXISTS ${tablePrefix}_data (
20
+ uri TEXT PRIMARY KEY,
21
+ payload BLOB NOT NULL,
22
+ created_at TEXT DEFAULT (datetime('now')),
23
+ updated_at TEXT DEFAULT (datetime('now'))
24
+ );
25
+ `;
26
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * SqliteStore — SQLite implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. Pushes
5
+ * fn=ls/fn=count down to SQL using the shallow-direct-leaves
6
+ * predicate: `uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'`.
7
+ *
8
+ * Uses an injected synchronous SqliteExecutor; results are wrapped in
9
+ * Promise.resolve() to satisfy the async Store interface. `payload`
10
+ * is a BLOB; the store does not inspect or transform its contents.
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 { SqliteExecutor } from "./mod.js";
18
+ export declare class SqliteStore implements EntityStore<BytesOnlyEntityMeta> {
19
+ private readonly tableName;
20
+ private readonly executor;
21
+ private readonly provisioned;
22
+ constructor(tablePrefix: string, executor: SqliteExecutor);
23
+ entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
24
+ entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
25
+ provisionEntity(meta: BytesOnlyEntityMeta): Promise<void>;
26
+ write(meta: BytesOnlyEntityMeta, entries: {
27
+ uri: string;
28
+ record: EntityRecord;
29
+ }[]): Promise<StoreWriteResult[]>;
30
+ read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
31
+ delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
32
+ private _writeBytes;
33
+ private _readBytes;
34
+ private _readOne;
35
+ private _ls;
36
+ private _count;
37
+ private _deleteBytes;
38
+ status(): Promise<StatusResult>;
39
+ private schemaUris;
40
+ capabilities(): StoreCapabilities;
41
+ }
42
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sqlite/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,cAAc,EAAE,MAAM,UAAU,CAAC;AAW/C,qBAAa,WAAY,YAAW,WAAW,CAAC,mBAAmB,CAAC;IAClE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAK1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBAErC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc;IAUzD,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,mBAAmB;IAIxD,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAI1E,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD,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;IAqCzB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,GAAG;IA2BX,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,YAAY;IAwBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAoB/B,OAAO,CAAC,UAAU;IAIlB,YAAY,IAAI,iBAAiB;CAGlC"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * SqliteStore — SQLite implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. Pushes
5
+ * fn=ls/fn=count down to SQL using the shallow-direct-leaves
6
+ * predicate: `uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'`.
7
+ *
8
+ * Uses an injected synchronous SqliteExecutor; results are wrapped in
9
+ * Promise.resolve() to satisfy the async Store interface. `payload`
10
+ * is a BLOB; the store does not inspect or transform its contents.
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 = "SqliteStore";
18
+ function rowToBytes(value) {
19
+ if (value instanceof Uint8Array)
20
+ return value;
21
+ if (value instanceof ArrayBuffer)
22
+ return new Uint8Array(value);
23
+ if (Array.isArray(value))
24
+ return new Uint8Array(value);
25
+ throw new Error(`SqliteStore: unexpected payload type ${typeof value}`);
26
+ }
27
+ export class SqliteStore {
28
+ tableName;
29
+ executor;
30
+ // Names of entities provisioned on this store. SqliteStore currently
31
+ // routes only BYTES_ENTITY, so this is at most `{"bytes"}` — but the
32
+ // tracking shape matches MemoryStore so `status().schema` reads the
33
+ // same way across backends.
34
+ provisioned = new Set();
35
+ constructor(tablePrefix, executor) {
36
+ if (!tablePrefix)
37
+ throw new Error("tablePrefix is required");
38
+ if (!executor)
39
+ throw new Error("executor is required");
40
+ this.tableName = `${tablePrefix}_data`;
41
+ this.executor = executor;
42
+ }
43
+ // ── EntityStore surface ──────────────────────────────────────────
44
+ entitySupport(schema) {
45
+ return bytesOnlyEntitySupport(schema);
46
+ }
47
+ entityStatus(meta) {
48
+ return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
49
+ }
50
+ provisionEntity(meta) {
51
+ // Only BYTES_ENTITY is honored at the storage layer; record the
52
+ // name so `status()` surfaces what callers can actually use.
53
+ // Non-bytes schemas are silently ignored — `write`/`read` already
54
+ // surface "Unsupported schema" via the shim.
55
+ if (meta.isBytes)
56
+ this.provisioned.add(meta.schema.name);
57
+ return Promise.resolve();
58
+ }
59
+ write(meta, entries) {
60
+ return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
61
+ }
62
+ read(meta, urls) {
63
+ return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
64
+ }
65
+ delete(meta, uris) {
66
+ return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
67
+ }
68
+ // ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
69
+ async _writeBytes(entries) {
70
+ if (entries.length === 0)
71
+ return [];
72
+ // Collect streams up front: the executor is synchronous (BLOB
73
+ // wants bytes), and we don't want to await mid-transaction.
74
+ const prepared = await Promise.all(entries.map(async (e) => ({
75
+ uri: e.uri,
76
+ bytes: await toBytes(e.payload),
77
+ })));
78
+ // `capabilities.atomicBatch` is true: the whole batch commits or
79
+ // nothing does. On failure every result is `{ success: false }`
80
+ // with the same root-cause error.
81
+ try {
82
+ this.executor.transaction((tx) => {
83
+ for (const entry of prepared) {
84
+ tx.query(`INSERT INTO ${this.tableName} (uri, payload, updated_at)
85
+ VALUES (?, ?, datetime('now'))
86
+ ON CONFLICT(uri) DO UPDATE SET
87
+ payload = excluded.payload,
88
+ updated_at = datetime('now')`, [entry.uri, entry.bytes]);
89
+ }
90
+ });
91
+ return entries.map(() => ({ success: true }));
92
+ }
93
+ catch (err) {
94
+ const failure = storageFailure(err, "Write failed");
95
+ return entries.map(() => ({ success: false, ...failure }));
96
+ }
97
+ }
98
+ _readBytes(urls) {
99
+ return dispatchRead(urls, STORE_NAME, {
100
+ read: (p) => this._readOne(p.uri),
101
+ ls: (p) => this._ls(p),
102
+ count: (p) => this._count(p),
103
+ });
104
+ }
105
+ _readOne(uri) {
106
+ const result = this.executor.query(`SELECT payload FROM ${this.tableName} WHERE uri = ?`, [uri]);
107
+ if (!result.rows.length)
108
+ return undefined;
109
+ return rowToBytes(result.rows[0].payload);
110
+ }
111
+ _ls(parsed) {
112
+ validateReadParams(parsed.params, STORE_NAME);
113
+ const { params } = parsed;
114
+ const format = params.format ?? "full";
115
+ const select = format === "uris" ? "uri" : "uri, payload";
116
+ const order = params.sortBy === "uri"
117
+ ? ` ORDER BY uri ${params.sortOrder === "desc" ? "DESC" : "ASC"}`
118
+ : "";
119
+ let sql = `SELECT ${select} FROM ${this.tableName} WHERE uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'${order}`;
120
+ const args = [parsed.uri, parsed.uri];
121
+ if (params.limit !== undefined) {
122
+ const page = params.page ?? 1;
123
+ sql += ` LIMIT ? OFFSET ?`;
124
+ args.push(params.limit, (page - 1) * params.limit);
125
+ }
126
+ const result = this.executor.query(sql, args);
127
+ const rows = result.rows;
128
+ if (format === "uris")
129
+ return rows.map((r) => r.uri);
130
+ return rows.map((r) => [r.uri, rowToBytes(r.payload)]);
131
+ }
132
+ _count(parsed) {
133
+ if (parsed.params.pattern !== undefined) {
134
+ throw new Error(`${STORE_NAME}: pattern filter not supported`);
135
+ }
136
+ const result = this.executor.query(`SELECT COUNT(*) AS n FROM ${this.tableName} WHERE uri LIKE ? || '%' AND uri NOT LIKE ? || '%/%'`, [parsed.uri, parsed.uri]);
137
+ const row = result.rows[0];
138
+ return row?.n ?? 0;
139
+ }
140
+ _deleteBytes(uris) {
141
+ if (uris.length === 0)
142
+ return Promise.resolve([]);
143
+ // Atomic batch: every uri either deletes together or not at all.
144
+ try {
145
+ this.executor.transaction((tx) => {
146
+ for (const uri of uris) {
147
+ tx.query(`DELETE FROM ${this.tableName} WHERE uri = ?`, [uri]);
148
+ }
149
+ });
150
+ return Promise.resolve(uris.map(() => ({ success: true })));
151
+ }
152
+ catch (err) {
153
+ const failure = storageFailure(err, "Delete failed");
154
+ return Promise.resolve(uris.map(() => ({ success: false, ...failure })));
155
+ }
156
+ }
157
+ // ── Status ───────────────────────────────────────────────────────
158
+ status() {
159
+ try {
160
+ this.executor.query("SELECT 1");
161
+ return Promise.resolve({
162
+ status: "healthy",
163
+ message: "SQLite store is operational",
164
+ schema: this.schemaUris(),
165
+ fns: ["read", "ls", "count"],
166
+ details: { tableName: this.tableName },
167
+ });
168
+ }
169
+ catch (error) {
170
+ return Promise.resolve({
171
+ status: "unhealthy",
172
+ message: error instanceof Error ? error.message : String(error),
173
+ schema: this.schemaUris(),
174
+ fns: ["read", "ls", "count"],
175
+ });
176
+ }
177
+ }
178
+ schemaUris() {
179
+ return [...this.provisioned].map((name) => `entity:${name}`);
180
+ }
181
+ capabilities() {
182
+ return { atomicBatch: true };
183
+ }
184
+ }