@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/fields.ts ADDED
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Field type constructors — the value half of the `schema!` field grammar
3
+ * (`docs/architecture/70-api.md`): `bool`, `u64`, `i64`, `str`, `bytes(n)`,
4
+ * `interval(u64|i64[, width])`, each a plain frozen value carrying its
5
+ * structural type at runtime and its host value type in a phantom generic.
6
+ * Newtypes are DECLARATION-FIRST, one spelling only (owner ruling
7
+ * 2026-07-16): `const AccountId = u64.newtype("AccountId")` declares the
8
+ * brand ONCE as a value that IS the field, paired with
9
+ * `type AccountId = Infer<typeof AccountId>` for signatures; every field
10
+ * position references the declared value (`holder: HolderId`). The macro's
11
+ * refusals are reproduced representationally: `.newtype` exists only where
12
+ * Rust's `as` is legal (u64, i64, bytes, intervals — never bool/str),
13
+ * `.fresh` exists only on declared u64 newtypes (the macro demands `as
14
+ * NewType` on fresh fields), and no field-level constraint vocabulary of
15
+ * any kind exists — `unique`/`fk` are unwritable, not rejected.
16
+ */
17
+
18
+ import * as errors from "@superbuilders/errors"
19
+ import type { Brand, Interval, IntervalValue } from "#brand.ts"
20
+ import { phantom } from "#brand.ts"
21
+ import type { LiteralSpec, ValueTypeSpec } from "#spec.ts"
22
+
23
+ /**
24
+ * The typed shape refusal of the selection-literal machine — reached only
25
+ * through ill-typed input (the well-typed surfaces make it unrepresentable).
26
+ */
27
+ function literalShapeError(expected: string, value: unknown): Error {
28
+ return errors.new(`selection literal shape mismatch: expected ${expected}, got ${typeof value}`)
29
+ }
30
+
31
+ /** Narrows an interval literal: a plain object with bigint start/end. */
32
+ function isIntervalLiteral(value: unknown): value is IntervalValue {
33
+ return (
34
+ typeof value === "object" &&
35
+ value !== null &&
36
+ "start" in value &&
37
+ "end" in value &&
38
+ typeof value.start === "bigint" &&
39
+ typeof value.end === "bigint"
40
+ )
41
+ }
42
+
43
+ /**
44
+ * Resolves one closed-handle literal: the branded id (a bigint at runtime)
45
+ * back to its handle NAME through the roster — an out-of-roster id is a
46
+ * construction error, the belt the type level cannot provide against
47
+ * forged brands.
48
+ */
49
+ function handleLiteral(closed: ClosedRoster, value: unknown): LiteralSpec {
50
+ if (typeof value !== "bigint") {
51
+ throw literalShapeError(`a ${closed.name} handle id (bigint)`, value)
52
+ }
53
+ const handle = closed.handles[Number(value)]
54
+ if (handle === undefined) {
55
+ throw errors.new(
56
+ `closed relation ${closed.name} has no handle with id ${value} (roster holds ${closed.handles.length})`
57
+ )
58
+ }
59
+ return { kind: "handle", handle }
60
+ }
61
+
62
+ /** Lowers one interval literal at its element type. */
63
+ function intervalLiteral(element: "u64" | "i64", value: unknown): LiteralSpec {
64
+ if (!isIntervalLiteral(value)) {
65
+ throw literalShapeError("interval ({ start, end } bigints)", value)
66
+ }
67
+ if (element === "u64") {
68
+ return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
69
+ }
70
+ return { kind: "value", value: { kind: "intervalI64", start: value.start, end: value.end } }
71
+ }
72
+
73
+ /**
74
+ * A closed relation's roster as seen from a referencing field: the handle
75
+ * namespace `where()` selections and ground axioms resolve bare handles
76
+ * through (the macro's own rule: a handle is legal exactly on a field whose
77
+ * newtype is a closed relation's handle newtype).
78
+ */
79
+ interface ClosedRoster {
80
+ readonly name: string
81
+ readonly handles: readonly string[]
82
+ }
83
+
84
+ /**
85
+ * One field's runtime description: structural type, host newtype name, the
86
+ * `fresh` mint mark (`minted` — the property name `fresh` is taken by the
87
+ * builder surface), and the closed-relation reference when the field is a
88
+ * closed relation's id type.
89
+ */
90
+ interface FieldData<Minted extends boolean = boolean> {
91
+ readonly type: ValueTypeSpec
92
+ readonly newtype: string | undefined
93
+ readonly minted: Minted
94
+ readonly closed: ClosedRoster | undefined
95
+ }
96
+
97
+ /**
98
+ * The base shape every field value shares: runtime data plus the phantom
99
+ * host value type `V` (never present at runtime).
100
+ */
101
+ interface Field<V> {
102
+ readonly data: FieldData
103
+ readonly [phantom]?: V
104
+ }
105
+
106
+ /** Extracts a field value's host value type from its phantom. */
107
+ type FieldValue<F> = F extends Field<infer V> ? V : never
108
+
109
+ /** Any field value, whatever its host value type. */
110
+ type AnyField = Field<unknown>
111
+
112
+ /** The `bool` field: host type `boolean`. No `.newtype`, no `.fresh` (macro parity). */
113
+ interface BoolField extends Field<boolean> {
114
+ readonly data: FieldData<false>
115
+ }
116
+
117
+ /** The `str` field: host type `string`. No `.newtype`, no `.fresh` (macro parity). */
118
+ interface StrField extends Field<string> {
119
+ readonly data: FieldData<false>
120
+ }
121
+
122
+ /**
123
+ * A `fresh`-marked u64 newtype field — `id: AccountId.fresh` (Rust: `id:
124
+ * u64 as AccountId, fresh`). Terminal: the mark implies the key
125
+ * `R(field) -> R`, which the ENGINE materializes
126
+ * (`SchemaDescriptor::materialized_statements`); `schema()` rejects an
127
+ * explicit duplicate of it (macro parity).
128
+ */
129
+ interface FreshU64Newtype<Name extends string> extends Field<Brand<bigint, Name>> {
130
+ readonly data: FieldData<true>
131
+ }
132
+
133
+ /**
134
+ * A declared u64 newtype — `const AccountId = u64.newtype("AccountId")`
135
+ * (Rust: `u64 as AccountId`). The value IS the field: relation blocks
136
+ * reference it (`holder: HolderId`). `.fresh` marks it as minted; the
137
+ * property exists ONLY here (the macro demands `as NewType` on fresh
138
+ * fields, so bare-u64 fresh is unwritable).
139
+ */
140
+ interface U64Newtype<Name extends string> extends Field<Brand<bigint, Name>> {
141
+ readonly data: FieldData<false>
142
+ readonly fresh: FreshU64Newtype<Name>
143
+ }
144
+
145
+ /** The bare `u64` field; `.newtype(name)` declares a branded u64 newtype. */
146
+ interface U64Field extends Field<bigint> {
147
+ readonly data: FieldData<false>
148
+ newtype<const Name extends string>(name: Name): U64Newtype<Name>
149
+ }
150
+
151
+ /** A declared i64 newtype. Terminal: `fresh` is legal on u64 only. */
152
+ interface I64Newtype<Name extends string> extends Field<Brand<bigint, Name>> {
153
+ readonly data: FieldData<false>
154
+ }
155
+
156
+ /** The bare `i64` field; `.newtype(name)` declares a branded i64 newtype. */
157
+ interface I64Field extends Field<bigint> {
158
+ readonly data: FieldData<false>
159
+ newtype<const Name extends string>(name: Name): I64Newtype<Name>
160
+ }
161
+
162
+ /** A declared `bytes<N>` newtype (no order derived — no comparators exist). */
163
+ interface BytesNewtype<Name extends string> extends Field<Brand<Uint8Array, Name>> {
164
+ readonly data: FieldData<false>
165
+ }
166
+
167
+ /** A `bytes<N>` field; `.newtype(name)` declares a branded bytes newtype. */
168
+ interface BytesField extends Field<Uint8Array> {
169
+ readonly data: FieldData<false>
170
+ newtype<const Name extends string>(name: Name): BytesNewtype<Name>
171
+ }
172
+
173
+ /** A declared interval newtype — Rust: `interval<i64> as ActiveDuring`. */
174
+ interface IntervalNewtype<Name extends string> extends Field<Interval<Name>> {
175
+ readonly data: FieldData<false>
176
+ }
177
+
178
+ /** An interval field; `.newtype(name)` brands the whole `{ start, end }` object. */
179
+ interface IntervalField extends Field<IntervalValue> {
180
+ readonly data: FieldData<false>
181
+ newtype<const Name extends string>(name: Name): IntervalNewtype<Name>
182
+ }
183
+
184
+ /**
185
+ * The branded value type of a declared newtype — the type half of the
186
+ * declaration-first pairing (owner ruling 2026-07-16): `const AccountId =
187
+ * u64.newtype("AccountId")` + `type AccountId = Infer<typeof AccountId>`.
188
+ * Reads the phantom, so it works on any field value (a closed relation's
189
+ * `id` field infers its handle brand the same way).
190
+ */
191
+ type Infer<F extends AnyField> = F extends Field<infer V> ? V : never
192
+
193
+ /**
194
+ * A closed relation's id field constructor (`Kind.id`) — a u64 field
195
+ * pre-branded with the closed relation's handle newtype, for use in other
196
+ * relations' field blocks (`kind: Kind.id`). Terminal: its newtype IS the
197
+ * closed relation, so `.newtype` and `.fresh` do not exist.
198
+ */
199
+ interface ClosedIdField<Name extends string> extends Field<Brand<bigint, Name>> {
200
+ readonly data: FieldData<false>
201
+ }
202
+
203
+ /**
204
+ * Rejects a declaration name that JavaScript would re-order. Declaration
205
+ * order = ordinal ids is the law relations, columns, and schemas all lean
206
+ * on, and it is carried by object-literal key order — which ECMA-262's
207
+ * OrdinaryOwnPropertyKeys breaks for integer-index keys (they enumerate
208
+ * first, ascending, regardless of where they were written). An
209
+ * integer-index name would silently reorder its declaration, so it is a
210
+ * construction error, exactly as an unparseable name is a macro expansion
211
+ * error.
212
+ */
213
+ function assertDeclarationOrderKey(where: string, name: string): void {
214
+ if (/^(?:0|[1-9][0-9]*)$/.test(name)) {
215
+ throw errors.new(
216
+ `${where}: name ${name} is an integer index — JavaScript object keys re-order integer indices, breaking the declaration-order law; use a non-numeric name`
217
+ )
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Builds one frozen {@link FieldData}. Internal seam shared by the field
223
+ * constructors here and by `closed()` (which mints {@link ClosedIdField}
224
+ * values against its own roster).
225
+ */
226
+ function fieldData<Minted extends boolean>(
227
+ type: ValueTypeSpec,
228
+ newtype: string | undefined,
229
+ minted: Minted,
230
+ closed: ClosedRoster | undefined
231
+ ): FieldData<Minted> {
232
+ return Object.freeze({ type: Object.freeze(type), newtype, minted, closed })
233
+ }
234
+
235
+ /** The one `u64` field constructor value. */
236
+ const u64: U64Field = Object.freeze({
237
+ data: fieldData({ kind: "u64" }, undefined, false, undefined),
238
+ newtype<const Name extends string>(name: Name): U64Newtype<Name> {
239
+ const fresh: FreshU64Newtype<Name> = Object.freeze({
240
+ data: fieldData({ kind: "u64" }, name, true, undefined)
241
+ })
242
+ return Object.freeze({
243
+ data: fieldData({ kind: "u64" }, name, false, undefined),
244
+ fresh
245
+ })
246
+ }
247
+ })
248
+
249
+ /** The one `i64` field constructor value. */
250
+ const i64: I64Field = Object.freeze({
251
+ data: fieldData({ kind: "i64" }, undefined, false, undefined),
252
+ newtype<const Name extends string>(name: Name): I64Newtype<Name> {
253
+ return Object.freeze({ data: fieldData({ kind: "i64" }, name, false, undefined) })
254
+ }
255
+ })
256
+
257
+ /** The one `bool` field constructor value. */
258
+ const bool: BoolField = Object.freeze({
259
+ data: fieldData({ kind: "bool" }, undefined, false, undefined)
260
+ })
261
+
262
+ /** The one `str` field constructor value. */
263
+ const str: StrField = Object.freeze({
264
+ data: fieldData({ kind: "string" }, undefined, false, undefined)
265
+ })
266
+
267
+ /**
268
+ * The `bytes<N>` field constructor. The width is mandatory and part of the
269
+ * type; `len` is validated to 1..=64 here because the grammar pins that
270
+ * range at declaration (`docs/architecture/70-api.md` § the `schema!`
271
+ * grammar: N ∈ 1..=64 — bare `bytes` does not parse), the macro-expansion
272
+ * boundary's analog being construction.
273
+ */
274
+ function bytes(len: number): BytesField {
275
+ if (!Number.isInteger(len) || len < 1 || len > 64) {
276
+ throw errors.new(
277
+ `bytes width must be an integer in 1..=64 (got ${len}) — docs/architecture/70-api.md pins the range at declaration`
278
+ )
279
+ }
280
+ return Object.freeze({
281
+ data: fieldData({ kind: "fixedBytes", len }, undefined, false, undefined),
282
+ newtype<const Name extends string>(name: Name): BytesNewtype<Name> {
283
+ return Object.freeze({ data: fieldData({ kind: "fixedBytes", len }, name, false, undefined) })
284
+ }
285
+ })
286
+ }
287
+
288
+ /**
289
+ * The interval field constructor — `interval(u64)` / `interval(i64)` for
290
+ * the general type (rays representable), `interval(u64, w)` for the
291
+ * fixed-width family whose width IS the type. The element is spelled with
292
+ * the u64/i64 field constructor values themselves, never a string. `width
293
+ * >= 1` is validated here because the grammar pins it at declaration
294
+ * (`docs/architecture/70-api.md`: w ≥ 1; `interval<u64, 0>` is an
295
+ * expansion error naming the field).
296
+ */
297
+ function interval(element: U64Field | I64Field, width?: bigint): IntervalField {
298
+ const elementKind = element.data.type.kind
299
+ if (elementKind !== "u64" && elementKind !== "i64") {
300
+ throw errors.new(`interval element must be the u64 or i64 field constructor (got ${elementKind})`)
301
+ }
302
+ if (width !== undefined && width < 1n) {
303
+ throw errors.new(
304
+ `interval width must be >= 1 (got ${width}) — docs/architecture/70-api.md pins w >= 1 at declaration`
305
+ )
306
+ }
307
+ const type: ValueTypeSpec = { kind: "interval", element: elementKind, width }
308
+ return Object.freeze({
309
+ data: fieldData(type, undefined, false, undefined),
310
+ newtype<const Name extends string>(name: Name): IntervalNewtype<Name> {
311
+ return Object.freeze({ data: fieldData(type, name, false, undefined) })
312
+ }
313
+ })
314
+ }
315
+
316
+ /**
317
+ * Lowers one host literal at its field position to the wire
318
+ * {@link LiteralSpec} — the selection-literal machine ground axioms and
319
+ * `where()` bindings both ride (one machine, same errors — the macro's own
320
+ * rule). A value on a closed-reference field resolves to its handle NAME
321
+ * (the id is re-verified against the roster: an out-of-roster id is a
322
+ * construction error); everything else lowers to a plain value tagged by
323
+ * the field's structural type.
324
+ */
325
+ function literalOf(field: FieldData, value: unknown): LiteralSpec {
326
+ if (field.closed !== undefined) {
327
+ return handleLiteral(field.closed, value)
328
+ }
329
+ switch (field.type.kind) {
330
+ case "bool": {
331
+ if (typeof value !== "boolean") {
332
+ throw literalShapeError("boolean", value)
333
+ }
334
+ return { kind: "value", value: { kind: "bool", value } }
335
+ }
336
+ case "u64": {
337
+ if (typeof value !== "bigint") {
338
+ throw literalShapeError("bigint", value)
339
+ }
340
+ return { kind: "value", value: { kind: "u64", value } }
341
+ }
342
+ case "i64": {
343
+ if (typeof value !== "bigint") {
344
+ throw literalShapeError("bigint", value)
345
+ }
346
+ return { kind: "value", value: { kind: "i64", value } }
347
+ }
348
+ case "string": {
349
+ if (typeof value !== "string") {
350
+ throw literalShapeError("string", value)
351
+ }
352
+ return { kind: "value", value: { kind: "string", value } }
353
+ }
354
+ case "fixedBytes": {
355
+ if (!(value instanceof Uint8Array)) {
356
+ throw literalShapeError("Uint8Array", value)
357
+ }
358
+ return { kind: "value", value: { kind: "fixedBytes", value } }
359
+ }
360
+ case "interval":
361
+ return intervalLiteral(field.type.element, value)
362
+ }
363
+ }
364
+
365
+ export type {
366
+ AnyField,
367
+ BoolField,
368
+ BytesField,
369
+ BytesNewtype,
370
+ ClosedIdField,
371
+ ClosedRoster,
372
+ Field,
373
+ FieldData,
374
+ FieldValue,
375
+ FreshU64Newtype,
376
+ I64Field,
377
+ I64Newtype,
378
+ Infer,
379
+ IntervalField,
380
+ IntervalNewtype,
381
+ StrField,
382
+ U64Field,
383
+ U64Newtype
384
+ }
385
+ export { assertDeclarationOrderKey, bool, bytes, fieldData, i64, interval, literalOf, str, u64 }
package/src/index.ts ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * @bjornpagen/bumbledb — the type-theoretic TypeScript SDK for the
3
+ * bumbledb embedded relational engine. Public surface: the type kernel
4
+ * (brands, fields, `relation()`, `closed()` — PRD-05), the statement
5
+ * algebra with `schema()` and `SchemaSpec` lowering (PRD-06), the `Db`
6
+ * runtime (path-cached stores, transactions, typed violations, scoped
7
+ * snapshot reads, the witnessed write loop with `abandon` — PRD-07, zero
8
+ * closables), the query surface (Datalog as values: scoped vars/params,
9
+ * atoms, negation, conditions, aggregates, engine recursion via predicates,
10
+ * `db.prepare` as a plain value — PRD-08), and the exhume surface
11
+ * (`Db.exhume` — the one schema-independent read path: the store's
12
+ * self-described shapes and raw facts by name, deliberately untyped —
13
+ * course-serialization PRD-02). The raw native bridge is not exported (the
14
+ * PRD-03 stub died here as scheduled).
15
+ */
16
+
17
+ export type { Brand, Interval, IntervalValue } from "#brand.ts"
18
+ export { span } from "#brand.ts"
19
+ export type {
20
+ AnyClosed,
21
+ AxiomRow,
22
+ Axioms,
23
+ Closed,
24
+ ClosedColumn,
25
+ ClosedCore,
26
+ ClosedData,
27
+ ClosedRow,
28
+ PayloadField
29
+ } from "#closed.ts"
30
+ export { closed } from "#closed.ts"
31
+ export type { Count } from "#count.ts"
32
+ export { atLeast, atMost, between, exactly, none } from "#count.ts"
33
+ export type {
34
+ Abandon,
35
+ DeclaredKeyFact,
36
+ DeltaBuild,
37
+ MemberRelation,
38
+ OffendingFact,
39
+ Prepared,
40
+ ReadScope,
41
+ Tx,
42
+ Violation,
43
+ WitnessedWriteResult,
44
+ WriteResult
45
+ } from "#db.ts"
46
+ export { abandon, Db } from "#db.ts"
47
+ export type {
48
+ Exhumed,
49
+ ExhumedAxiom,
50
+ ExhumedDescriptor,
51
+ ExhumedFact,
52
+ ExhumedField,
53
+ ExhumedRelation
54
+ } from "#exhume.ts"
55
+ export {
56
+ ErrExhumeCorruption,
57
+ ErrExhumeFormatMismatch,
58
+ ErrExhumeNoDescriptor
59
+ } from "#exhume.ts"
60
+ export type {
61
+ AnyFace,
62
+ Arity,
63
+ Face,
64
+ FaceArityMismatch,
65
+ FaceData,
66
+ FaceFields,
67
+ FaceOwner,
68
+ FaceSource,
69
+ OneOf,
70
+ SameArity
71
+ } from "#face.ts"
72
+ export { on, oneOf } from "#face.ts"
73
+ export type {
74
+ AnyField,
75
+ BoolField,
76
+ BytesField,
77
+ BytesNewtype,
78
+ ClosedIdField,
79
+ ClosedRoster,
80
+ Field,
81
+ FieldData,
82
+ FieldValue,
83
+ FreshU64Newtype,
84
+ I64Field,
85
+ I64Newtype,
86
+ Infer,
87
+ IntervalField,
88
+ IntervalNewtype,
89
+ StrField,
90
+ U64Field,
91
+ U64Newtype
92
+ } from "#fields.ts"
93
+ export { bool, bytes, i64, interval, str, u64 } from "#fields.ts"
94
+ export { lower, lowerClosed, lowerRelation } from "#lower.ts"
95
+ export type { KeyFact, Minted } from "#marshal.ts"
96
+ export type {
97
+ FactValue,
98
+ OccurrenceDrift,
99
+ ProgramIr,
100
+ Staleness,
101
+ StatementKindTag
102
+ } from "#native.ts"
103
+
104
+ export type {
105
+ AnyBodyItem,
106
+ AnyCondition,
107
+ ComparisonItem,
108
+ ConditionTreeItem,
109
+ Duration,
110
+ MatchAtom,
111
+ MatchInput,
112
+ TermInput
113
+ } from "#query/atom.ts"
114
+
115
+ export {
116
+ ALLEN,
117
+ allen,
118
+ and,
119
+ covers,
120
+ duration,
121
+ ge,
122
+ gt,
123
+ is,
124
+ le,
125
+ lt,
126
+ match,
127
+ ne,
128
+ not,
129
+ or
130
+ } from "#query/atom.ts"
131
+
132
+ export type {
133
+ AnyQuery,
134
+ Query,
135
+ QueryBuild,
136
+ QueryParams,
137
+ QueryRow,
138
+ Scope
139
+ } from "#query/lower.ts"
140
+ export { lowerQuery, query } from "#query/lower.ts"
141
+ export type {
142
+ Predicate,
143
+ PredicateBindings,
144
+ PredicateRuleInput,
145
+ PredicateSelf
146
+ } from "#query/predicate.ts"
147
+ export type { MaskParam, Param, ParamSet, ParamsRecord, Var } from "#query/scope.ts"
148
+ export type { Aggregate, RowOf, SelectShape } from "#query/select.ts"
149
+ export { argmax, argmin, count, countDistinct, max, min, pack, sum } from "#query/select.ts"
150
+ export type {
151
+ AnyRelation,
152
+ AnySelected,
153
+ Fact,
154
+ FieldRef,
155
+ FieldRefs,
156
+ FieldsShape,
157
+ FreshKeys,
158
+ InsertFact,
159
+ Relation,
160
+ RelationData,
161
+ RelationField,
162
+ RelationFields,
163
+ Selected,
164
+ SelectionBinding,
165
+ SelectionInput
166
+ } from "#relation.ts"
167
+ export { relation } from "#relation.ts"
168
+ export type { AnySchema, Schema, SchemaRelation, SchemaRelations } from "#schema.ts"
169
+ export { schema } from "#schema.ts"
170
+ export type {
171
+ FieldSpec,
172
+ LiteralSetSpec,
173
+ LiteralSpec,
174
+ RelationSpec,
175
+ RowSpec,
176
+ SchemaSpec,
177
+ SideSpec,
178
+ StatementSpec,
179
+ ValueSpec,
180
+ ValueTypeSpec,
181
+ WindowSpec
182
+ } from "#spec.ts"
183
+ export { renderLiteral, renderLiteralSet, renderWindow } from "#spec.ts"
184
+ export type { KeyStatement, Statement, StatementData } from "#statements.ts"
185
+ export { contained, key, mirrors, renderStatement, window } from "#statements.ts"
package/src/lower.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Descriptor lowering: SDK values down to the PRD-01 `SchemaSpec` plain
3
+ * data (`#spec.ts`), which the napi bridge marshals verbatim. Lowering is
4
+ * TOTAL on well-typed inputs — no validation lives here beyond what the
5
+ * types and the construction boundaries already guarantee — and it is the
6
+ * only place statement internals are read for the wire. Ordering is
7
+ * declaration order throughout, and every output object is built with one
8
+ * fixed key order, so serialization is deterministic (byte-stable).
9
+ */
10
+
11
+ import type { AnyClosed } from "#closed.ts"
12
+ import type { FaceData } from "#face.ts"
13
+ import type { AnyRelation } from "#relation.ts"
14
+ import type { AnySchema, SchemaRelation } from "#schema.ts"
15
+ import type { FieldSpec, RelationSpec, SchemaSpec, SideSpec, StatementSpec } from "#spec.ts"
16
+ import type { Statement } from "#statements.ts"
17
+
18
+ /**
19
+ * The relation-kind discriminant: a closed relation's runtime description
20
+ * carries its handle roster, an ordinary relation's never does.
21
+ */
22
+ function isClosedMember(member: SchemaRelation): member is AnyClosed {
23
+ return "handles" in member.data
24
+ }
25
+
26
+ /** Lowers one face to a `SideSpec`: names only, σ as (field, set) pairs. */
27
+ function lowerFace(face: FaceData): SideSpec {
28
+ return {
29
+ relation: face.owner.name,
30
+ projection: [...face.projection],
31
+ selection: face.selection.map(function lowerBinding(binding) {
32
+ return [binding.field, binding.set] as const
33
+ })
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Lowers one statement. `mirrors` stays ONE spec statement
39
+ * (`bidirectional: true`) — the engine performs the `==` lowering to two
40
+ * adjacent containments, `source <= target` first, exactly as the macro
41
+ * does.
42
+ */
43
+ function lowerStatement(statement: Statement): StatementSpec {
44
+ const data = statement.data
45
+ switch (data.kind) {
46
+ case "key":
47
+ return { kind: "fd", relation: data.owner.name, projection: [...data.projection] }
48
+ case "containment":
49
+ return {
50
+ kind: "containment",
51
+ source: lowerFace(data.source),
52
+ target: lowerFace(data.target),
53
+ bidirectional: data.bidirectional
54
+ }
55
+ case "window":
56
+ return {
57
+ kind: "cardinality",
58
+ target: lowerFace(data.target),
59
+ window: data.window,
60
+ source: lowerFace(data.source)
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Lowers one ordinary relation to its `RelationSpec` fragment: fields in
67
+ * declaration order, `extension: undefined` (the option is the kind).
68
+ */
69
+ function lowerRelation(relation: AnyRelation): RelationSpec {
70
+ const fields: FieldSpec[] = relation.data.fields.map(function lowerField(declared) {
71
+ return {
72
+ name: declared.name,
73
+ valueType: declared.field.type,
74
+ newtype: declared.field.newtype,
75
+ fresh: declared.field.minted
76
+ }
77
+ })
78
+ return { name: relation.name, newtype: undefined, fields, extension: undefined }
79
+ }
80
+
81
+ /**
82
+ * Lowers one closed relation to its `RelationSpec` fragment: declared
83
+ * intrinsic columns only (the engine materializes the synthetic `id`),
84
+ * the relation's own name as its handle newtype, and the ground axioms in
85
+ * declaration order (row id = index) — the literals were already lowered
86
+ * at `closed()` construction.
87
+ */
88
+ function lowerClosed(member: AnyClosed): RelationSpec {
89
+ const fields: FieldSpec[] = member.data.columns.map(function lowerColumn(column) {
90
+ return {
91
+ name: column.name,
92
+ valueType: column.field.type,
93
+ newtype: column.field.newtype,
94
+ fresh: false
95
+ }
96
+ })
97
+ const extension = member.data.rows.map(function lowerRow(row) {
98
+ return { handle: row.handle, values: row.values }
99
+ })
100
+ return { name: member.name, newtype: member.name, fields, extension }
101
+ }
102
+
103
+ /**
104
+ * Lowers a whole theory to the `SchemaSpec` the bridge takes: relations in
105
+ * record declaration order, DECLARED statements only in written order (the
106
+ * engine materializes the fresh-implied and closed auto-keys itself).
107
+ */
108
+ function lower(theory: AnySchema): SchemaSpec {
109
+ const relations: RelationSpec[] = Object.values(theory.relations).map(function lowerMember(member) {
110
+ if (isClosedMember(member)) {
111
+ return lowerClosed(member)
112
+ }
113
+ return lowerRelation(member)
114
+ })
115
+ return { relations, statements: theory.statements.map(lowerStatement) }
116
+ }
117
+
118
+ export { lower, lowerClosed, lowerRelation }