@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
@@ -1,26 +1,85 @@
1
1
  /**
2
- * MongoStore — MongoDB implementation of Store.
2
+ * MongoStore — MongoDB implementation of `EntityStore`.
3
3
  *
4
- * Pure mechanical byte storage with no protocol awareness. Uses an
5
- * injected MongoExecutor so the package does not depend on a specific
6
- * MongoDB driver. Payload is stored as a BSON `Binary` value (the
7
- * `mongodb` driver maps `Uint8Array` to it transparently).
4
+ * One layout for every schema: a per-entity collection named
5
+ * `{prefix}_{entity}_data` with a `uri` primary key and one document
6
+ * key per supported field, plus `createdAt` / `updatedAt`.
7
+ * `BYTES_ENTITY` is just an entity with one `payload` field of BSON
8
+ * binary — no legacy special case.
8
9
  *
9
- * `fn=ls`/`fn=count` push down to a regex prefix query that enforces
10
- * the shallow-direct-leaves contract: `^<prefix>[^/]+$`.
10
+ * ## Lifecycle
11
+ *
12
+ * `entitySupport(schema)` is pure — it runs `planFields` and returns
13
+ * a `MongoEntityMeta` with the target collection name, declared field
14
+ * set, bytes-field set, canonical-tag-per-field signature, and the
15
+ * support report. No IO.
16
+ *
17
+ * `provisionEntity(meta)` materialises the entity:
18
+ *
19
+ * 1. Reads the meta collection `{prefix}_meta` for a prior entry
20
+ * under `meta.support.entity`.
21
+ * 2. If found with a matching signature → no-op (idempotent).
22
+ * 3. If found with a different signature → throws (collision: a
23
+ * same-name entity is already live in a different shape).
24
+ * 4. Otherwise: creates the entity collection, creates the `uri`
25
+ * index, and upserts the meta entry.
26
+ *
27
+ * `entityStatus(meta)` reads the meta collection and reports `"live"`
28
+ * only when the stored signature matches. Mongo is schemaless on the
29
+ * wire — without an explicit meta record there is nothing to
30
+ * introspect, so the meta collection is how shape collisions get
31
+ * detected (the same approach MemoryStore uses; the SQL backends get
32
+ * the same guarantee for free via column-type introspection).
33
+ *
34
+ * Writes/reads/deletes consume the meta directly: no per-call cache
35
+ * gate. The store enforces the extras check (a record with keys not
36
+ * declared in `meta` produces a per-entry write failure); value-type
37
+ * enforcement is left to higher layers because Mongo will accept any
38
+ * value at the BSON level.
39
+ *
40
+ * `fn=ls` / `fn=count` push down to MongoDB via a regex prefix filter
41
+ * on `uri` that enforces the shallow-direct-leaves contract
42
+ * (`^<prefix>[^/]+$`).
43
+ *
44
+ * `fn=find` (v2 §3.3) reuses the same regex query shape but composes
45
+ * the body for the broader grammar (`**` → `.*`, crosses `/`). The
46
+ * literal prefix stays escaped; the glob tail is compiled via
47
+ * `saveGlobToRegexBody` — the same helper `_ls` uses, which already
48
+ * emits `.*` for `**` regardless of fn (the wildcard semantics are a
49
+ * property of the glob token, not the verb). The `[^/]+` shallow
50
+ * fallback that `_ls` uses for "no pattern" is NOT used here: `find`
51
+ * always arrives with a pattern (parseUrl mirrors the URI-embedded
52
+ * glob into `params.pattern`; dispatch pushes it through under
53
+ * `pushDownFind: true`).
54
+ *
55
+ * Perf note: an anchored `^<literal-prefix><body>$` regex with a long
56
+ * literal prefix can use the per-collection `uri` index for prefix
57
+ * scans; broader patterns (`**` at the start) degrade to a collection
58
+ * scan. The contract advertises `find` with that caveat — callers that
59
+ * need fast deep walks under shallow prefixes should keep their root
60
+ * literal (`<root><room>/alice/**` scans only `alice/`).
61
+ *
62
+ * Mongo does not provide a portable single-collection batch
63
+ * write/delete primitive that fits the package's per-entry result
64
+ * contract, so writes happen one document at a time and
65
+ * `capabilities.atomicBatch` is `false`. Stream-shaped values on
66
+ * bytes fields are collected to `Uint8Array` up front per entry.
11
67
  */
12
- import { bytesOnlyDelete, bytesOnlyEntitySupport, bytesOnlyRead, bytesOnlyWrite, } from "../byte-entity-shim.js";
13
68
  import { dispatchRead } from "../dispatch.js";
14
69
  import { storageFailure } from "../errors.js";
15
70
  import { toBytes } from "../payload.js";
16
71
  import { validateReadParams } from "../read.js";
72
+ import { saveGlobToRegexBody } from "../glob.js";
73
+ import { TYPE_TAGS, } from "../entity.js";
74
+ import { IDENTIFIER_PATTERN, isValidIdentifier } from "../identifiers.js";
75
+ import { computeSignature, planFields } from "./fields.js";
17
76
  const STORE_NAME = "MongoStore";
18
- /** Escape special regex characters for safe use in a RegExp pattern. */
77
+ /** Escape special regex characters for safe use in a `$regex` filter. */
19
78
  function escapeRegex(input) {
20
79
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21
80
  }
22
81
  /**
23
- * Normalize a Mongo document field into a `Uint8Array`. The driver may
82
+ * Normalise a Mongo document field into a `Uint8Array`. The driver may
24
83
  * surface BSON `Binary` (which exposes `.buffer`) or a raw
25
84
  * `Uint8Array` depending on options.
26
85
  */
@@ -37,123 +96,136 @@ function docToBytes(value) {
37
96
  value.buffer instanceof ArrayBuffer) {
38
97
  return new Uint8Array(value.buffer);
39
98
  }
40
- throw new Error(`MongoStore: unexpected payload type ${typeof value}`);
99
+ throw new Error(`${STORE_NAME}: unexpected payload type ${typeof value}`);
100
+ }
101
+ function entityCollectionName(prefix, entityName) {
102
+ if (!isValidIdentifier(entityName)) {
103
+ throw new Error(`${STORE_NAME}: entity name '${entityName}' must match ${IDENTIFIER_PATTERN.source}`);
104
+ }
105
+ return `${prefix}_${entityName}_data`;
106
+ }
107
+ function metaCollectionName(prefix) {
108
+ return `${prefix}_meta`;
41
109
  }
42
110
  export class MongoStore {
43
- collectionName;
111
+ collectionPrefix;
44
112
  executor;
45
- constructor(collectionName, executor) {
46
- if (!collectionName)
47
- throw new Error("collectionName is required");
113
+ constructor(collectionPrefix, executor) {
114
+ if (!collectionPrefix)
115
+ throw new Error("collectionPrefix is required");
116
+ if (!isValidIdentifier(collectionPrefix)) {
117
+ throw new Error(`collectionPrefix must match ${IDENTIFIER_PATTERN.source}; got '${collectionPrefix}'`);
118
+ }
48
119
  if (!executor)
49
120
  throw new Error("executor is required");
50
- this.collectionName = collectionName;
121
+ this.collectionPrefix = collectionPrefix;
51
122
  this.executor = executor;
52
123
  }
53
- // ── EntityStore surface ──────────────────────────────────────────
124
+ // ── Lifecycle ────────────────────────────────────────────────────
54
125
  entitySupport(schema) {
55
- return bytesOnlyEntitySupport(schema);
56
- }
57
- entityStatus(meta) {
58
- return Promise.resolve(meta.isBytes ? "live" : "unprovisioned");
59
- }
60
- provisionEntity(_meta) {
61
- return Promise.resolve();
126
+ const { fields, unsupported } = planFields(schema.fields);
127
+ const support = {
128
+ entity: schema.name,
129
+ supported: fields.map((f) => f.name),
130
+ unsupported,
131
+ };
132
+ return {
133
+ support,
134
+ collectionName: entityCollectionName(this.collectionPrefix, schema.name),
135
+ fields,
136
+ declared: new Set(fields.map((f) => f.name)),
137
+ bytesFields: new Set(fields.filter((f) => f.tag === TYPE_TAGS.BYTES).map((f) => f.name)),
138
+ signature: computeSignature(schema.name, fields),
139
+ };
62
140
  }
63
- write(meta, entries) {
64
- return bytesOnlyWrite(meta, STORE_NAME, entries, (e) => this._writeBytes(e));
141
+ async entityStatus(meta) {
142
+ const metaDoc = await this.executor
143
+ .collection(metaCollectionName(this.collectionPrefix))
144
+ .findOne({ _id: meta.support.entity });
145
+ if (!metaDoc)
146
+ return "unprovisioned";
147
+ return metaDoc.signature === meta.signature ? "live" : "unprovisioned";
65
148
  }
66
- read(meta, urls) {
67
- return bytesOnlyRead(meta, STORE_NAME, urls, (u) => this._readBytes(u));
68
- }
69
- delete(meta, uris) {
70
- return bytesOnlyDelete(meta, STORE_NAME, uris, (u) => this._deleteBytes(u));
149
+ async provisionEntity(meta) {
150
+ const metaColl = this.executor.collection(metaCollectionName(this.collectionPrefix));
151
+ const existing = await metaColl.findOne({ _id: meta.support.entity });
152
+ if (existing) {
153
+ if (existing.signature !== meta.signature) {
154
+ throw new Error(`${STORE_NAME}: entity '${meta.support.entity}' is already ` +
155
+ `provisioned with a different shape at collection '${meta.collectionName}'`);
156
+ }
157
+ return;
158
+ }
159
+ await this.executor.createCollection(meta.collectionName);
160
+ await this.executor.collection(meta.collectionName).createIndex({ uri: 1 });
161
+ await metaColl.updateOne({ _id: meta.support.entity }, { $set: { _id: meta.support.entity, signature: meta.signature } }, { upsert: true });
71
162
  }
72
- // ── Byte ops (BYTES_ENTITY routing) ──────────────────────────────
73
- async _writeBytes(entries) {
74
- const results = [];
75
- for (const entry of entries) {
163
+ // ── Write ────────────────────────────────────────────────────────
164
+ async write(meta, entries) {
165
+ if (entries.length === 0)
166
+ return [];
167
+ const out = new Array(entries.length);
168
+ const coll = this.executor.collection(meta.collectionName);
169
+ for (let i = 0; i < entries.length; i++) {
170
+ const { uri, record } = entries[i];
171
+ const extras = Object.keys(record).filter((k) => !meta.declared.has(k));
172
+ if (extras.length > 0) {
173
+ out[i] = {
174
+ success: false,
175
+ ...storageFailure(new Error(`${STORE_NAME}: record contains keys not declared in schema '${meta.support.entity}': ${extras.join(", ")}`), "Schema mismatch", uri),
176
+ };
177
+ continue;
178
+ }
76
179
  try {
77
- const bytes = await toBytes(entry.payload);
78
- await this.executor.updateOne({ uri: entry.uri }, {
79
- $set: {
80
- uri: entry.uri,
81
- payload: bytes,
82
- updatedAt: new Date(),
83
- },
180
+ const doc = await buildDocument(meta, uri, record);
181
+ await coll.updateOne({ uri }, {
182
+ $set: { ...doc, updatedAt: new Date() },
183
+ $setOnInsert: { createdAt: new Date() },
84
184
  }, { upsert: true });
85
- results.push({ success: true });
185
+ out[i] = { success: true };
86
186
  }
87
187
  catch (err) {
88
- results.push({
188
+ out[i] = {
89
189
  success: false,
90
- ...storageFailure(err, "Write failed", entry.uri),
91
- });
190
+ ...storageFailure(err, "Write failed", uri),
191
+ };
92
192
  }
93
193
  }
94
- return results;
194
+ return out;
95
195
  }
96
- _readBytes(urls) {
196
+ // ── Read ─────────────────────────────────────────────────────────
197
+ read(meta, urls) {
97
198
  return dispatchRead(urls, STORE_NAME, {
98
- read: (p) => this._readOne(p.uri),
99
- ls: (p) => this._ls(p),
100
- count: (p) => this._count(p),
199
+ read: (p) => this._readOne(meta, p.uri),
200
+ ls: (p) => this._ls(meta, p),
201
+ count: (p) => this._count(meta, p),
202
+ find: (p) => this._find(meta, p),
203
+ pushDownPattern: true,
204
+ pushDownCursor: true,
205
+ pushDownSortBy: true,
206
+ pushDownFind: true,
101
207
  });
102
208
  }
103
- async _readOne(uri) {
104
- const doc = await this.executor.findOne({ uri });
105
- if (!doc)
106
- return undefined;
107
- return docToBytes(doc.payload);
108
- }
109
- /** Build the shallow-direct-leaves regex filter for a prefix. */
110
- _leafFilter(prefixUri) {
111
- return { uri: { $regex: `^${escapeRegex(prefixUri)}[^/]+$` } };
112
- }
113
- async _ls(parsed) {
114
- validateReadParams(parsed.params, STORE_NAME);
115
- const { params } = parsed;
116
- const format = params.format ?? "full";
117
- const options = {};
118
- if (params.sortBy === "uri") {
119
- options.sort = { uri: params.sortOrder === "desc" ? -1 : 1 };
120
- }
121
- if (params.limit !== undefined) {
122
- const page = params.page ?? 1;
123
- options.limit = params.limit;
124
- options.skip = (page - 1) * params.limit;
125
- }
126
- if (format === "uris") {
127
- options.projection = { uri: 1, _id: 0 };
128
- }
129
- const docs = await this.executor.findMany(this._leafFilter(parsed.uri), options);
130
- if (format === "uris")
131
- return docs.map((d) => d.uri);
132
- return docs.map((d) => [d.uri, docToBytes(d.payload)]);
133
- }
134
- async _count(parsed) {
135
- if (parsed.params.pattern !== undefined) {
136
- throw new Error(`${STORE_NAME}: pattern filter not supported`);
137
- }
138
- return await this.executor.countDocuments(this._leafFilter(parsed.uri));
139
- }
140
- async _deleteBytes(uris) {
141
- const results = [];
142
- for (const uri of uris) {
209
+ async delete(meta, uris) {
210
+ if (uris.length === 0)
211
+ return [];
212
+ const coll = this.executor.collection(meta.collectionName);
213
+ const out = new Array(uris.length);
214
+ for (let i = 0; i < uris.length; i++) {
143
215
  try {
144
- await this.executor.deleteOne({ uri });
145
- results.push({ success: true });
216
+ await coll.deleteOne({ uri: uris[i] });
217
+ out[i] = { success: true };
146
218
  }
147
219
  catch (err) {
148
- results.push({
220
+ out[i] = {
149
221
  success: false,
150
- ...storageFailure(err, "Delete failed", uri),
151
- });
222
+ ...storageFailure(err, "Delete failed", uris[i]),
223
+ };
152
224
  }
153
225
  }
154
- return results;
226
+ return out;
155
227
  }
156
- // ── Status ───────────────────────────────────────────────────────
228
+ // ── Status / capabilities ────────────────────────────────────────
157
229
  async status() {
158
230
  try {
159
231
  const ok = await this.executor.ping();
@@ -161,25 +233,206 @@ export class MongoStore {
161
233
  return {
162
234
  status: "unhealthy",
163
235
  message: "MongoDB ping failed",
164
- fns: ["read", "ls", "count"],
236
+ fns: ["read", "ls", "find", "count"],
165
237
  };
166
238
  }
167
239
  return {
168
240
  status: "healthy",
169
241
  message: "MongoDB store is operational",
170
- fns: ["read", "ls", "count"],
171
- details: { collectionName: this.collectionName },
242
+ fns: ["read", "ls", "find", "count"],
243
+ details: { collectionPrefix: this.collectionPrefix },
172
244
  };
173
245
  }
174
246
  catch (error) {
175
247
  return {
176
248
  status: "unhealthy",
177
249
  message: error instanceof Error ? error.message : String(error),
178
- fns: ["read", "ls", "count"],
250
+ fns: ["read", "ls", "find", "count"],
179
251
  };
180
252
  }
181
253
  }
182
254
  capabilities() {
183
255
  return { atomicBatch: false };
184
256
  }
257
+ // ── Internals ────────────────────────────────────────────────────
258
+ async _readOne(meta, uri) {
259
+ const doc = await this.executor.collection(meta.collectionName).findOne({
260
+ uri,
261
+ });
262
+ if (!doc)
263
+ return undefined;
264
+ if (meta.fields.length === 0)
265
+ return {};
266
+ return adaptDocForRead(meta, doc);
267
+ }
268
+ /**
269
+ * Compose the Mongo filter for shallow-direct-leaves under
270
+ * `prefixUri`, optionally tightened by a glob `pattern` (regex body
271
+ * spliced in place of `[^/]+`) and a `cursor` (`$gt` or `$lt` on
272
+ * `uri` matching the active sort order).
273
+ */
274
+ _leafFilter(prefixUri, pattern, cursor, sortOrder) {
275
+ const body = pattern !== undefined ? saveGlobToRegexBody(pattern) : "[^/]+";
276
+ const uriFilter = {
277
+ $regex: `^${escapeRegex(prefixUri)}${body}$`,
278
+ };
279
+ if (cursor !== undefined) {
280
+ uriFilter[sortOrder === "desc" ? "$lt" : "$gt"] = cursor;
281
+ }
282
+ return { uri: uriFilter };
283
+ }
284
+ async _ls(meta, parsed) {
285
+ validateReadParams(parsed.params, STORE_NAME);
286
+ const { params } = parsed;
287
+ const format = params.format ?? "full";
288
+ const options = {};
289
+ // sortBy: "uri" sorts on the implicit URI key; any other value
290
+ // must be a declared field. Dispatch only routes non-uri sortBy
291
+ // through here when pushDownSortBy is true, which it is for this
292
+ // backend; we still gate on meta.declared so an unrecognised
293
+ // field falls through to no sort rather than a Mongo error.
294
+ if (params.sortBy === "uri") {
295
+ options.sort = { uri: params.sortOrder === "desc" ? -1 : 1 };
296
+ }
297
+ else if (params.sortBy !== undefined && meta.declared.has(params.sortBy)) {
298
+ options.sort = {
299
+ [params.sortBy]: params.sortOrder === "desc" ? -1 : 1,
300
+ };
301
+ }
302
+ if (params.limit !== undefined) {
303
+ const page = params.page ?? 1;
304
+ options.limit = params.limit;
305
+ options.skip = (page - 1) * params.limit;
306
+ }
307
+ if (format === "uris") {
308
+ options.projection = { uri: 1, _id: 0 };
309
+ }
310
+ const docs = await this.executor.collection(meta.collectionName).findMany(this._leafFilter(parsed.uri, params.pattern, params.cursor, params.sortOrder), options);
311
+ if (format === "uris")
312
+ return docs.map((d) => d.uri);
313
+ return docs.map((d) => [
314
+ d.uri,
315
+ adaptDocForRead(meta, d),
316
+ ]);
317
+ }
318
+ /**
319
+ * Compose the Mongo filter for a `find` (deep walk) under
320
+ * `prefixUri`. Same shape as `_leafFilter` — `$regex` anchored on
321
+ * both ends, optional cursor `$gt`/`$lt` — but the body is the glob
322
+ * compiled with `**` → `.*` semantics (crosses `/`). For find we
323
+ * REQUIRE a pattern: parseUrl guarantees `params.pattern` is set
324
+ * whenever fn=find dispatches to this handler (URI-embedded glob or
325
+ * the dual-accept `?pattern=` form). If pattern is absent we throw
326
+ * loudly rather than silently widening to `.*` — that would conflate
327
+ * "find everything" with a parser bug.
328
+ */
329
+ _subtreeFilter(prefixUri, pattern, cursor, sortOrder) {
330
+ if (pattern === undefined) {
331
+ throw new Error(`${STORE_NAME}: fn=find handler reached without params.pattern set ` +
332
+ `(parser invariant violated)`);
333
+ }
334
+ const body = saveGlobToRegexBody(pattern);
335
+ const uriFilter = {
336
+ $regex: `^${escapeRegex(prefixUri)}${body}$`,
337
+ };
338
+ if (cursor !== undefined) {
339
+ uriFilter[sortOrder === "desc" ? "$lt" : "$gt"] = cursor;
340
+ }
341
+ return { uri: uriFilter };
342
+ }
343
+ async _find(meta, parsed) {
344
+ validateReadParams(parsed.params, STORE_NAME);
345
+ const { params } = parsed;
346
+ const format = params.format ?? "full";
347
+ const options = {};
348
+ if (params.sortBy === "uri") {
349
+ options.sort = { uri: params.sortOrder === "desc" ? -1 : 1 };
350
+ }
351
+ else if (params.sortBy !== undefined && meta.declared.has(params.sortBy)) {
352
+ options.sort = {
353
+ [params.sortBy]: params.sortOrder === "desc" ? -1 : 1,
354
+ };
355
+ }
356
+ if (params.limit !== undefined) {
357
+ const page = params.page ?? 1;
358
+ options.limit = params.limit;
359
+ options.skip = (page - 1) * params.limit;
360
+ }
361
+ if (format === "uris") {
362
+ options.projection = { uri: 1, _id: 0 };
363
+ }
364
+ const docs = await this.executor.collection(meta.collectionName).findMany(this._subtreeFilter(parsed.uri, params.pattern, params.cursor, params.sortOrder), options);
365
+ if (format === "uris")
366
+ return docs.map((d) => d.uri);
367
+ return docs.map((d) => [
368
+ d.uri,
369
+ adaptDocForRead(meta, d),
370
+ ]);
371
+ }
372
+ async _count(meta, parsed) {
373
+ return await this.executor.collection(meta.collectionName).countDocuments(this._leafFilter(parsed.uri, parsed.params.pattern, parsed.params.cursor, parsed.params.sortOrder));
374
+ }
375
+ }
376
+ /**
377
+ * Build the document to upsert: `uri` + every declared field, with
378
+ * stream-shaped bytes fields collected up front so a stream failure
379
+ * surfaces as a per-entry write failure (caught by the caller) before
380
+ * any I/O against the collection.
381
+ */
382
+ async function buildDocument(meta, uri, record) {
383
+ const doc = { uri };
384
+ for (const field of meta.fields) {
385
+ const value = record[field.name];
386
+ if (value === undefined)
387
+ continue;
388
+ if (meta.bytesFields.has(field.name)) {
389
+ if (value === null) {
390
+ doc[field.name] = null;
391
+ continue;
392
+ }
393
+ if (value instanceof Uint8Array) {
394
+ doc[field.name] = value;
395
+ continue;
396
+ }
397
+ if (value instanceof ReadableStream) {
398
+ doc[field.name] = await toBytes(value);
399
+ continue;
400
+ }
401
+ throw new Error(`${STORE_NAME}: field '${field.name}' must be Uint8Array or ReadableStream, got ${typeof value}`);
402
+ }
403
+ doc[field.name] = adaptValueForWrite(field, value);
404
+ }
405
+ return doc;
406
+ }
407
+ function adaptValueForWrite(field, value) {
408
+ if (value === null)
409
+ return null;
410
+ if (field.tag === TYPE_TAGS.TIMESTAMP) {
411
+ if (value instanceof Date)
412
+ return value.toISOString();
413
+ if (typeof value === "number")
414
+ return new Date(value).toISOString();
415
+ return value;
416
+ }
417
+ return value;
418
+ }
419
+ function adaptDocForRead(meta, doc) {
420
+ const rec = {};
421
+ for (const field of meta.fields) {
422
+ const v = doc[field.name];
423
+ if (v === undefined || v === null) {
424
+ rec[field.name] = undefined;
425
+ continue;
426
+ }
427
+ if (field.tag === TYPE_TAGS.BYTES) {
428
+ rec[field.name] = docToBytes(v);
429
+ }
430
+ else if (field.tag === TYPE_TAGS.TIMESTAMP) {
431
+ rec[field.name] = v instanceof Date ? v : new Date(v);
432
+ }
433
+ else {
434
+ rec[field.name] = v;
435
+ }
436
+ }
437
+ return rec;
185
438
  }
@@ -27,12 +27,33 @@
27
27
  * predicate (`uri LIKE prefix% AND uri NOT LIKE prefix%/%`) is
28
28
  * enforced in the WHERE clause against the entity's table.
29
29
  *
30
+ * `fn=find` (v2 §3.5) push down to SQL too — same shape minus the
31
+ * `NOT LIKE %/%` shallow clause. With `pushDownFind: true` dispatch
32
+ * trusts the backend to combine the URI-embedded glob with pagination,
33
+ * cursor, and sort inside the query.
34
+ *
35
+ * `fn=count` honours deep (`**`) URLs by dropping the shallow clause
36
+ * when `parsed.glob.includes("**")`. With `pushDownCount: true`,
37
+ * dispatch routes every count shape — shallow and deep — to the
38
+ * backend's own `COUNT(*)` (faster than walking-and-counting through
39
+ * find).
40
+ *
41
+ * ## LIKE-injection safety
42
+ *
43
+ * Every value spliced into a SQL `LIKE` pattern via concatenation is
44
+ * pre-escaped with `escapeSqlLike()` (`%` → `\%`, `_` → `\_`,
45
+ * `\` → `\\`) and the surrounding clauses declare `ESCAPE '\\'`. This
46
+ * prevents a URI prefix or cursor containing `%` or `_` from matching
47
+ * siblings (e.g. `store://r/100%/` must NOT match `store://r/100x/...`
48
+ * under `fn=ls`). The glob body produced by `globToSqlLike` already
49
+ * encodes its own escaping for literal `%`/`_` in the pattern source.
50
+ *
30
51
  * Writes are wrapped in a transaction (`capabilities.atomicBatch =
31
52
  * true`). Stream-shaped values on `BYTEA` columns are collected to
32
53
  * `Uint8Array` *before* the transaction opens, so a stream failure
33
54
  * can't leave a half-applied commit.
34
55
  */
35
- import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js";
56
+ import type { DeleteResult, Output, StatusResult } from "../deps/jsr.io/@bandeira-tech/b3nd-core/0.25.0/src/types/types.js";
36
57
  import type { EntityStore } from "../entity-store.js";
37
58
  import type { EntityMeta, EntityRecord, EntitySchema } from "../entity.js";
38
59
  import type { StoreCapabilities, StoreWriteResult } from "../types.js";
@@ -70,6 +91,34 @@ export declare class PostgresStore implements EntityStore<PostgresEntityMeta> {
70
91
  capabilities(): StoreCapabilities;
71
92
  private _readOne;
72
93
  private _ls;
94
+ /**
95
+ * `fn=find` handler — deep walk under the prefix. Same query shape as
96
+ * `_ls` minus the `NOT LIKE prefix || '%/%'` shallow clause: every
97
+ * descendant URI matches the prefix; the URI-embedded glob (passed in
98
+ * via `params.pattern` by the parser) tightens the set with a SQL
99
+ * `LIKE` body. `pushDownFind: true` is declared so dispatch trusts the
100
+ * backend with pattern + pagination + sort.
101
+ */
102
+ private _find;
103
+ /**
104
+ * Shared SELECT for `_ls` and `_find`. The only structural difference
105
+ * is the shallow clause: `_ls` includes `uri NOT LIKE prefix || '%/%'
106
+ * ESCAPE '\\'`; `_find` drops it. Everything else — pattern body,
107
+ * cursor, sort, pagination — is identical.
108
+ *
109
+ * LIKE-injection safety: `parsed.uri` is pre-escaped with
110
+ * `escapeSqlLike()` before being bound. The bind value is the escaped
111
+ * prefix; the surrounding `LIKE $1 || '%' ESCAPE '\\'` then treats
112
+ * any `%`/`_` in the original prefix as literals.
113
+ */
114
+ private _listQuery;
115
+ /**
116
+ * `fn=count` handler. Honours both shallow (`*`/`?` or no glob) and
117
+ * deep (`**`) URL shapes — the latter drops the `NOT LIKE %/%`
118
+ * predicate so descendants at any depth get counted. With
119
+ * `pushDownCount: true`, dispatch routes every count call through
120
+ * here (no walk-then-length fallback).
121
+ */
73
122
  private _count;
74
123
  }
75
124
  //# sourceMappingURL=store.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/postgres/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,MAAM,EACN,YAAY,EACb,MAAM,mEAAmE,CAAC;AAO3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,cAAc,CAAC;AAEtB,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;AAI5C;;;;;;;;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;AAqDD,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;IAcjB,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;IAOjB;;;;;;;OAOG;YACW,KAAK;IAOnB;;;;;;;;;;OAUG;YACW,UAAU;IAiExB;;;;;;OAMG;YACW,MAAM;CAwBrB"}