@bjornpagen/bumbledb 0.1.0

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 (108) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +84 -0
  3. package/dist/brand.d.ts +59 -0
  4. package/dist/brand.d.ts.map +1 -0
  5. package/dist/brand.js +47 -0
  6. package/dist/brand.js.map +1 -0
  7. package/dist/closed.d.ts +97 -0
  8. package/dist/closed.d.ts.map +1 -0
  9. package/dist/closed.js +107 -0
  10. package/dist/closed.js.map +1 -0
  11. package/dist/count.d.ts +55 -0
  12. package/dist/count.d.ts.map +1 -0
  13. package/dist/count.js +92 -0
  14. package/dist/count.js.map +1 -0
  15. package/dist/db.d.ts +341 -0
  16. package/dist/db.d.ts.map +1 -0
  17. package/dist/db.js +1016 -0
  18. package/dist/db.js.map +1 -0
  19. package/dist/exhume.d.ts +130 -0
  20. package/dist/exhume.d.ts.map +1 -0
  21. package/dist/exhume.js +145 -0
  22. package/dist/exhume.js.map +1 -0
  23. package/dist/face.d.ts +91 -0
  24. package/dist/face.d.ts.map +1 -0
  25. package/dist/face.js +69 -0
  26. package/dist/face.js.map +1 -0
  27. package/dist/fields.d.ts +187 -0
  28. package/dist/fields.d.ts.map +1 -0
  29. package/dist/fields.js +204 -0
  30. package/dist/fields.js.map +1 -0
  31. package/dist/index.d.ts +49 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +31 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lower.d.ts +34 -0
  36. package/dist/lower.d.ts.map +1 -0
  37. package/dist/lower.js +105 -0
  38. package/dist/lower.js.map +1 -0
  39. package/dist/marshal.d.ts +79 -0
  40. package/dist/marshal.d.ts.map +1 -0
  41. package/dist/marshal.js +167 -0
  42. package/dist/marshal.js.map +1 -0
  43. package/dist/native.d.ts +532 -0
  44. package/dist/native.d.ts.map +1 -0
  45. package/dist/native.js +64 -0
  46. package/dist/native.js.map +1 -0
  47. package/dist/query/atom.d.ts +285 -0
  48. package/dist/query/atom.d.ts.map +1 -0
  49. package/dist/query/atom.js +281 -0
  50. package/dist/query/atom.js.map +1 -0
  51. package/dist/query/lower.d.ts +145 -0
  52. package/dist/query/lower.d.ts.map +1 -0
  53. package/dist/query/lower.js +604 -0
  54. package/dist/query/lower.js.map +1 -0
  55. package/dist/query/predicate.d.ts +101 -0
  56. package/dist/query/predicate.d.ts.map +1 -0
  57. package/dist/query/predicate.js +85 -0
  58. package/dist/query/predicate.js.map +1 -0
  59. package/dist/query/run.d.ts +30 -0
  60. package/dist/query/run.d.ts.map +1 -0
  61. package/dist/query/run.js +94 -0
  62. package/dist/query/run.js.map +1 -0
  63. package/dist/query/scope.d.ts +168 -0
  64. package/dist/query/scope.d.ts.map +1 -0
  65. package/dist/query/scope.js +134 -0
  66. package/dist/query/scope.js.map +1 -0
  67. package/dist/query/select.d.ts +106 -0
  68. package/dist/query/select.d.ts.map +1 -0
  69. package/dist/query/select.js +69 -0
  70. package/dist/query/select.js.map +1 -0
  71. package/dist/relation.d.ts +120 -0
  72. package/dist/relation.d.ts.map +1 -0
  73. package/dist/relation.js +108 -0
  74. package/dist/relation.js.map +1 -0
  75. package/dist/schema.d.ts +50 -0
  76. package/dist/schema.d.ts.map +1 -0
  77. package/dist/schema.js +235 -0
  78. package/dist/schema.js.map +1 -0
  79. package/dist/spec.d.ts +218 -0
  80. package/dist/spec.d.ts.map +1 -0
  81. package/dist/spec.js +154 -0
  82. package/dist/spec.js.map +1 -0
  83. package/dist/statements.d.ts +91 -0
  84. package/dist/statements.d.ts.map +1 -0
  85. package/dist/statements.js +101 -0
  86. package/dist/statements.js.map +1 -0
  87. package/package.json +66 -0
  88. package/src/brand.ts +82 -0
  89. package/src/closed.ts +228 -0
  90. package/src/count.ts +117 -0
  91. package/src/db.ts +1519 -0
  92. package/src/exhume.ts +243 -0
  93. package/src/face.ts +161 -0
  94. package/src/fields.ts +385 -0
  95. package/src/index.ts +185 -0
  96. package/src/lower.ts +118 -0
  97. package/src/marshal.ts +220 -0
  98. package/src/native.ts +576 -0
  99. package/src/query/atom.ts +556 -0
  100. package/src/query/lower.ts +855 -0
  101. package/src/query/predicate.ts +195 -0
  102. package/src/query/run.ts +106 -0
  103. package/src/query/scope.ts +301 -0
  104. package/src/query/select.ts +140 -0
  105. package/src/relation.ts +252 -0
  106. package/src/schema.ts +297 -0
  107. package/src/spec.ts +325 -0
  108. package/src/statements.ts +148 -0
package/src/marshal.ts ADDED
@@ -0,0 +1,220 @@
1
+ /**
2
+ * The marshal layer (PRD-07): fact object ⇄ positional `FactValue[]` by
3
+ * field ordinal, schema-directed, in ONE place only. The write side lowers
4
+ * named host objects to rows in the relation's field-declaration order
5
+ * (declaration order = ordinal ids, the macro's law); the read side decodes
6
+ * rows back to named objects with brands applied by assertion — the store
7
+ * is the proof carrier: a row the engine admitted IS a legal fact of its
8
+ * relation, so readback asserts the brand instead of re-judging the value
9
+ * (the same trust direction as Rust's typed readback). Shape mismatches
10
+ * here are genuine failures and THROW typed; they are never domain data.
11
+ */
12
+
13
+ import * as errors from "@superbuilders/errors"
14
+ import type { FieldData } from "#fields.ts"
15
+ import type { FactValue } from "#native.ts"
16
+ import type { AnyRelation, Fact, FreshKeys, RelationData } from "#relation.ts"
17
+
18
+ /**
19
+ * The inferred object type `tx.insert` returns: one property per
20
+ * fresh-marked field of `R`, carrying the minted (or resupplied) branded
21
+ * id. A relation with no fresh field returns the empty object.
22
+ */
23
+ type Minted<R extends AnyRelation> = { [K in FreshKeys<R>]: Fact<R>[K] }
24
+
25
+ /**
26
+ * The key object `get` reads through. THE PRIMARY-KEY RULE: `get` always
27
+ * reads through the PRIMARY candidate key — the first-declared one in the
28
+ * engine's materialized statement order (fresh-implied keys first, closed
29
+ * auto-keys second, declared `key()` statements last), so a fresh-bearing
30
+ * relation's primary key is always its fresh field. When `R` carries a
31
+ * fresh field the type demands exactly that field; otherwise the primary
32
+ * key lives in the schema's statement list, which the type system cannot
33
+ * see (statements are untyped values), so the type admits any partial fact
34
+ * and the projection is verified at runtime — a missing key field throws
35
+ * naming the projection.
36
+ */
37
+ type KeyFact<R extends AnyRelation> = [FreshKeys<R>] extends [never]
38
+ ? Partial<Fact<R>>
39
+ : { [K in FreshKeys<R>]: Fact<R>[K] }
40
+
41
+ /** The typed shape refusal of the row marshaler — a genuine failure, never data. */
42
+ function cellShapeError(context: string, expected: string, value: unknown): Error {
43
+ return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
44
+ }
45
+
46
+ /** Narrows an interval cell: a plain object with bigint start/end. */
47
+ function isIntervalCell(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
48
+ return (
49
+ typeof value === "object" &&
50
+ value !== null &&
51
+ "start" in value &&
52
+ "end" in value &&
53
+ typeof value.start === "bigint" &&
54
+ typeof value.end === "bigint"
55
+ )
56
+ }
57
+
58
+ /**
59
+ * Reprojects any host object to a string-indexed record — the boundary
60
+ * through which generic fact objects (whose type parameters carry no index
61
+ * signature) enter the name-directed marshaling below, without a cast.
62
+ */
63
+ function recordOf(fact: object): Record<string, unknown> {
64
+ return Object.fromEntries(Object.entries(fact))
65
+ }
66
+
67
+ /**
68
+ * Marshals one host cell at its field position to the natural wire value,
69
+ * schema-directed by the field's structural type (never guessed). Brands
70
+ * are phantoms, so the runtime values are exactly the wire's natural JS
71
+ * values; only widths and domains are left to the engine's own judge.
72
+ */
73
+ function cellOf(context: string, field: FieldData, value: unknown): FactValue {
74
+ switch (field.type.kind) {
75
+ case "bool": {
76
+ if (typeof value !== "boolean") {
77
+ throw cellShapeError(context, "boolean", value)
78
+ }
79
+ return value
80
+ }
81
+ case "u64":
82
+ case "i64": {
83
+ if (typeof value !== "bigint") {
84
+ throw cellShapeError(context, "bigint", value)
85
+ }
86
+ return value
87
+ }
88
+ case "string": {
89
+ if (typeof value !== "string") {
90
+ throw cellShapeError(context, "string", value)
91
+ }
92
+ /**
93
+ * A lone surrogate would be lossily replaced with U+FFFD at the
94
+ * bridge's UTF-8 crossing — the stored fact would differ from the
95
+ * written one, and distinct JS strings would collapse to one fact.
96
+ * The bijection law refuses it here, the one seam every write and
97
+ * lookup lowers through.
98
+ */
99
+ if (!value.isWellFormed()) {
100
+ throw cellShapeError(context, "well-formed string", value)
101
+ }
102
+ return value
103
+ }
104
+ case "fixedBytes": {
105
+ if (!(value instanceof Uint8Array)) {
106
+ throw cellShapeError(context, "Uint8Array", value)
107
+ }
108
+ return value
109
+ }
110
+ case "interval": {
111
+ if (!isIntervalCell(value)) {
112
+ throw cellShapeError(context, "interval ({ start, end } bigints)", value)
113
+ }
114
+ return { start: value.start, end: value.end }
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Marshals one complete fact object to its positional row, in field
121
+ * declaration order (= ordinal ids). Every declared field must be present;
122
+ * fresh minting happens BEFORE this point (the transaction fills omitted
123
+ * fresh cells via the engine's alloc lane).
124
+ */
125
+ function rowOf(relation: RelationData, fact: Readonly<Record<string, unknown>>): FactValue[] {
126
+ return relation.fields.map(function marshalCell(declared) {
127
+ const value = fact[declared.name]
128
+ if (value === undefined) {
129
+ throw errors.new(`relation ${relation.name}: fact is missing field ${declared.name}`)
130
+ }
131
+ return cellOf(`relation ${relation.name} field ${declared.name}`, declared.field, value)
132
+ })
133
+ }
134
+
135
+ /**
136
+ * Marshals a key object through a key statement's projection, in the
137
+ * statement's projection order (what the engine's keyed point reads take).
138
+ * A key field absent from the object throws naming the primary projection
139
+ * (the {@link KeyFact} rule's runtime half).
140
+ */
141
+ function keyRowOf(
142
+ relation: RelationData,
143
+ projection: readonly string[],
144
+ key: Readonly<Record<string, unknown>>
145
+ ): FactValue[] {
146
+ return projection.map(function marshalKeyCell(fieldName) {
147
+ const declared = relation.fields.find(function byName(candidate) {
148
+ return candidate.name === fieldName
149
+ })
150
+ if (declared === undefined) {
151
+ throw errors.new(`relation ${relation.name}: key projection cites unknown field ${fieldName}`)
152
+ }
153
+ const value = key[fieldName]
154
+ if (value === undefined) {
155
+ throw errors.new(
156
+ `relation ${relation.name}: key object is missing field ${fieldName} — get reads through the primary (first-declared) key, whose projection is (${projection.join(", ")})`
157
+ )
158
+ }
159
+ return cellOf(`relation ${relation.name} key field ${fieldName}`, declared.field, value)
160
+ })
161
+ }
162
+
163
+ /**
164
+ * The read-side trusted seam: a decoded row carrying every declared field
165
+ * IS a fact of its relation — the engine admitted it, so the brand is
166
+ * asserted, not re-derived. The checkable half (completeness) is verified;
167
+ * the phantom half (brands) is carried by the store's own judgment.
168
+ */
169
+ function isBrandedFact<R extends AnyRelation>(
170
+ relation: R,
171
+ decoded: Readonly<Record<string, FactValue>>
172
+ ): decoded is Readonly<Record<string, FactValue>> & Fact<R> {
173
+ return relation.data.fields.every(function present(declared) {
174
+ return decoded[declared.name] !== undefined
175
+ })
176
+ }
177
+
178
+ /**
179
+ * The insert-return trusted seam: the collected fresh cells of one insert
180
+ * (minted by the engine or resupplied by the caller) are the relation's
181
+ * branded fresh ids — same assertion direction as {@link isBrandedFact}.
182
+ */
183
+ function isMintedFresh<R extends AnyRelation>(
184
+ relation: R,
185
+ minted: Readonly<Record<string, FactValue>>
186
+ ): minted is Readonly<Record<string, FactValue>> & Minted<R> {
187
+ return relation.data.fields.every(function presentWhenFresh(declared) {
188
+ return !declared.field.minted || minted[declared.name] !== undefined
189
+ })
190
+ }
191
+
192
+ /**
193
+ * Unmarshals one positional row to the relation's named, branded, frozen
194
+ * fact object — the inverse of {@link rowOf}, ordinal-directed by the same
195
+ * declaration order.
196
+ */
197
+ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]): Fact<R> {
198
+ const data = relation.data
199
+ if (row.length !== data.fields.length) {
200
+ throw errors.new(
201
+ `relation ${data.name}: row arity ${row.length} does not match the ${data.fields.length} declared fields`
202
+ )
203
+ }
204
+ const decoded: Record<string, FactValue> = {}
205
+ data.fields.forEach(function decodeCell(declared, ordinal) {
206
+ const cell = row[ordinal]
207
+ if (cell === undefined) {
208
+ throw errors.new(`relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`)
209
+ }
210
+ decoded[declared.name] = cell
211
+ })
212
+ Object.freeze(decoded)
213
+ if (!isBrandedFact(relation, decoded)) {
214
+ throw errors.new(`relation ${data.name}: decoded row is not a complete fact`)
215
+ }
216
+ return decoded
217
+ }
218
+
219
+ export type { KeyFact, Minted }
220
+ export { cellOf, factOf, isMintedFresh, keyRowOf, recordOf, rowOf }