@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,24 @@
1
+ /**
2
+ * Payload normalizers for backends.
3
+ *
4
+ * `StorePayload = Uint8Array | ReadableStream<Uint8Array>`. Backends
5
+ * with no native streaming path call `toBytes` on write to collect
6
+ * incoming streams. Backends that natively stream call `toStream` on
7
+ * read to wrap a buffered value back into a `ReadableStream` when
8
+ * that's the cheap shape for the caller.
9
+ *
10
+ * Both helpers go through `Response` so we get the platform's
11
+ * battle-tested coercion (handles `Uint8Array`, `ReadableStream`,
12
+ * locked stream errors, etc.) without re-implementing.
13
+ */
14
+ import type { StorePayload } from "./types.js";
15
+ /** Collect a `StorePayload` into a single `Uint8Array`. */
16
+ export declare function toBytes(payload: StorePayload): Promise<Uint8Array>;
17
+ /**
18
+ * Wrap a `StorePayload` as a `ReadableStream<Uint8Array>`. A
19
+ * `Uint8Array` becomes a single-chunk stream; an existing stream
20
+ * passes through. Use this on read paths from backends that prefer to
21
+ * yield a stream to callers.
22
+ */
23
+ export declare function toStream(payload: StorePayload): ReadableStream<Uint8Array>;
24
+ //# sourceMappingURL=payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../src/payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,2DAA2D;AAC3D,wBAAsB,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAGxE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,cAAc,CAAC,UAAU,CAAC,CAG1E"}
package/esm/payload.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Payload normalizers for backends.
3
+ *
4
+ * `StorePayload = Uint8Array | ReadableStream<Uint8Array>`. Backends
5
+ * with no native streaming path call `toBytes` on write to collect
6
+ * incoming streams. Backends that natively stream call `toStream` on
7
+ * read to wrap a buffered value back into a `ReadableStream` when
8
+ * that's the cheap shape for the caller.
9
+ *
10
+ * Both helpers go through `Response` so we get the platform's
11
+ * battle-tested coercion (handles `Uint8Array`, `ReadableStream`,
12
+ * locked stream errors, etc.) without re-implementing.
13
+ */
14
+ /** Collect a `StorePayload` into a single `Uint8Array`. */
15
+ export async function toBytes(payload) {
16
+ if (payload instanceof Uint8Array)
17
+ return payload;
18
+ return new Uint8Array(await new Response(payload).arrayBuffer());
19
+ }
20
+ /**
21
+ * Wrap a `StorePayload` as a `ReadableStream<Uint8Array>`. A
22
+ * `Uint8Array` becomes a single-chunk stream; an existing stream
23
+ * passes through. Use this on read paths from backends that prefer to
24
+ * yield a stream to callers.
25
+ */
26
+ export function toStream(payload) {
27
+ if (payload instanceof ReadableStream)
28
+ return payload;
29
+ return new Response(payload).body;
30
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * TYPE_TAGS → Postgres column type mapping for native entity tables.
3
+ *
4
+ * Each canonical tag in `TYPE_TAGS` resolves to one SQL type. A field
5
+ * carrying multiple tags (e.g. `["string", "email"]`) picks the first
6
+ * recognized canonical tag in declaration order — extra refinement
7
+ * tags pass through without affecting storage.
8
+ */
9
+ import { type EntityField } from "../entity.js";
10
+ export interface ColumnPlan {
11
+ /** Field/column name. */
12
+ name: string;
13
+ /** Postgres SQL type. */
14
+ sqlType: string;
15
+ /** The recognized tag that decided `sqlType` (for diagnostics). */
16
+ tag: string;
17
+ }
18
+ export interface ColumnPlanResult {
19
+ columns: ColumnPlan[];
20
+ unsupported: {
21
+ name: string;
22
+ reason: string;
23
+ }[];
24
+ }
25
+ /**
26
+ * Resolve a schema's fields into Postgres column plans.
27
+ *
28
+ * Skips fields whose tags are all outside `TYPE_TAGS`; reports them
29
+ * via `unsupported`. Rejects invalid field names up front so we never
30
+ * emit unsafe SQL identifiers.
31
+ */
32
+ export declare function planColumns(fields: EntityField[]): ColumnPlanResult;
33
+ //# sourceMappingURL=columns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columns.d.ts","sourceRoot":"","sources":["../../src/postgres/columns.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,cAAc,CAAC;AAc3D,MAAM,WAAW,UAAU;IACzB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,gBAAgB,CA0BnE"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * TYPE_TAGS → Postgres column type mapping for native entity tables.
3
+ *
4
+ * Each canonical tag in `TYPE_TAGS` resolves to one SQL type. A field
5
+ * carrying multiple tags (e.g. `["string", "email"]`) picks the first
6
+ * recognized canonical tag in declaration order — extra refinement
7
+ * tags pass through without affecting storage.
8
+ */
9
+ import { TYPE_TAGS } from "../entity.js";
10
+ const SQL_TYPE = {
11
+ [TYPE_TAGS.STRING]: "TEXT",
12
+ [TYPE_TAGS.NUMBER]: "DOUBLE PRECISION",
13
+ [TYPE_TAGS.BIGINT]: "BIGINT",
14
+ [TYPE_TAGS.BOOLEAN]: "BOOLEAN",
15
+ [TYPE_TAGS.BYTES]: "BYTEA",
16
+ [TYPE_TAGS.TIMESTAMP]: "TIMESTAMPTZ",
17
+ [TYPE_TAGS.JSON]: "JSONB",
18
+ };
19
+ const FIELD_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
20
+ /**
21
+ * Resolve a schema's fields into Postgres column plans.
22
+ *
23
+ * Skips fields whose tags are all outside `TYPE_TAGS`; reports them
24
+ * via `unsupported`. Rejects invalid field names up front so we never
25
+ * emit unsafe SQL identifiers.
26
+ */
27
+ export function planColumns(fields) {
28
+ const columns = [];
29
+ const unsupported = [];
30
+ for (const field of fields) {
31
+ if (!FIELD_NAME.test(field.name)) {
32
+ unsupported.push({
33
+ name: field.name,
34
+ reason: `field name must match ${FIELD_NAME.source}; got '${field.name}'`,
35
+ });
36
+ continue;
37
+ }
38
+ const tag = field.type.find((t) => SQL_TYPE[t] !== undefined);
39
+ if (!tag) {
40
+ unsupported.push({
41
+ name: field.name,
42
+ reason: field.type.length === 0
43
+ ? "field declares no type tags"
44
+ : `no recognised tag in [${field.type.join(", ")}]`,
45
+ });
46
+ continue;
47
+ }
48
+ columns.push({ name: field.name, sqlType: SQL_TYPE[tag], tag });
49
+ }
50
+ return { columns, unsupported };
51
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PostgreSQL backend for b3nd.
3
+ *
4
+ * `EntityStore` implementation backed by PostgreSQL. Requires an
5
+ * injected `SqlExecutor` so the SDK does not depend on a specific
6
+ * Postgres driver. Tables are provisioned lazily via `ensureEntity` —
7
+ * no separate schema-bootstrap step.
8
+ */
9
+ export interface SqlExecutorResult {
10
+ rows: unknown[];
11
+ rowCount?: number;
12
+ }
13
+ export interface SqlExecutor {
14
+ query: (sql: string, args?: unknown[]) => Promise<SqlExecutorResult>;
15
+ transaction: <T>(fn: (tx: SqlExecutor) => Promise<T>) => Promise<T>;
16
+ cleanup?: () => Promise<void>;
17
+ }
18
+ export { PostgresStore } from "./store.js";
19
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/postgres/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACrE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * PostgreSQL backend for b3nd.
3
+ *
4
+ * `EntityStore` implementation backed by PostgreSQL. Requires an
5
+ * injected `SqlExecutor` so the SDK does not depend on a specific
6
+ * Postgres driver. Tables are provisioned lazily via `ensureEntity` —
7
+ * no separate schema-bootstrap step.
8
+ */
9
+ export { PostgresStore } from "./store.js";
@@ -0,0 +1,75 @@
1
+ /**
2
+ * PostgresStore — PostgreSQL implementation of `EntityStore`.
3
+ *
4
+ * One layout for every schema: `{prefix}_{entity}_data` with `uri TEXT
5
+ * PRIMARY KEY` and one column per supported field, typed by the
6
+ * canonical `TYPE_TAGS` it carries (see `./columns.ts`). `BYTES_ENTITY`
7
+ * is just an entity with one `payload BYTEA` column — no legacy
8
+ * special case.
9
+ *
10
+ * ## Lifecycle
11
+ *
12
+ * `entitySupport(schema)` is pure — it runs `planColumns` and returns
13
+ * a `PostgresEntityMeta` with the target table name, column plans,
14
+ * bytes-column set, and the support report. No IO.
15
+ *
16
+ * `provisionEntity(meta)` runs `CREATE TABLE IF NOT EXISTS` and then
17
+ * verifies the live shape matches the meta (catches same-name-
18
+ * different-shape collisions). `entityStatus(meta)` performs the same
19
+ * check in isolation via `information_schema`.
20
+ *
21
+ * Writes/reads/deletes consume the meta directly: no per-call cache
22
+ * gate. If the table is not provisioned, Postgres returns
23
+ * `undefined_table` (`42P01`); the standard `storageFailure` wrap
24
+ * surfaces it per entry.
25
+ *
26
+ * `fn=ls` / `fn=count` push down to SQL — the shallow-direct-leaves
27
+ * predicate (`uri LIKE prefix% AND uri NOT LIKE prefix%/%`) is
28
+ * enforced in the WHERE clause against the entity's table.
29
+ *
30
+ * Writes are wrapped in a transaction (`capabilities.atomicBatch =
31
+ * true`). Stream-shaped values on `BYTEA` columns are collected to
32
+ * `Uint8Array` *before* the transaction opens, so a stream failure
33
+ * can't leave a half-applied commit.
34
+ */
35
+ import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
36
+ import type { EntityStore } from "../entity-store.js";
37
+ import type { EntityMeta, EntityRecord, EntitySchema } from "../entity.js";
38
+ import type { StoreCapabilities, StoreWriteResult } from "../types.js";
39
+ import { type ColumnPlan } from "./columns.js";
40
+ import type { SqlExecutor } from "./mod.js";
41
+ /**
42
+ * PostgresStore-specific entity handle.
43
+ *
44
+ * `tableName` is the prefixed-and-suffixed target table; `columns`
45
+ * are the planned user columns (without the always-present `uri`,
46
+ * `created_at`, `updated_at`); `declared` is the set of user column
47
+ * names for the per-write extras check; `bytesColumns` drives
48
+ * pre-transaction stream normalisation.
49
+ */
50
+ export interface PostgresEntityMeta extends EntityMeta {
51
+ readonly tableName: string;
52
+ readonly columns: readonly ColumnPlan[];
53
+ readonly declared: ReadonlySet<string>;
54
+ readonly bytesColumns: ReadonlySet<string>;
55
+ }
56
+ export declare class PostgresStore implements EntityStore<PostgresEntityMeta> {
57
+ private readonly tablePrefix;
58
+ private readonly executor;
59
+ constructor(tablePrefix: string, executor: SqlExecutor);
60
+ entitySupport(schema: EntitySchema): PostgresEntityMeta;
61
+ entityStatus(meta: PostgresEntityMeta): Promise<"live" | "unprovisioned">;
62
+ provisionEntity(meta: PostgresEntityMeta): Promise<void>;
63
+ write(meta: PostgresEntityMeta, entries: {
64
+ uri: string;
65
+ record: EntityRecord;
66
+ }[]): Promise<StoreWriteResult[]>;
67
+ read<T = EntityRecord | undefined>(meta: PostgresEntityMeta, urls: string[]): Promise<Output<T>[]>;
68
+ delete(meta: PostgresEntityMeta, uris: string[]): Promise<DeleteResult[]>;
69
+ status(): Promise<StatusResult>;
70
+ capabilities(): StoreCapabilities;
71
+ private _readOne;
72
+ private _ls;
73
+ private _count;
74
+ }
75
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/postgres/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAM3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC5C;AAoCD,qBAAa,aAAc,YAAW,WAAW,CAAC,kBAAkB,CAAC;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;gBAE3B,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW;IAetD,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,kBAAkB;IAkBjD,YAAY,CAChB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IA4B9B,eAAe,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBxD,KAAK,CACT,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA4E9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAQjB,MAAM,CACV,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,YAAY,EAAE,CAAC;IAkBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAoBrC,YAAY,IAAI,iBAAiB;YAMnB,QAAQ;YAqBR,GAAG;YAgCH,MAAM;CAcrB"}
@@ -0,0 +1,364 @@
1
+ /**
2
+ * PostgresStore — PostgreSQL implementation of `EntityStore`.
3
+ *
4
+ * One layout for every schema: `{prefix}_{entity}_data` with `uri TEXT
5
+ * PRIMARY KEY` and one column per supported field, typed by the
6
+ * canonical `TYPE_TAGS` it carries (see `./columns.ts`). `BYTES_ENTITY`
7
+ * is just an entity with one `payload BYTEA` column — no legacy
8
+ * special case.
9
+ *
10
+ * ## Lifecycle
11
+ *
12
+ * `entitySupport(schema)` is pure — it runs `planColumns` and returns
13
+ * a `PostgresEntityMeta` with the target table name, column plans,
14
+ * bytes-column set, and the support report. No IO.
15
+ *
16
+ * `provisionEntity(meta)` runs `CREATE TABLE IF NOT EXISTS` and then
17
+ * verifies the live shape matches the meta (catches same-name-
18
+ * different-shape collisions). `entityStatus(meta)` performs the same
19
+ * check in isolation via `information_schema`.
20
+ *
21
+ * Writes/reads/deletes consume the meta directly: no per-call cache
22
+ * gate. If the table is not provisioned, Postgres returns
23
+ * `undefined_table` (`42P01`); the standard `storageFailure` wrap
24
+ * surfaces it per entry.
25
+ *
26
+ * `fn=ls` / `fn=count` push down to SQL — the shallow-direct-leaves
27
+ * predicate (`uri LIKE prefix% AND uri NOT LIKE prefix%/%`) is
28
+ * enforced in the WHERE clause against the entity's table.
29
+ *
30
+ * Writes are wrapped in a transaction (`capabilities.atomicBatch =
31
+ * true`). Stream-shaped values on `BYTEA` columns are collected to
32
+ * `Uint8Array` *before* the transaction opens, so a stream failure
33
+ * can't leave a half-applied commit.
34
+ */
35
+ import { dispatchRead } from "../dispatch.js";
36
+ import { storageFailure } from "../errors.js";
37
+ import { toBytes } from "../payload.js";
38
+ import { validateReadParams } from "../read.js";
39
+ import { planColumns } from "./columns.js";
40
+ const STORE_NAME = "PostgresStore";
41
+ const TABLE_PREFIX = /^[a-zA-Z][a-zA-Z0-9_]*$/;
42
+ /** Map a `ColumnPlan.sqlType` to the `information_schema.columns.data_type` value. */
43
+ const INFO_SCHEMA_TYPE = {
44
+ TEXT: "text",
45
+ "DOUBLE PRECISION": "double precision",
46
+ BIGINT: "bigint",
47
+ BOOLEAN: "boolean",
48
+ BYTEA: "bytea",
49
+ TIMESTAMPTZ: "timestamp with time zone",
50
+ JSONB: "jsonb",
51
+ };
52
+ /** Coerce a pg `bytea` row value into a `Uint8Array`. */
53
+ function rowToBytes(value) {
54
+ if (value instanceof Uint8Array)
55
+ return value;
56
+ if (typeof value === "string" && value.startsWith("\\x")) {
57
+ const hex = value.slice(2);
58
+ const out = new Uint8Array(hex.length / 2);
59
+ for (let i = 0; i < out.length; i++) {
60
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
61
+ }
62
+ return out;
63
+ }
64
+ throw new Error(`${STORE_NAME}: unexpected payload type ${typeof value}`);
65
+ }
66
+ function entityTableName(tablePrefix, entityName) {
67
+ if (!TABLE_PREFIX.test(entityName)) {
68
+ throw new Error(`${STORE_NAME}: entity name '${entityName}' must match ${TABLE_PREFIX.source}`);
69
+ }
70
+ return `${tablePrefix}_${entityName}_data`;
71
+ }
72
+ export class PostgresStore {
73
+ tablePrefix;
74
+ executor;
75
+ constructor(tablePrefix, executor) {
76
+ if (!tablePrefix)
77
+ throw new Error("tablePrefix is required");
78
+ if (!TABLE_PREFIX.test(tablePrefix)) {
79
+ throw new Error(`tablePrefix must match ${TABLE_PREFIX.source}; got '${tablePrefix}'`);
80
+ }
81
+ if (!executor)
82
+ throw new Error("executor is required");
83
+ this.tablePrefix = tablePrefix;
84
+ this.executor = executor;
85
+ }
86
+ // ── Lifecycle ────────────────────────────────────────────────────
87
+ entitySupport(schema) {
88
+ const { columns, unsupported } = planColumns(schema.fields);
89
+ const support = {
90
+ entity: schema.name,
91
+ supported: columns.map((c) => c.name),
92
+ unsupported,
93
+ };
94
+ return {
95
+ support,
96
+ tableName: entityTableName(this.tablePrefix, schema.name),
97
+ columns,
98
+ declared: new Set(columns.map((c) => c.name)),
99
+ bytesColumns: new Set(columns.filter((c) => c.sqlType === "BYTEA").map((c) => c.name)),
100
+ };
101
+ }
102
+ async entityStatus(meta) {
103
+ const res = await this.executor.query(`SELECT column_name, data_type FROM information_schema.columns
104
+ WHERE table_name = $1`, [meta.tableName]);
105
+ const rows = (res.rows ?? []);
106
+ if (rows.length === 0)
107
+ return "unprovisioned";
108
+ const actual = new Map(rows.map((r) => [r.column_name, r.data_type]));
109
+ for (const c of meta.columns) {
110
+ const got = actual.get(c.name);
111
+ if (got === undefined)
112
+ return "unprovisioned";
113
+ const expected = INFO_SCHEMA_TYPE[c.sqlType];
114
+ if (expected !== undefined && got !== expected)
115
+ return "unprovisioned";
116
+ }
117
+ // Reject collisions in the other direction: the live table carrying
118
+ // user columns the meta does not declare is a different shape.
119
+ for (const name of actual.keys()) {
120
+ if (name === "uri" || name === "created_at" || name === "updated_at") {
121
+ continue;
122
+ }
123
+ if (!meta.declared.has(name))
124
+ return "unprovisioned";
125
+ }
126
+ return "live";
127
+ }
128
+ async provisionEntity(meta) {
129
+ const colDdl = meta.columns
130
+ .map((c) => `"${c.name}" ${c.sqlType}`)
131
+ .join(",\n ");
132
+ const sql = `CREATE TABLE IF NOT EXISTS ${meta.tableName} (
133
+ uri TEXT PRIMARY KEY${meta.columns.length > 0 ? ",\n " + colDdl : ""},
134
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
135
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
136
+ );
137
+ CREATE INDEX IF NOT EXISTS idx_${meta.tableName}_uri ON ${meta.tableName} (uri);`;
138
+ await this.executor.query(sql);
139
+ // If the table already existed with a different shape, the
140
+ // CREATE IF NOT EXISTS was a no-op — surface the collision.
141
+ const status = await this.entityStatus(meta);
142
+ if (status !== "live") {
143
+ throw new Error(`${STORE_NAME}: entity '${meta.support.entity}' is already ` +
144
+ `provisioned with a different shape at table '${meta.tableName}'`);
145
+ }
146
+ }
147
+ // ── Write ────────────────────────────────────────────────────────
148
+ async write(meta, entries) {
149
+ if (entries.length === 0)
150
+ return [];
151
+ const out = new Array(entries.length);
152
+ const accepted = [];
153
+ // Validate + collect stream-shaped bytes fields BEFORE the
154
+ // transaction. Per-entry validation errors leak as failures in
155
+ // their slots; everything else gets committed atomically below.
156
+ for (let i = 0; i < entries.length; i++) {
157
+ const { uri, record } = entries[i];
158
+ const extras = Object.keys(record).filter((k) => !meta.declared.has(k));
159
+ if (extras.length > 0) {
160
+ out[i] = {
161
+ success: false,
162
+ ...storageFailure(new Error(`${STORE_NAME}: record contains keys not declared in schema '${meta.support.entity}': ${extras.join(", ")}`), "Schema mismatch", uri),
163
+ };
164
+ continue;
165
+ }
166
+ try {
167
+ const normalised = await normaliseBytesFields(record, meta.bytesColumns);
168
+ accepted.push({ idx: i, uri, record: normalised });
169
+ }
170
+ catch (err) {
171
+ out[i] = {
172
+ success: false,
173
+ ...storageFailure(err, "Invalid record", uri),
174
+ };
175
+ }
176
+ }
177
+ if (accepted.length === 0)
178
+ return out;
179
+ const cols = meta.columns;
180
+ const colList = cols.map((c) => `"${c.name}"`).join(", ");
181
+ const placeholders = cols.map((_, i) => `$${i + 2}`).join(", ");
182
+ const updates = cols
183
+ .map((c) => `"${c.name}" = EXCLUDED."${c.name}"`)
184
+ .concat(["updated_at = CURRENT_TIMESTAMP"])
185
+ .join(", ");
186
+ try {
187
+ await this.executor.transaction(async (tx) => {
188
+ for (const { uri, record } of accepted) {
189
+ const args = [uri];
190
+ for (const c of cols)
191
+ args.push(adaptForWrite(c, record[c.name]));
192
+ await tx.query(`INSERT INTO ${meta.tableName} (uri${colList ? ", " + colList : ""}) VALUES ($1${placeholders ? ", " + placeholders : ""})
193
+ ON CONFLICT (uri) DO UPDATE SET ${updates}`, args);
194
+ }
195
+ });
196
+ for (const { idx } of accepted)
197
+ out[idx] = { success: true };
198
+ return out;
199
+ }
200
+ catch (err) {
201
+ // Atomic batch failure — every accepted entry shares the same failure.
202
+ // An undefined_table from a not-yet-provisioned meta lands here too.
203
+ const failure = storageFailure(err, "Write failed");
204
+ for (const { idx } of accepted)
205
+ out[idx] = { success: false, ...failure };
206
+ return out;
207
+ }
208
+ }
209
+ // ── Read ─────────────────────────────────────────────────────────
210
+ read(meta, urls) {
211
+ return dispatchRead(urls, STORE_NAME, {
212
+ read: (p) => this._readOne(meta, p.uri),
213
+ ls: (p) => this._ls(meta, p),
214
+ count: (p) => this._count(meta, p),
215
+ });
216
+ }
217
+ async delete(meta, uris) {
218
+ if (uris.length === 0)
219
+ return [];
220
+ try {
221
+ await this.executor.transaction(async (tx) => {
222
+ for (const uri of uris) {
223
+ await tx.query(`DELETE FROM ${meta.tableName} WHERE uri = $1`, [uri]);
224
+ }
225
+ });
226
+ return uris.map(() => ({ success: true }));
227
+ }
228
+ catch (err) {
229
+ const failure = storageFailure(err, "Delete failed");
230
+ return uris.map(() => ({ success: false, ...failure }));
231
+ }
232
+ }
233
+ async status() {
234
+ try {
235
+ await this.executor.query("SELECT 1");
236
+ return {
237
+ status: "healthy",
238
+ message: "PostgreSQL store is operational",
239
+ fns: ["read", "ls", "count"],
240
+ details: { tablePrefix: this.tablePrefix },
241
+ };
242
+ }
243
+ catch (error) {
244
+ return {
245
+ status: "unhealthy",
246
+ message: `PostgreSQL health check failed: ${error instanceof Error ? error.message : String(error)}`,
247
+ fns: ["read", "ls", "count"],
248
+ };
249
+ }
250
+ }
251
+ capabilities() {
252
+ return { atomicBatch: true };
253
+ }
254
+ // ── Internals ────────────────────────────────────────────────────
255
+ async _readOne(meta, uri) {
256
+ if (meta.columns.length === 0) {
257
+ // Empty schema: presence-only — return an empty record on hit.
258
+ const res = await this.executor.query(`SELECT 1 FROM ${meta.tableName} WHERE uri = $1`, [uri]);
259
+ return res.rows && res.rows.length > 0 ? {} : undefined;
260
+ }
261
+ const cols = meta.columns.map((c) => `"${c.name}"`).join(", ");
262
+ const res = await this.executor.query(`SELECT ${cols} FROM ${meta.tableName} WHERE uri = $1`, [uri]);
263
+ if (!res.rows || res.rows.length === 0)
264
+ return undefined;
265
+ return adaptRowForRead(meta, res.rows[0]);
266
+ }
267
+ async _ls(meta, parsed) {
268
+ validateReadParams(parsed.params, STORE_NAME);
269
+ const { params } = parsed;
270
+ const format = params.format ?? "full";
271
+ const cols = meta.columns.length > 0
272
+ ? "uri, " + meta.columns.map((c) => `"${c.name}"`).join(", ")
273
+ : "uri";
274
+ const selectClause = format === "uris" ? "uri" : cols;
275
+ const order = params.sortBy === "uri"
276
+ ? ` ORDER BY uri ${params.sortOrder === "desc" ? "DESC" : "ASC"}`
277
+ : "";
278
+ let sql = `SELECT ${selectClause} FROM ${meta.tableName} WHERE uri LIKE $1 || '%' AND uri NOT LIKE $1 || '%/%'${order}`;
279
+ const args = [parsed.uri];
280
+ if (params.limit !== undefined) {
281
+ const page = params.page ?? 1;
282
+ args.push(params.limit, (page - 1) * params.limit);
283
+ sql += ` LIMIT $${args.length - 1} OFFSET $${args.length}`;
284
+ }
285
+ const res = await this.executor.query(sql, args);
286
+ const rows = (res.rows ?? []);
287
+ if (format === "uris")
288
+ return rows.map((r) => r.uri);
289
+ return rows.map((r) => [
290
+ r.uri,
291
+ adaptRowForRead(meta, r),
292
+ ]);
293
+ }
294
+ async _count(meta, parsed) {
295
+ if (parsed.params.pattern !== undefined) {
296
+ throw new Error(`${STORE_NAME}: pattern filter not supported`);
297
+ }
298
+ const res = await this.executor.query(`SELECT COUNT(*)::int AS n FROM ${meta.tableName} WHERE uri LIKE $1 || '%' AND uri NOT LIKE $1 || '%/%'`, [parsed.uri]);
299
+ const row = res.rows?.[0];
300
+ return row?.n ?? 0;
301
+ }
302
+ }
303
+ /**
304
+ * Collect any `ReadableStream` values on `BYTEA` columns into
305
+ * `Uint8Array` and return a shallow-copied record. Runs before the
306
+ * write transaction so stream failures can't leave a half-applied
307
+ * commit. Non-stream values pass through verbatim.
308
+ */
309
+ async function normaliseBytesFields(record, bytesColumns) {
310
+ if (bytesColumns.size === 0)
311
+ return record;
312
+ const out = { ...record };
313
+ for (const name of bytesColumns) {
314
+ const v = out[name];
315
+ if (v === undefined || v === null)
316
+ continue;
317
+ if (v instanceof Uint8Array)
318
+ continue;
319
+ if (v instanceof ReadableStream) {
320
+ out[name] = await toBytes(v);
321
+ continue;
322
+ }
323
+ throw new Error(`${STORE_NAME}: field '${name}' must be Uint8Array or ReadableStream, got ${typeof v}`);
324
+ }
325
+ return out;
326
+ }
327
+ /** Coerce a record field value into something the driver will accept. */
328
+ function adaptForWrite(col, value) {
329
+ if (value === undefined || value === null)
330
+ return null;
331
+ if (col.sqlType === "JSONB") {
332
+ return typeof value === "string" ? value : JSON.stringify(value);
333
+ }
334
+ if (col.sqlType === "TIMESTAMPTZ") {
335
+ if (value instanceof Date)
336
+ return value.toISOString();
337
+ if (typeof value === "number")
338
+ return new Date(value).toISOString();
339
+ return value;
340
+ }
341
+ return value;
342
+ }
343
+ /** Reconstruct an EntityRecord from a Postgres row. */
344
+ function adaptRowForRead(meta, row) {
345
+ const rec = {};
346
+ for (const col of meta.columns) {
347
+ const v = row[col.name];
348
+ if (v === null || v === undefined) {
349
+ rec[col.name] = undefined;
350
+ continue;
351
+ }
352
+ if (col.sqlType === "BYTEA")
353
+ rec[col.name] = rowToBytes(v);
354
+ else if (col.sqlType === "JSONB") {
355
+ rec[col.name] = typeof v === "string" ? JSON.parse(v) : v;
356
+ }
357
+ else if (col.sqlType === "TIMESTAMPTZ") {
358
+ rec[col.name] = v instanceof Date ? v : new Date(v);
359
+ }
360
+ else
361
+ rec[col.name] = v;
362
+ }
363
+ return rec;
364
+ }
package/esm/read.d.ts ADDED
@@ -0,0 +1,40 @@
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
+ import type { Output } from "./deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
14
+ import type { ReadParams } from "./url.js";
15
+ /**
16
+ * Validate standard ReadParams and throw on anything we cannot honor.
17
+ *
18
+ * Push-down stores (postgres, mongo, ES, S3) call this first, then
19
+ * translate the surviving params into their backend query language.
20
+ * Stores that post-process in memory call `applyReadParams` instead,
21
+ * which validates and applies in one go.
22
+ *
23
+ * Project-wide baseline: `pattern` and `cursor` are unsupported
24
+ * everywhere; `sortBy` only accepts `"uri"`; `format` only accepts
25
+ * `"full"` (default) or `"uris"`. Per-store relaxations should be
26
+ * added explicitly as features land.
27
+ */
28
+ export declare function validateReadParams(params: ReadParams, storeName: string): void;
29
+ /**
30
+ * Apply standard ReadParams to a list of rows.
31
+ *
32
+ * @param rows raw `[uri, payload]` entries collected from the backend
33
+ * @param params parsed read params (from `parseUrl(...).params`)
34
+ * @param storeName label used in thrown error messages
35
+ *
36
+ * Returns `Output[]` when `format` is `"full"` (default) or `string[]`
37
+ * when `format` is `"uris"`.
38
+ */
39
+ export declare function applyReadParams<T>(rows: Output<T>[], params: ReadParams, storeName: string): Output<T>[] | string[];
40
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kEAAkE,CAAC;AAC/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,GAChB,IAAI,CAcN;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EACjB,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAkBxB"}