@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,44 @@
1
+ /**
2
+ * ElasticsearchStore — Elasticsearch implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. URIs are
5
+ * partitioned into one index per `protocol_hostname` pair, with the
6
+ * path as the document `_id`. Payload bytes are base64-encoded into a
7
+ * string field — ES has no native binary type that round-trips
8
+ * arbitrary bytes through `_source`.
9
+ *
10
+ * `fn=ls` / `fn=count` are pushed down via an ES regex query that
11
+ * enforces the shallow-direct-leaves contract: `<docPrefix>[^/]+`.
12
+ * Lucene regex queries are auto-anchored, so the pattern must match
13
+ * the full `_id`.
14
+ */
15
+ import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
16
+ import { type BytesOnlyEntityMeta } from "../byte-entity-shim.js";
17
+ import type { EntityStore } from "../entity-store.js";
18
+ import type { EntityRecord, EntitySchema } from "../entity.js";
19
+ import type { StoreCapabilities, StoreWriteResult } from "../types.js";
20
+ import type { ElasticsearchExecutor } from "./mod.js";
21
+ export declare class ElasticsearchStore implements EntityStore<BytesOnlyEntityMeta> {
22
+ private readonly indexPrefix;
23
+ private readonly executor;
24
+ constructor(indexPrefix: string, executor: ElasticsearchExecutor);
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
+ private _leafQuery;
38
+ private _ls;
39
+ private _count;
40
+ private _deleteBytes;
41
+ status(): Promise<StatusResult>;
42
+ capabilities(): StoreCapabilities;
43
+ }
44
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/elasticsearch/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;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;AACrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAwCtD,qBAAa,kBAAmB,YAAW,WAAW,CAAC,mBAAmB,CAAC;IACzE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;gBAErC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB;IAUhE,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;IAkCzB,OAAO,CAAC,UAAU;YAQJ,QAAQ;IAOtB,OAAO,CAAC,UAAU;YAQJ,GAAG;YAoCH,MAAM;YAQN,YAAY;IAqBpB,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAyBrC,YAAY,IAAI,iBAAiB;CAGlC"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * ElasticsearchStore — Elasticsearch implementation of Store.
3
+ *
4
+ * Pure mechanical byte storage with no protocol awareness. URIs are
5
+ * partitioned into one index per `protocol_hostname` pair, with the
6
+ * path as the document `_id`. Payload bytes are base64-encoded into a
7
+ * string field — ES has no native binary type that round-trips
8
+ * arbitrary bytes through `_source`.
9
+ *
10
+ * `fn=ls` / `fn=count` are pushed down via an ES regex query that
11
+ * enforces the shallow-direct-leaves contract: `<docPrefix>[^/]+`.
12
+ * Lucene regex queries are auto-anchored, so the pattern must match
13
+ * the full `_id`.
14
+ */
15
+ import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
16
+ import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js";
17
+ import { dispatchRead } from "../dispatch.js";
18
+ import { storageFailure } from "../errors.js";
19
+ import { toBytes } from "../payload.js";
20
+ import { validateReadParams } from "../read.js";
21
+ const STORE_NAME = "ElasticsearchStore";
22
+ /** Escape characters that are special in Lucene regex syntax. */
23
+ function escapeLuceneRegex(input) {
24
+ return input.replace(/[.?+*|{}\[\]()"\\#@&<>~]/g, "\\$&");
25
+ }
26
+ /**
27
+ * Parse a URI into an Elasticsearch index name and document ID.
28
+ * `protocol://hostname/path` → index: `prefix_protocol_hostname`,
29
+ * docId: `path` (without leading slash).
30
+ */
31
+ function uriToIndexAndDocId(uri, indexPrefix) {
32
+ const url = new URL(uri);
33
+ const protocol = url.protocol.replace(":", "");
34
+ const hostname = url.hostname;
35
+ return {
36
+ index: `${indexPrefix}_${protocol}_${hostname}`,
37
+ docId: url.pathname.substring(1),
38
+ };
39
+ }
40
+ /** Reconstruct a URI from an index name + document ID. */
41
+ function indexAndDocIdToUri(index, indexPrefix, docId) {
42
+ const withoutPrefix = index.substring(indexPrefix.length + 1);
43
+ const firstUnderscore = withoutPrefix.indexOf("_");
44
+ const protocol = withoutPrefix.substring(0, firstUnderscore);
45
+ const hostname = withoutPrefix.substring(firstUnderscore + 1);
46
+ return `${protocol}://${hostname}/${docId}`;
47
+ }
48
+ export class ElasticsearchStore {
49
+ indexPrefix;
50
+ executor;
51
+ constructor(indexPrefix, executor) {
52
+ if (!indexPrefix)
53
+ throw new Error("indexPrefix is required");
54
+ if (!executor)
55
+ throw new Error("executor is required");
56
+ this.indexPrefix = indexPrefix;
57
+ this.executor = executor;
58
+ }
59
+ // ── EntityStore surface ──────────────────────────────────────────
60
+ entitySupport(schema) {
61
+ return bytesOnlyEntitySupport(schema);
62
+ }
63
+ entityStatus(meta) {
64
+ return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
65
+ }
66
+ provisionEntity(_meta) {
67
+ return Promise.resolve();
68
+ }
69
+ write(meta, entries) {
70
+ return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
71
+ }
72
+ read(meta, urls) {
73
+ return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
74
+ }
75
+ delete(meta, uris) {
76
+ return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
77
+ }
78
+ // ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
79
+ async _writeBytes(entries) {
80
+ const results = [];
81
+ for (const entry of entries) {
82
+ try {
83
+ const { index, docId } = uriToIndexAndDocId(entry.uri, this.indexPrefix);
84
+ // We mirror docId into a `path` source field so `ls`/`count`
85
+ // can run analyzed queries against it — ES 8 disallows
86
+ // `regexp` (and prefix/wildcard) against the `_id` metadata
87
+ // field. ES's default dynamic mapping for a string source
88
+ // field produces `path` (text) + `path.keyword` (keyword);
89
+ // we target `path.keyword` for exact-match regex push-down.
90
+ const bytes = await toBytes(entry.payload);
91
+ await this.executor.index(index, docId, {
92
+ payload: encodeBase64(bytes),
93
+ path: docId,
94
+ });
95
+ results.push({ success: true });
96
+ }
97
+ catch (err) {
98
+ results.push({
99
+ success: false,
100
+ ...storageFailure(err, "Write failed", entry.uri),
101
+ });
102
+ }
103
+ }
104
+ return results;
105
+ }
106
+ _readBytes(urls) {
107
+ return dispatchRead(urls, STORE_NAME, {
108
+ read: (p) => this._readOne(p.uri),
109
+ ls: (p) => this._ls(p),
110
+ count: (p) => this._count(p),
111
+ });
112
+ }
113
+ async _readOne(uri) {
114
+ const { index, docId } = uriToIndexAndDocId(uri, this.indexPrefix);
115
+ const doc = await this.executor.get(index, docId);
116
+ if (!doc)
117
+ return undefined;
118
+ return decodeBase64(doc.payload);
119
+ }
120
+ _leafQuery(docPrefix) {
121
+ return {
122
+ regexp: {
123
+ "path.keyword": `${escapeLuceneRegex(docPrefix)}[^/]+`,
124
+ },
125
+ };
126
+ }
127
+ async _ls(parsed) {
128
+ validateReadParams(parsed.params, STORE_NAME);
129
+ const { params } = parsed;
130
+ const format = params.format ?? "full";
131
+ const { index, docId } = uriToIndexAndDocId(parsed.uri, this.indexPrefix);
132
+ const body = {
133
+ query: this._leafQuery(docId),
134
+ };
135
+ if (params.sortBy === "uri") {
136
+ // Sort on the keyword subfield, same reason as the query above.
137
+ body.sort = [{
138
+ "path.keyword": params.sortOrder === "desc" ? "desc" : "asc",
139
+ }];
140
+ }
141
+ if (params.limit !== undefined) {
142
+ const page = params.page ?? 1;
143
+ body.size = params.limit;
144
+ body.from = (page - 1) * params.limit;
145
+ }
146
+ else {
147
+ body.size = 10_000;
148
+ }
149
+ if (format === "uris")
150
+ body._source = false;
151
+ const result = await this.executor.search(index, body);
152
+ if (format === "uris") {
153
+ return result.hits.map((hit) => indexAndDocIdToUri(index, this.indexPrefix, hit._id));
154
+ }
155
+ return result.hits.map((hit) => [
156
+ indexAndDocIdToUri(index, this.indexPrefix, hit._id),
157
+ hit._source ? decodeBase64(hit._source.payload) : undefined,
158
+ ]);
159
+ }
160
+ async _count(parsed) {
161
+ if (parsed.params.pattern !== undefined) {
162
+ throw new Error(`${STORE_NAME}: pattern filter not supported`);
163
+ }
164
+ const { index, docId } = uriToIndexAndDocId(parsed.uri, this.indexPrefix);
165
+ return await this.executor.count(index, { query: this._leafQuery(docId) });
166
+ }
167
+ async _deleteBytes(uris) {
168
+ const results = [];
169
+ for (const uri of uris) {
170
+ try {
171
+ const { index, docId } = uriToIndexAndDocId(uri, this.indexPrefix);
172
+ await this.executor.delete(index, docId);
173
+ results.push({ success: true });
174
+ }
175
+ catch (err) {
176
+ results.push({
177
+ success: false,
178
+ ...storageFailure(err, "Delete failed", uri),
179
+ });
180
+ }
181
+ }
182
+ return results;
183
+ }
184
+ // ── Status ───────────────────────────────────────────────────────
185
+ async status() {
186
+ try {
187
+ const ok = await this.executor.ping();
188
+ if (!ok) {
189
+ return {
190
+ status: "unhealthy",
191
+ message: "Elasticsearch cluster is not reachable",
192
+ fns: ["read", "ls", "count"],
193
+ };
194
+ }
195
+ return {
196
+ status: "healthy",
197
+ message: "Elasticsearch store is operational",
198
+ fns: ["read", "ls", "count"],
199
+ details: { indexPrefix: this.indexPrefix },
200
+ };
201
+ }
202
+ catch (error) {
203
+ return {
204
+ status: "unhealthy",
205
+ message: error instanceof Error ? error.message : String(error),
206
+ fns: ["read", "ls", "count"],
207
+ };
208
+ }
209
+ }
210
+ capabilities() {
211
+ return { atomicBatch: false };
212
+ }
213
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * EntityStore — schema-aware storage contract.
3
+ *
4
+ * A single store instance hosts many entities side-by-side. Nothing is
5
+ * pinned at construction; every operation is keyed by an opaque
6
+ * {@link EntityMeta} returned from {@link EntityStore.entitySupport}.
7
+ *
8
+ * ## Three-step lifecycle, controlled by the caller
9
+ *
10
+ * 1. **`entitySupport(schema)`** — pure. Compiles the schema into the
11
+ * backend's operational handle (column plans, target table names,
12
+ * bytes-field sets, the per-field {@link EntitySupport} report).
13
+ * No side effects, no IO. The returned meta is what the rest of
14
+ * the surface consumes.
15
+ * 2. **`provisionEntity(meta)`** — idempotent. Materialises the
16
+ * entity on the medium: creates the table, allocates the bucket,
17
+ * writes the index. Re-running with the same meta is a no-op.
18
+ * Running with a meta whose shape conflicts with what is already
19
+ * provisioned at the same name surfaces an error.
20
+ * 3. **`entityStatus(meta)`** — checks whether the entity described
21
+ * by `meta` is live on the medium right now. Returns `"live"`
22
+ * only when the medium's current shape matches `meta`; a same-name
23
+ * entity with a different shape reports `"unprovisioned"`. Use to
24
+ * decide whether to provision now or skip; do not use as a
25
+ * precondition before every operation — it adds cost that the
26
+ * natural backend failure already gives you for free.
27
+ *
28
+ * Provisioning is the caller's job. `write` / `read` / `delete` do
29
+ * not pre-check or auto-provision; if a meta refers to an entity
30
+ * that is not live on the medium, the operation surfaces the
31
+ * backend's natural failure (storage error / undefined table / etc.).
32
+ *
33
+ * ## Strictness
34
+ *
35
+ * Writes/reads/deletes are strict. A record that does not match the
36
+ * meta's declared shape produces a `StoreWriteResult.error` (or
37
+ * `DeleteResult.error` for delete-time failures). The store never
38
+ * silently drops or coerces — coercion is the client's job (see
39
+ * `SaveClient`, which projects raw bytes into a `BYTES_ENTITY`
40
+ * record when its target is `BYTES_ENTITY`).
41
+ *
42
+ * Reads return `Output<EntityRecord | undefined>` for `fn=read`;
43
+ * `fn=ls` and `fn=count` follow the same conventions as `Store`
44
+ * (URI lists and numbers — see `./read.ts`).
45
+ */
46
+ import type { DeleteResult, Output, StatusResult } from "./deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
47
+ import type { EntityMeta, EntityRecord, EntitySchema } from "./entity.js";
48
+ import type { StoreCapabilities, StoreWriteResult } from "./types.js";
49
+ export interface EntityStore<TMeta extends EntityMeta = EntityMeta> {
50
+ /**
51
+ * Compile a schema into the backend's opaque operational handle.
52
+ * Pure: no IO, no cache mutation. The caller owns the returned
53
+ * meta's lifetime and decides whether to memoize, re-derive, or
54
+ * pass it through.
55
+ */
56
+ entitySupport(schema: EntitySchema): TMeta;
57
+ /**
58
+ * Whether the entity described by `meta` is live on the medium.
59
+ * `"live"` only when the medium's current shape matches `meta`;
60
+ * a same-name entity with a different shape reports
61
+ * `"unprovisioned"` so callers do not act on a collision.
62
+ */
63
+ entityStatus(meta: TMeta): Promise<"live" | "unprovisioned">;
64
+ /**
65
+ * Materialise the entity described by `meta` on the medium.
66
+ * Idempotent: repeat calls with the same meta no-op. Calling with
67
+ * a meta whose shape conflicts with an already-provisioned entity
68
+ * at the same name throws.
69
+ */
70
+ provisionEntity(meta: TMeta): Promise<void>;
71
+ /**
72
+ * Write records described by `meta` at the given URIs. Per-entry
73
+ * result is returned in input order. A record that does not match
74
+ * `meta`'s declared shape produces a failure result for that
75
+ * entry — the store does not coerce. Calling with a meta whose
76
+ * entity is not live on the medium surfaces the backend's natural
77
+ * storage failure per entry.
78
+ */
79
+ write(meta: TMeta, entries: {
80
+ uri: string;
81
+ record: EntityRecord;
82
+ }[]): Promise<StoreWriteResult[]>;
83
+ /**
84
+ * Read records described by `meta` at the given URLs. URLs follow
85
+ * the standard `fn=read|ls|count|x-*` grammar — see `./read.ts`.
86
+ * `fn=read` yields `Output<EntityRecord | undefined>` (undefined
87
+ * payload means miss); `fn=ls` and `fn=count` follow the package
88
+ * conventions. Reads against an entity that is not live on the
89
+ * medium surface as misses or the backend's natural read error.
90
+ */
91
+ read<T = EntityRecord | undefined>(meta: TMeta, urls: string[]): Promise<Output<T>[]>;
92
+ /**
93
+ * Delete records described by `meta` at the given URIs. Per-entry
94
+ * result is returned in input order.
95
+ */
96
+ delete(meta: TMeta, uris: string[]): Promise<DeleteResult[]>;
97
+ /** Health + capabilities, aggregated across all hosted entities. */
98
+ status(): Promise<StatusResult>;
99
+ /**
100
+ * Optional capability reporting. Backends advertise what they can
101
+ * do so clients can make informed decisions (e.g. wrap deletes +
102
+ * writes in a transaction when `atomicBatch` is true).
103
+ */
104
+ capabilities?(): StoreCapabilities;
105
+ }
106
+ //# sourceMappingURL=entity-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-store.d.ts","sourceRoot":"","sources":["../src/entity-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,kEAAkE,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU;IAChE;;;;;OAKG;IACH,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,KAAK,CAAC;IAE3C;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC;IAE7D;;;;;OAKG;IACH,eAAe,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C;;;;;;;OAOG;IACH,KAAK,CACH,IAAI,EAAE,KAAK,EACX,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,GAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE/B;;;;;;;OAOG;IACH,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,SAAS,EAC/B,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAExB;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAE7D,oEAAoE;IACpE,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAEhC;;;;OAIG;IACH,YAAY,CAAC,IAAI,iBAAiB,CAAC;CACpC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * EntityStore — schema-aware storage contract.
3
+ *
4
+ * A single store instance hosts many entities side-by-side. Nothing is
5
+ * pinned at construction; every operation is keyed by an opaque
6
+ * {@link EntityMeta} returned from {@link EntityStore.entitySupport}.
7
+ *
8
+ * ## Three-step lifecycle, controlled by the caller
9
+ *
10
+ * 1. **`entitySupport(schema)`** — pure. Compiles the schema into the
11
+ * backend's operational handle (column plans, target table names,
12
+ * bytes-field sets, the per-field {@link EntitySupport} report).
13
+ * No side effects, no IO. The returned meta is what the rest of
14
+ * the surface consumes.
15
+ * 2. **`provisionEntity(meta)`** — idempotent. Materialises the
16
+ * entity on the medium: creates the table, allocates the bucket,
17
+ * writes the index. Re-running with the same meta is a no-op.
18
+ * Running with a meta whose shape conflicts with what is already
19
+ * provisioned at the same name surfaces an error.
20
+ * 3. **`entityStatus(meta)`** — checks whether the entity described
21
+ * by `meta` is live on the medium right now. Returns `"live"`
22
+ * only when the medium's current shape matches `meta`; a same-name
23
+ * entity with a different shape reports `"unprovisioned"`. Use to
24
+ * decide whether to provision now or skip; do not use as a
25
+ * precondition before every operation — it adds cost that the
26
+ * natural backend failure already gives you for free.
27
+ *
28
+ * Provisioning is the caller's job. `write` / `read` / `delete` do
29
+ * not pre-check or auto-provision; if a meta refers to an entity
30
+ * that is not live on the medium, the operation surfaces the
31
+ * backend's natural failure (storage error / undefined table / etc.).
32
+ *
33
+ * ## Strictness
34
+ *
35
+ * Writes/reads/deletes are strict. A record that does not match the
36
+ * meta's declared shape produces a `StoreWriteResult.error` (or
37
+ * `DeleteResult.error` for delete-time failures). The store never
38
+ * silently drops or coerces — coercion is the client's job (see
39
+ * `SaveClient`, which projects raw bytes into a `BYTES_ENTITY`
40
+ * record when its target is `BYTES_ENTITY`).
41
+ *
42
+ * Reads return `Output<EntityRecord | undefined>` for `fn=read`;
43
+ * `fn=ls` and `fn=count` follow the same conventions as `Store`
44
+ * (URI lists and numbers — see `./read.ts`).
45
+ */
46
+ export {};
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Entity schema types.
3
+ *
4
+ * `b3nd-save` ships one store interface: `EntityStore` (see
5
+ * `./entity-store.ts`) — schema-aware: every operation takes the
6
+ * `EntitySchema` it targets, so a single store instance can host many
7
+ * entities side-by-side. Schema is per-call, not pinned at
8
+ * construction.
9
+ *
10
+ * Entities are a client-layer concern. Clients map their wire payload
11
+ * (`Output<T>`) into `EntityRecord`s and call the store with an
12
+ * explicit schema; the store organises the medium accordingly
13
+ * (separate Postgres tables, separate Mongo collections, separate
14
+ * in-memory record maps). The schema travels with the data: there is
15
+ * no implicit "current entity" inside the store.
16
+ *
17
+ * ## Open type vocabulary
18
+ *
19
+ * `EntityField.type` is `string[]`. The strings this package knows
20
+ * about live in {@link TYPE_TAGS}, but the field type itself is
21
+ * intentionally not a literal union — entities are shared across
22
+ * protocols and custom stores, and one protocol's `"money"` or
23
+ * `"geo"` tag should travel unchanged through stores that don't
24
+ * recognise it. A field may carry multiple tags (e.g.
25
+ * `["string", "email"]`) — they are refinements, not a value-type
26
+ * union. A store consults the tags it knows to decide how to
27
+ * materialise the field, and reports any field whose tags it cannot
28
+ * make sense of as unsupported.
29
+ *
30
+ * ## Coercion vs. error reporting
31
+ *
32
+ * The store is strict: anything that doesn't fit the schema becomes
33
+ * an error result so the rig wiring can be fixed. Coercion and field
34
+ * projection are the client's job — `SaveClient` in its default
35
+ * `BYTES_ENTITY` mode builds a `{ payload: bytes }` record from
36
+ * incoming bytes; in record mode it passes records through verbatim.
37
+ * If a record arrives with extra or mistyped fields, the store
38
+ * reports the error rather than silently dropping data.
39
+ */
40
+ /**
41
+ * Canonical type tags published by this package.
42
+ *
43
+ * Stores in `b3nd-save` recognise these tags. Other protocols and
44
+ * custom stores may freely add their own; `EntityField.type` stays
45
+ * `string[]` so unknown tags pass through intact.
46
+ */
47
+ export declare const TYPE_TAGS: {
48
+ readonly STRING: "string";
49
+ readonly NUMBER: "number";
50
+ readonly BIGINT: "bigint";
51
+ readonly BOOLEAN: "boolean";
52
+ readonly BYTES: "bytes";
53
+ readonly TIMESTAMP: "timestamp";
54
+ readonly JSON: "json";
55
+ };
56
+ /**
57
+ * A field of an entity — a name plus an open list of type tags.
58
+ *
59
+ * Multiple tags are refinements describing the same value (e.g.
60
+ * `["string", "email"]` — a string value semantically an email). A
61
+ * store that understands `"string"` can store the value; the
62
+ * `"email"` tag is a hint stores that want to validate or index
63
+ * differently can use.
64
+ */
65
+ export interface EntityField {
66
+ name: string;
67
+ type: string[];
68
+ }
69
+ /**
70
+ * Declarative description of an entity.
71
+ *
72
+ * `name` is the entity's identifier in the medium — e.g. the Postgres
73
+ * table name, the Mongo collection name. Authors should pick a value
74
+ * that is safe for those mediums (lowercase, no special chars).
75
+ */
76
+ export interface EntitySchema {
77
+ name: string;
78
+ fields: EntityField[];
79
+ }
80
+ /**
81
+ * A record of an entity — values keyed by field name. The type is
82
+ * `Record<string, unknown>` because the schema is open: per-field
83
+ * value types are decided by the tags an adapter understands. Store
84
+ * implementations validate against the schema on the way in.
85
+ */
86
+ export type EntityRecord = Record<string, unknown>;
87
+ /**
88
+ * Per-field support reported by `EntityStore.entitySupport`.
89
+ *
90
+ * A field is *supported* when the store recognised at least one of
91
+ * its tags and would provision storage for it. Unsupported fields
92
+ * carry a human-readable `reason`. Stores do not silently drop
93
+ * unsupported fields on write — see the file header on coercion.
94
+ */
95
+ export interface EntitySupport {
96
+ entity: string;
97
+ supported: string[];
98
+ unsupported: {
99
+ name: string;
100
+ reason: string;
101
+ }[];
102
+ }
103
+ /**
104
+ * Opaque operational handle for an entity on a specific store.
105
+ *
106
+ * Returned by `EntityStore.entitySupport(schema)` and threaded back
107
+ * to `provisionEntity`, `entityStatus`, `write`, `read`, and
108
+ * `delete`. The base shape only exposes `support`; each backend
109
+ * extends this with whatever it needs to operate (column plans,
110
+ * target table names, bytes-field sets, etc.). The caller treats
111
+ * the meta as opaque and owns its lifetime — the store keeps no
112
+ * per-entity caches of its own.
113
+ */
114
+ export interface EntityMeta {
115
+ readonly support: EntitySupport;
116
+ }
117
+ /**
118
+ * Canonical entity for raw-byte storage.
119
+ *
120
+ * `EntityStore.write(BYTES_ENTITY, [{ uri, record: { payload: bytes }}])`
121
+ * is how opaque bytes go into the store; pair it with `mapToBytes`
122
+ * from `./clients` to expose a bytes-on-the-wire `ProtocolInterfaceNode`.
123
+ */
124
+ export declare const BYTES_ENTITY: EntitySchema;
125
+ //# sourceMappingURL=entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;;;;;;;;CAQZ,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,YAG1B,CAAC"}
package/esm/entity.js ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Entity schema types.
3
+ *
4
+ * `b3nd-save` ships one store interface: `EntityStore` (see
5
+ * `./entity-store.ts`) — schema-aware: every operation takes the
6
+ * `EntitySchema` it targets, so a single store instance can host many
7
+ * entities side-by-side. Schema is per-call, not pinned at
8
+ * construction.
9
+ *
10
+ * Entities are a client-layer concern. Clients map their wire payload
11
+ * (`Output<T>`) into `EntityRecord`s and call the store with an
12
+ * explicit schema; the store organises the medium accordingly
13
+ * (separate Postgres tables, separate Mongo collections, separate
14
+ * in-memory record maps). The schema travels with the data: there is
15
+ * no implicit "current entity" inside the store.
16
+ *
17
+ * ## Open type vocabulary
18
+ *
19
+ * `EntityField.type` is `string[]`. The strings this package knows
20
+ * about live in {@link TYPE_TAGS}, but the field type itself is
21
+ * intentionally not a literal union — entities are shared across
22
+ * protocols and custom stores, and one protocol's `"money"` or
23
+ * `"geo"` tag should travel unchanged through stores that don't
24
+ * recognise it. A field may carry multiple tags (e.g.
25
+ * `["string", "email"]`) — they are refinements, not a value-type
26
+ * union. A store consults the tags it knows to decide how to
27
+ * materialise the field, and reports any field whose tags it cannot
28
+ * make sense of as unsupported.
29
+ *
30
+ * ## Coercion vs. error reporting
31
+ *
32
+ * The store is strict: anything that doesn't fit the schema becomes
33
+ * an error result so the rig wiring can be fixed. Coercion and field
34
+ * projection are the client's job — `SaveClient` in its default
35
+ * `BYTES_ENTITY` mode builds a `{ payload: bytes }` record from
36
+ * incoming bytes; in record mode it passes records through verbatim.
37
+ * If a record arrives with extra or mistyped fields, the store
38
+ * reports the error rather than silently dropping data.
39
+ */
40
+ /**
41
+ * Canonical type tags published by this package.
42
+ *
43
+ * Stores in `b3nd-save` recognise these tags. Other protocols and
44
+ * custom stores may freely add their own; `EntityField.type` stays
45
+ * `string[]` so unknown tags pass through intact.
46
+ */
47
+ export const TYPE_TAGS = {
48
+ STRING: "string",
49
+ NUMBER: "number",
50
+ BIGINT: "bigint",
51
+ BOOLEAN: "boolean",
52
+ BYTES: "bytes",
53
+ TIMESTAMP: "timestamp",
54
+ JSON: "json",
55
+ };
56
+ /**
57
+ * Canonical entity for raw-byte storage.
58
+ *
59
+ * `EntityStore.write(BYTES_ENTITY, [{ uri, record: { payload: bytes }}])`
60
+ * is how opaque bytes go into the store; pair it with `mapToBytes`
61
+ * from `./clients` to expose a bytes-on-the-wire `ProtocolInterfaceNode`.
62
+ */
63
+ export const BYTES_ENTITY = {
64
+ name: "bytes",
65
+ fields: [{ name: "payload", type: [TYPE_TAGS.BYTES] }],
66
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Backend-author helpers for translating thrown executor errors into
3
+ * the structured failure shape used by `StoreWriteResult` and
4
+ * `DeleteResult`.
5
+ *
6
+ * Every Save backend ends up wanting the same `try/catch → { error,
7
+ * errorDetail }` translation; this keeps the per-store catch blocks
8
+ * one line and the error code consistent (`STORAGE_ERROR` for
9
+ * anything that came from the underlying driver).
10
+ */
11
+ import type { B3ndError } from "./deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
12
+ /**
13
+ * Build the structured-failure half of a Store result from a thrown
14
+ * value. `fallback` is used when `err` isn't an `Error` (rare). `uri`
15
+ * is included on `errorDetail` when the failure attributes to a
16
+ * specific entry — omit for atomic-batch failures that affect the
17
+ * whole batch.
18
+ */
19
+ export declare function storageFailure(err: unknown, fallback: string, uri?: string): {
20
+ error: string;
21
+ errorDetail: B3ndError;
22
+ };
23
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kEAAkE,CAAC;AAElG;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,SAAS,CAAA;CAAE,CAM3C"}
package/esm/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Backend-author helpers for translating thrown executor errors into
3
+ * the structured failure shape used by `StoreWriteResult` and
4
+ * `DeleteResult`.
5
+ *
6
+ * Every Save backend ends up wanting the same `try/catch → { error,
7
+ * errorDetail }` translation; this keeps the per-store catch blocks
8
+ * one line and the error code consistent (`STORAGE_ERROR` for
9
+ * anything that came from the underlying driver).
10
+ */
11
+ import { Errors } from "./deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js";
12
+ /**
13
+ * Build the structured-failure half of a Store result from a thrown
14
+ * value. `fallback` is used when `err` isn't an `Error` (rare). `uri`
15
+ * is included on `errorDetail` when the failure attributes to a
16
+ * specific entry — omit for atomic-batch failures that affect the
17
+ * whole batch.
18
+ */
19
+ export function storageFailure(err, fallback, uri) {
20
+ const message = err instanceof Error ? err.message : fallback;
21
+ return {
22
+ error: message,
23
+ errorDetail: Errors.storageError(message, uri),
24
+ };
25
+ }