@bandeira-tech/b3nd-save 0.8.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/esm/clients/save-client.d.ts +63 -29
  2. package/esm/clients/save-client.d.ts.map +1 -1
  3. package/esm/clients/save-client.js +148 -68
  4. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/mod.d.ts +1 -1
  5. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/mod.d.ts.map +1 -0
  6. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.d.ts.map +1 -1
  7. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.d.ts.map +1 -1
  8. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encrypt/mod.d.ts +1 -0
  9. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/mod.d.ts.map +1 -0
  10. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encrypt/mod.js +1 -0
  11. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.d.ts +6 -0
  12. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.d.ts.map +1 -0
  13. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/encrypt/utils.js +12 -0
  14. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.d.ts.map +1 -1
  15. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.d.ts.map +1 -1
  16. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.d.ts.map +1 -1
  17. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.d.ts.map +1 -1
  18. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.d.ts.map +1 -1
  19. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.d.ts.map +1 -1
  20. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/path-vector.d.ts +3 -1
  21. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/network/policies/path-vector.d.ts.map +1 -0
  22. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/path-vector.js +3 -1
  23. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.d.ts.map +1 -1
  24. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.d.ts.map +1 -1
  25. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.d.ts.map +1 -1
  26. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/connection.d.ts +13 -5
  27. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/connection.d.ts.map +1 -0
  28. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/connection.js +1 -1
  29. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.d.ts.map +1 -1
  30. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.d.ts.map +1 -1
  31. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.d.ts.map +1 -1
  32. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.d.ts.map +1 -1
  33. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.d.ts.map +1 -1
  34. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/rig.d.ts +3 -0
  35. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/rig.d.ts.map +1 -0
  36. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/rig.js +46 -4
  37. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/types.d.ts +4 -4
  38. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/rig/types.d.ts.map +1 -0
  39. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/types/types.d.ts +119 -80
  40. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.d.ts.map +1 -0
  41. package/esm/dispatch.d.ts +116 -3
  42. package/esm/dispatch.d.ts.map +1 -1
  43. package/esm/dispatch.js +381 -5
  44. package/esm/elasticsearch/store.d.ts +124 -25
  45. package/esm/elasticsearch/store.d.ts.map +1 -1
  46. package/esm/elasticsearch/store.js +451 -92
  47. package/esm/entity-store.d.ts +1 -1
  48. package/esm/errors.d.ts +1 -1
  49. package/esm/errors.js +1 -1
  50. package/esm/fs/mod.d.ts +26 -0
  51. package/esm/fs/mod.d.ts.map +1 -1
  52. package/esm/fs/store.d.ts +100 -23
  53. package/esm/fs/store.d.ts.map +1 -1
  54. package/esm/fs/store.js +250 -66
  55. package/esm/glob.d.ts +114 -0
  56. package/esm/glob.d.ts.map +1 -0
  57. package/esm/glob.js +352 -0
  58. package/esm/identifiers.d.ts +18 -0
  59. package/esm/identifiers.d.ts.map +1 -0
  60. package/esm/identifiers.js +19 -0
  61. package/esm/indexeddb/fields.d.ts +32 -0
  62. package/esm/indexeddb/fields.d.ts.map +1 -0
  63. package/esm/indexeddb/fields.js +51 -0
  64. package/esm/indexeddb/store.d.ts +72 -17
  65. package/esm/indexeddb/store.d.ts.map +1 -1
  66. package/esm/indexeddb/store.js +362 -106
  67. package/esm/ipfs/mod.d.ts +19 -0
  68. package/esm/ipfs/mod.d.ts.map +1 -1
  69. package/esm/ipfs/mod.js +19 -0
  70. package/esm/ipfs/store.d.ts +82 -21
  71. package/esm/ipfs/store.d.ts.map +1 -1
  72. package/esm/ipfs/store.js +269 -68
  73. package/esm/json-fields.d.ts +61 -0
  74. package/esm/json-fields.d.ts.map +1 -0
  75. package/esm/json-fields.js +121 -0
  76. package/esm/localstorage/store.d.ts +89 -21
  77. package/esm/localstorage/store.d.ts.map +1 -1
  78. package/esm/localstorage/store.js +247 -54
  79. package/esm/memory/store.d.ts +35 -4
  80. package/esm/memory/store.d.ts.map +1 -1
  81. package/esm/memory/store.js +69 -47
  82. package/esm/mod.d.ts +2 -0
  83. package/esm/mod.d.ts.map +1 -1
  84. package/esm/mod.js +1 -0
  85. package/esm/mongo/fields.d.ts +53 -0
  86. package/esm/mongo/fields.d.ts.map +1 -0
  87. package/esm/mongo/fields.js +71 -0
  88. package/esm/mongo/mod.d.ts +19 -4
  89. package/esm/mongo/mod.d.ts.map +1 -1
  90. package/esm/mongo/mod.js +4 -2
  91. package/esm/mongo/store.d.ts +114 -25
  92. package/esm/mongo/store.d.ts.map +1 -1
  93. package/esm/mongo/store.js +355 -102
  94. package/esm/postgres/store.d.ts +50 -1
  95. package/esm/postgres/store.d.ts.map +1 -1
  96. package/esm/postgres/store.js +133 -13
  97. package/esm/read.d.ts +54 -7
  98. package/esm/read.d.ts.map +1 -1
  99. package/esm/read.js +141 -16
  100. package/esm/s3/store.d.ts +125 -23
  101. package/esm/s3/store.d.ts.map +1 -1
  102. package/esm/s3/store.js +313 -57
  103. package/esm/sqlite/columns.d.ts +33 -0
  104. package/esm/sqlite/columns.d.ts.map +1 -0
  105. package/esm/sqlite/columns.js +51 -0
  106. package/esm/sqlite/store.d.ts +73 -25
  107. package/esm/sqlite/store.d.ts.map +1 -1
  108. package/esm/sqlite/store.js +411 -107
  109. package/esm/types.d.ts +1 -1
  110. package/esm/url.d.ts +122 -19
  111. package/esm/url.d.ts.map +1 -1
  112. package/esm/url.js +218 -24
  113. package/esm/walk-via-ls.d.ts +122 -0
  114. package/esm/walk-via-ls.d.ts.map +1 -0
  115. package/esm/walk-via-ls.js +178 -0
  116. package/package.json +1 -1
  117. package/esm/byte-entity-shim.d.ts +0 -47
  118. package/esm/byte-entity-shim.d.ts.map +0 -1
  119. package/esm/byte-entity-shim.js +0 -138
  120. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +0 -1
  121. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +0 -1
  122. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +0 -1
  123. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +0 -1
  124. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +0 -1
  125. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +0 -1
  126. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +0 -1
  127. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/mod.js +0 -0
  128. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.d.ts +0 -0
  129. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/client-console/client.js +0 -0
  130. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.d.ts +0 -0
  131. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/encoding/encoding.js +0 -0
  132. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.d.ts +0 -0
  133. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/functional-client/functional-client.js +0 -0
  134. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.d.ts +0 -0
  135. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/match-pattern/match-pattern.js +0 -0
  136. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.d.ts +0 -0
  137. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/decorators.js +0 -0
  138. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.d.ts +0 -0
  139. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/network.js +0 -0
  140. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.d.ts +0 -0
  141. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/peer.js +0 -0
  142. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.d.ts +0 -0
  143. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/flood.js +0 -0
  144. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.d.ts +0 -0
  145. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/policies/tell-and-read.js +0 -0
  146. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.d.ts +0 -0
  147. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/network/types.js +0 -0
  148. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.d.ts +0 -0
  149. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/observe-emitter/observe-emitter.js +0 -0
  150. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.d.ts +0 -0
  151. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/events.js +0 -0
  152. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.d.ts +0 -0
  153. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/hooks.js +0 -0
  154. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.d.ts +0 -0
  155. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/identity.js +0 -0
  156. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.d.ts +0 -0
  157. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/operation-handle.js +0 -0
  158. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.d.ts +0 -0
  159. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/reactions.js +0 -0
  160. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/rig/types.js +0 -0
  161. /package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/{0.22.0 → 0.25.0}/src/types/types.js +0 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared TYPE_TAGS planning + JSON record encoding used by backends
3
+ * that store custom-entity records as JSON-encoded bytes.
4
+ *
5
+ * The fs / s3 / ipfs / localstorage / elasticsearch backends all
6
+ * serialise records the same way:
7
+ *
8
+ * - bytes → base64 string
9
+ * - bigint → decimal string
10
+ * - timestamp → ISO-8601 string
11
+ * - everything else passes through unchanged (`number`, `boolean`,
12
+ * `string`, `json` objects).
13
+ *
14
+ * Centralising the encoder/decoder here keeps the round-trip
15
+ * guarantee — write on one JSON-encoding backend, read on another,
16
+ * get the same `EntityRecord` shape back. Mongo (BSON-native) and
17
+ * IndexedDB (structured-clone-native) preserve the typed values
18
+ * without JSON encoding, so they don't use this module.
19
+ *
20
+ * `computeSignature` is the canonical collision-detection signature
21
+ * over `{name, [field, tag] ...}` — two metas with identical names
22
+ * + identical per-field canonical tags hash to the same string;
23
+ * any shape difference (including a tag swap on an existing field
24
+ * name) yields a different one. Matches the granularity SQL backends
25
+ * get for free via column-type introspection.
26
+ */
27
+ import { type EntityField, type EntityRecord } from "./entity.js";
28
+ export interface FieldPlan {
29
+ name: string;
30
+ tag: string;
31
+ }
32
+ export interface FieldPlanResult {
33
+ fields: FieldPlan[];
34
+ unsupported: {
35
+ name: string;
36
+ reason: string;
37
+ }[];
38
+ }
39
+ /**
40
+ * Walk a schema's fields, picking the first recognised TYPE_TAGS
41
+ * entry per field as the canonical tag. Fields with no recognised
42
+ * tag (or an invalid field name) are reported under `unsupported`.
43
+ */
44
+ export declare function planFields(fields: EntityField[]): FieldPlanResult;
45
+ /**
46
+ * Encode a record into a JSON-serialisable shape using the per-field
47
+ * canonical tag. Bytes → base64; bigint → string; timestamp →
48
+ * ISO-8601; everything else passes through unchanged.
49
+ */
50
+ export declare function encodeRecord(fields: readonly FieldPlan[], record: EntityRecord): Record<string, unknown>;
51
+ /** Inverse of `encodeRecord` — restores `Uint8Array`/`BigInt`/`Date`. */
52
+ export declare function decodeRecord(fields: readonly FieldPlan[], source: Record<string, unknown>): EntityRecord;
53
+ /**
54
+ * Canonical signature over `{name, [field, tag] ...}` used by the
55
+ * provisioning bookkeeping to detect same-name different-shape
56
+ * collisions. Identical to Memory's signature scheme — a field whose
57
+ * canonical tag flips counts as a collision even though the field
58
+ * name is unchanged.
59
+ */
60
+ export declare function computeSignature(name: string, fields: readonly FieldPlan[]): string;
61
+ //# sourceMappingURL=json-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-fields.d.ts","sourceRoot":"","sources":["../src/json-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAa,MAAM,aAAa,CAAC;AAK7E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,CAyBjE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBzB;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,YAAY,CAcd;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,SAAS,EAAE,GAC3B,MAAM,CAKR"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Shared TYPE_TAGS planning + JSON record encoding used by backends
3
+ * that store custom-entity records as JSON-encoded bytes.
4
+ *
5
+ * The fs / s3 / ipfs / localstorage / elasticsearch backends all
6
+ * serialise records the same way:
7
+ *
8
+ * - bytes → base64 string
9
+ * - bigint → decimal string
10
+ * - timestamp → ISO-8601 string
11
+ * - everything else passes through unchanged (`number`, `boolean`,
12
+ * `string`, `json` objects).
13
+ *
14
+ * Centralising the encoder/decoder here keeps the round-trip
15
+ * guarantee — write on one JSON-encoding backend, read on another,
16
+ * get the same `EntityRecord` shape back. Mongo (BSON-native) and
17
+ * IndexedDB (structured-clone-native) preserve the typed values
18
+ * without JSON encoding, so they don't use this module.
19
+ *
20
+ * `computeSignature` is the canonical collision-detection signature
21
+ * over `{name, [field, tag] ...}` — two metas with identical names
22
+ * + identical per-field canonical tags hash to the same string;
23
+ * any shape difference (including a tag swap on an existing field
24
+ * name) yields a different one. Matches the granularity SQL backends
25
+ * get for free via column-type introspection.
26
+ */
27
+ import { decodeBase64, encodeBase64 } from "./deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/mod.js";
28
+ import { TYPE_TAGS } from "./entity.js";
29
+ import { IDENTIFIER_PATTERN, isValidIdentifier } from "./identifiers.js";
30
+ const KNOWN = new Set(Object.values(TYPE_TAGS));
31
+ /**
32
+ * Walk a schema's fields, picking the first recognised TYPE_TAGS
33
+ * entry per field as the canonical tag. Fields with no recognised
34
+ * tag (or an invalid field name) are reported under `unsupported`.
35
+ */
36
+ export function planFields(fields) {
37
+ const out = [];
38
+ const unsupported = [];
39
+ for (const f of fields) {
40
+ if (!isValidIdentifier(f.name)) {
41
+ unsupported.push({
42
+ name: f.name,
43
+ reason: `field name must match ${IDENTIFIER_PATTERN.source}; got '${f.name}'`,
44
+ });
45
+ continue;
46
+ }
47
+ const tag = f.type.find((t) => KNOWN.has(t));
48
+ if (!tag) {
49
+ unsupported.push({
50
+ name: f.name,
51
+ reason: f.type.length === 0
52
+ ? "field declares no type tags"
53
+ : `no recognised tag in [${f.type.join(", ")}]`,
54
+ });
55
+ continue;
56
+ }
57
+ out.push({ name: f.name, tag });
58
+ }
59
+ return { fields: out, unsupported };
60
+ }
61
+ /**
62
+ * Encode a record into a JSON-serialisable shape using the per-field
63
+ * canonical tag. Bytes → base64; bigint → string; timestamp →
64
+ * ISO-8601; everything else passes through unchanged.
65
+ */
66
+ export function encodeRecord(fields, record) {
67
+ const out = {};
68
+ for (const f of fields) {
69
+ const v = record[f.name];
70
+ if (v === undefined || v === null) {
71
+ out[f.name] = null;
72
+ continue;
73
+ }
74
+ if (f.tag === "bytes")
75
+ out[f.name] = encodeBase64(v);
76
+ else if (f.tag === "bigint") {
77
+ out[f.name] = typeof v === "bigint" ? v.toString() : String(v);
78
+ }
79
+ else if (f.tag === "timestamp") {
80
+ out[f.name] = v instanceof Date
81
+ ? v.toISOString()
82
+ : new Date(v).toISOString();
83
+ }
84
+ else
85
+ out[f.name] = v;
86
+ }
87
+ return out;
88
+ }
89
+ /** Inverse of `encodeRecord` — restores `Uint8Array`/`BigInt`/`Date`. */
90
+ export function decodeRecord(fields, source) {
91
+ const out = {};
92
+ for (const f of fields) {
93
+ const v = source[f.name];
94
+ if (v === null || v === undefined) {
95
+ out[f.name] = undefined;
96
+ continue;
97
+ }
98
+ if (f.tag === "bytes")
99
+ out[f.name] = decodeBase64(v);
100
+ else if (f.tag === "bigint")
101
+ out[f.name] = BigInt(v);
102
+ else if (f.tag === "timestamp")
103
+ out[f.name] = new Date(v);
104
+ else
105
+ out[f.name] = v;
106
+ }
107
+ return out;
108
+ }
109
+ /**
110
+ * Canonical signature over `{name, [field, tag] ...}` used by the
111
+ * provisioning bookkeeping to detect same-name different-shape
112
+ * collisions. Identical to Memory's signature scheme — a field whose
113
+ * canonical tag flips counts as a collision even though the field
114
+ * name is unchanged.
115
+ */
116
+ export function computeSignature(name, fields) {
117
+ const sorted = [...fields]
118
+ .map((f) => [f.name, f.tag])
119
+ .sort(([a], [b]) => a.localeCompare(b));
120
+ return JSON.stringify({ name, fields: sorted });
121
+ }
@@ -1,45 +1,113 @@
1
1
  /**
2
- * LocalStorageStore — browser localStorage implementation of Store.
2
+ * LocalStorageStore — browser localStorage implementation of `EntityStore`.
3
3
  *
4
- * Pure mechanical byte storage with no protocol awareness. Uses
5
- * `localStorage` as a flat key→string KV. Payload bytes are
6
- * base64-encoded since `localStorage` values are strings.
4
+ * One backend, many entities. Layouts:
5
+ *
6
+ * - `BYTES_ENTITY` → key `{keyPrefix}{uri}` with a base64-encoded
7
+ * payload string. Matches the original bytes-only layout, so
8
+ * existing deployments keep working without migration.
9
+ * - any other schema → key `{keyPrefix}entities/{entityName}/{uri}`
10
+ * with a JSON-encoded record string. Per-field canonical tags
11
+ * round-trip the JSON boundary (bytes → base64, bigint → string,
12
+ * timestamp → ISO-8601 — see `./fields.ts`).
13
+ *
14
+ * ## Lifecycle
15
+ *
16
+ * `entitySupport(schema)` is pure — it plans the field set and computes
17
+ * a signature for collision detection. `provisionEntity(meta)` writes a
18
+ * tiny bookkeeping key (`{keyPrefix}__meta__/entities/{name}`) holding
19
+ * the signature; subsequent `entityStatus(meta)` reads it back and
20
+ * compares. Same-name different-shape provisioning throws.
21
+ *
22
+ * Writes/reads/deletes consume the meta directly: there is no per-call
23
+ * cache lookup gate. If `meta`'s bookkeeping is missing on the medium,
24
+ * `write` and `delete` surface a `"not provisioned"` storage failure
25
+ * per entry; `read` returns misses.
26
+ *
27
+ * ## Read params
28
+ *
29
+ * `fn=ls` / `fn=count` are shallow direct-leaves only. localStorage has
30
+ * no indexable cursor, so `_directLeaves` walks all keys once under the
31
+ * entity's `keyRoot` and standard params (`limit`/`page`/`sortBy`
32
+ * `=uri`/`sortOrder`/`format`) are applied in memory via
33
+ * `applyReadParams` from `../read.ts`.
34
+ *
35
+ * `fn=find` (v2 §3.5) walks every key under the entity's `keyRoot`
36
+ * matching the URI prefix at any depth — `_walkDeep` mirrors
37
+ * `_directLeaves` minus the `rest.includes("/")` cutoff. With
38
+ * `pushDownFind: false`, dispatch applies the embedded glob, sort,
39
+ * cursor, limit, format, and appends the cursor-as-trailing-slot.
40
+ * localStorage has no engine to leverage for push-down — iteration IS
41
+ * the work — so the symmetric shape (ls = shallow walk, find = deep
42
+ * walk; dispatch post-processes both) keeps the code minimal.
43
+ *
44
+ * `status().fns` advertises `["read", "ls", "count", "find"]`.
7
45
  */
8
- import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
9
- import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
46
+ import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.js";
10
47
  import type { EntityStore } from "../entity-store.js";
11
- import type { EntityRecord, EntitySchema } from "../entity.js";
48
+ import { type EntityMeta, type EntityRecord, type EntitySchema } from "../entity.js";
12
49
  import type { StoreCapabilities, StoreWriteResult } from "../types.js";
13
- export declare class LocalStorageStore implements EntityStore<BytesOnlyEntityMeta> {
50
+ import { type FieldPlan } from "../json-fields.js";
51
+ /**
52
+ * LocalStorageStore-specific entity handle.
53
+ *
54
+ * `keyRoot` is the full localStorage prefix under which records live
55
+ * (e.g. `b3nd:entities/users/`). `isBytes` selects the legacy
56
+ * base64-bytes value format vs the JSON record format. `fields`,
57
+ * `declared`, and `bytesFields` drive write-time validation and
58
+ * encoding; `signature` is used for collision detection in
59
+ * `entityStatus` / `provisionEntity`.
60
+ */
61
+ export interface LocalStorageEntityMeta extends EntityMeta {
62
+ readonly keyRoot: string;
63
+ readonly isBytes: boolean;
64
+ readonly fields: readonly FieldPlan[];
65
+ readonly declared: ReadonlySet<string>;
66
+ readonly bytesFields: ReadonlySet<string>;
67
+ readonly signature: string;
68
+ }
69
+ export declare class LocalStorageStore implements EntityStore<LocalStorageEntityMeta> {
14
70
  private readonly keyPrefix;
15
71
  private readonly storage;
16
72
  constructor(config?: {
17
73
  keyPrefix?: string;
18
74
  storage?: Storage;
19
75
  });
20
- private getKey;
21
- entitySupport(schema: EntitySchema): BytesOnlyEntityMeta;
22
- entityStatus(meta: BytesOnlyEntityMeta): Promise<"live" | "unprovisioned">;
23
- provisionEntity(_meta: BytesOnlyEntityMeta): Promise<void>;
24
- write(meta: BytesOnlyEntityMeta, entries: {
76
+ entitySupport(schema: EntitySchema): LocalStorageEntityMeta;
77
+ entityStatus(meta: LocalStorageEntityMeta): Promise<"live" | "unprovisioned">;
78
+ provisionEntity(meta: LocalStorageEntityMeta): Promise<void>;
79
+ write(meta: LocalStorageEntityMeta, entries: {
25
80
  uri: string;
26
81
  record: EntityRecord;
27
82
  }[]): Promise<StoreWriteResult[]>;
28
- read<T = EntityRecord | undefined>(meta: BytesOnlyEntityMeta, urls: string[]): Promise<Output<T>[]>;
29
- delete(meta: BytesOnlyEntityMeta, uris: string[]): Promise<DeleteResult[]>;
30
- private _writeBytes;
31
- private _readBytes;
83
+ read<T = EntityRecord | undefined>(meta: LocalStorageEntityMeta, urls: string[]): Promise<Output<T>[]>;
32
84
  private _readOne;
33
85
  /**
34
- * Walk localStorage once, returning `[uri, bytes]` for every entry
35
- * whose URI is `prefix + <segment>` with no further `/`. Subtree-only
36
- * paths are excluded by the slash check.
86
+ * Walk localStorage once, returning `Output<EntityRecord>` for every
87
+ * key under `meta.keyRoot` whose remainder is `uri + <segment>` with
88
+ * no further `/`. Subtree-only paths are excluded by the slash check.
37
89
  */
38
90
  private _directLeaves;
91
+ /**
92
+ * Walk localStorage once, returning `Output<EntityRecord>` for every
93
+ * key under `meta.keyRoot` whose remainder starts with `uri` — at
94
+ * any depth. Powers `fn=find`. Unlike `_directLeaves`, there is no
95
+ * `/` cutoff: keys with further slashes past the prefix are kept.
96
+ * Dispatch applies the glob (and sort/cursor/limit/format) on top.
97
+ */
98
+ private _walkDeep;
39
99
  private _ls;
40
100
  private _count;
41
- private _deleteBytes;
101
+ delete(meta: LocalStorageEntityMeta, uris: string[]): Promise<DeleteResult[]>;
42
102
  status(): Promise<StatusResult>;
43
103
  capabilities(): StoreCapabilities;
104
+ private _readMetaSignature;
105
+ /**
106
+ * Collect any `ReadableStream` values on `bytes`-tagged fields into
107
+ * `Uint8Array` and return a shallow-copied record. Non-stream values
108
+ * pass through; matches the normalisation pattern used by Memory and
109
+ * Postgres.
110
+ */
111
+ private _normaliseBytesFields;
44
112
  }
45
113
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/localstorage/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;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;AAQ/D,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAIrB,qBAAa,iBAAkB,YAAW,WAAW,CAAC,mBAAmB,CAAC;IACxE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEtB,MAAM,GAAE;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KACd;IAUN,OAAO,CAAC,MAAM;IAMd,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;IAqBzB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,QAAQ;IAMhB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,GAAG;IAQX,OAAO,CAAC,MAAM;IAOd,OAAO,CAAC,YAAY;IAoBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAmB/B,YAAY,IAAI,iBAAiB;CAGlC"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/localstorage/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAO3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAO3B;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAuB,SAAQ,UAAU;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAMD,qBAAa,iBAAkB,YAAW,WAAW,CAAC,sBAAsB,CAAC;IAC3E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEtB,MAAM,GAAE;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KACd;IAYN,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,sBAAsB;IA0C3D,YAAY,CACV,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC;IAS9B,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB5D,KAAK,CACT,IAAI,EAAE,sBAAsB,EAC5B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqE9B,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,sBAAsB,EAC5B,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IA6BvB,OAAO,CAAC,QAAQ;IAUhB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAkBrB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,GAAG;IAWX,OAAO,CAAC,MAAM;IASd,MAAM,CACJ,IAAI,EAAE,sBAAsB,EAC5B,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,YAAY,EAAE,CAAC;IA+B1B,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IA0B/B,YAAY,IAAI,iBAAiB;IAMjC,OAAO,CAAC,kBAAkB;IAM1B;;;;;OAKG;YACW,qBAAqB;CAmBpC"}