@bjornpagen/bumbledb 0.1.0 → 0.2.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 (103) hide show
  1. package/COOKBOOK.md +1394 -0
  2. package/README.md +4 -0
  3. package/dist/closed.d.ts +60 -37
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +159 -40
  6. package/dist/closed.js.map +1 -1
  7. package/dist/count.d.ts +64 -17
  8. package/dist/count.d.ts.map +1 -1
  9. package/dist/count.js +37 -14
  10. package/dist/count.js.map +1 -1
  11. package/dist/db.d.ts +15 -7
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +13 -5
  14. package/dist/db.js.map +1 -1
  15. package/dist/exhume.d.ts +12 -11
  16. package/dist/exhume.d.ts.map +1 -1
  17. package/dist/exhume.js +7 -6
  18. package/dist/exhume.js.map +1 -1
  19. package/dist/face.d.ts +79 -19
  20. package/dist/face.d.ts.map +1 -1
  21. package/dist/face.js +14 -15
  22. package/dist/face.js.map +1 -1
  23. package/dist/fields.d.ts +158 -129
  24. package/dist/fields.d.ts.map +1 -1
  25. package/dist/fields.js +80 -81
  26. package/dist/fields.js.map +1 -1
  27. package/dist/index.d.ts +21 -21
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +13 -12
  30. package/dist/index.js.map +1 -1
  31. package/dist/lower.d.ts +6 -3
  32. package/dist/lower.d.ts.map +1 -1
  33. package/dist/lower.js +48 -17
  34. package/dist/lower.js.map +1 -1
  35. package/dist/marshal.d.ts +36 -21
  36. package/dist/marshal.d.ts.map +1 -1
  37. package/dist/marshal.js +41 -25
  38. package/dist/marshal.js.map +1 -1
  39. package/dist/native.js +2 -2
  40. package/dist/query/atom.d.ts +332 -174
  41. package/dist/query/atom.d.ts.map +1 -1
  42. package/dist/query/atom.js +72 -172
  43. package/dist/query/atom.js.map +1 -1
  44. package/dist/query/lower.d.ts +295 -107
  45. package/dist/query/lower.d.ts.map +1 -1
  46. package/dist/query/lower.js +860 -388
  47. package/dist/query/lower.js.map +1 -1
  48. package/dist/query/predicate.d.ts +69 -88
  49. package/dist/query/predicate.d.ts.map +1 -1
  50. package/dist/query/predicate.js +118 -72
  51. package/dist/query/predicate.js.map +1 -1
  52. package/dist/query/run.d.ts +19 -16
  53. package/dist/query/run.d.ts.map +1 -1
  54. package/dist/query/run.js +31 -24
  55. package/dist/query/run.js.map +1 -1
  56. package/dist/query/scope.d.ts +139 -123
  57. package/dist/query/scope.d.ts.map +1 -1
  58. package/dist/query/scope.js +71 -115
  59. package/dist/query/scope.js.map +1 -1
  60. package/dist/query/select.d.ts +102 -80
  61. package/dist/query/select.d.ts.map +1 -1
  62. package/dist/query/select.js +39 -34
  63. package/dist/query/select.js.map +1 -1
  64. package/dist/relation.d.ts +33 -36
  65. package/dist/relation.d.ts.map +1 -1
  66. package/dist/relation.js +15 -16
  67. package/dist/relation.js.map +1 -1
  68. package/dist/schema.d.ts.map +1 -1
  69. package/dist/schema.js +19 -8
  70. package/dist/schema.js.map +1 -1
  71. package/dist/spec.d.ts +6 -5
  72. package/dist/spec.d.ts.map +1 -1
  73. package/dist/spec.js.map +1 -1
  74. package/dist/statements.d.ts +56 -31
  75. package/dist/statements.d.ts.map +1 -1
  76. package/dist/statements.js +38 -13
  77. package/dist/statements.js.map +1 -1
  78. package/package.json +4 -3
  79. package/src/closed.ts +271 -93
  80. package/src/count.ts +112 -18
  81. package/src/db.ts +22 -12
  82. package/src/exhume.ts +12 -11
  83. package/src/face.ts +114 -22
  84. package/src/fields.ts +261 -217
  85. package/src/index.ts +50 -59
  86. package/src/lower.ts +62 -20
  87. package/src/marshal.ts +48 -30
  88. package/src/native.ts +2 -2
  89. package/src/query/atom.ts +480 -376
  90. package/src/query/lower.ts +1341 -542
  91. package/src/query/predicate.ts +198 -161
  92. package/src/query/run.ts +35 -25
  93. package/src/query/scope.ts +188 -218
  94. package/src/query/select.ts +168 -93
  95. package/src/relation.ts +38 -44
  96. package/src/schema.ts +22 -12
  97. package/src/spec.ts +6 -5
  98. package/src/statements.ts +69 -26
  99. package/dist/brand.d.ts +0 -59
  100. package/dist/brand.d.ts.map +0 -1
  101. package/dist/brand.js +0 -47
  102. package/dist/brand.js.map +0 -1
  103. package/src/brand.ts +0 -82
@@ -1,195 +1,232 @@
1
1
  /**
2
- * IDB predicates (PRD-08) — engine recursion as values, mirroring the IR's
2
+ * Stratified recursion — engine recursion as values, mirroring the IR's
3
3
  * cut exactly (`ir::Program`/`PredicateDef`/`AtomSource::Idb`;
4
- * `docs/architecture/20-query-ir.md` § engine recursion): `$.predicate`
5
- * declares a predicate with a named, field-typed head; its rules are given
6
- * IN the declaration (a callback receiving the predicate's own reference,
7
- * so self-recursion is writable and the rules are data the type system can
8
- * see the params they use flow into the query's inferred `Params`); and
9
- * `.match({...})` uses it as a body atom, bindings addressing head
10
- * positions by the declared column names (lowered positionally —
11
- * `FieldId(i)` is head position i, exactly as the IR reads it). Strata
12
- * legality (no negation/aggregation through a cycle) is the ENGINE's
13
- * judge; its typed error surfaces at prepare.
4
+ * `docs/architecture/20-query-ir.md` § engine recursion):
5
+ *
6
+ * program(S, (p) => {
7
+ * const reach = p.rec("reach")
8
+ * reach.rule((r) => r.match(Node, { id: r.var("c") })
9
+ * .where(r.eq(r.var("c"), r.param("root"))).select("c"))
10
+ * reach.rule((r) => r.match(Parent, { child: r.var("c"), parent: r.var("m") })
11
+ * .idb(reach, r.var("m")).select("c"))
12
+ * return p.output((r) => r.match(Posting, { account: r.var("a"), minor: r.var("m") })
13
+ * .idb(reach, r.var("a")).select(r.sum("m")))
14
+ * })
15
+ *
16
+ * `p.rec(name)` declares one recursive predicate (declaration order = its
17
+ * dense `PredId`); `rec.rule(...)` attaches one clause — its builder's
18
+ * `idb` accepts ONLY the rec itself (the self-recursion cut as a
19
+ * type-level boundary: mutual recursion is unwritable) and its head
20
+ * projects bound variable NAMES only (aggregation/measure through a cycle
21
+ * is unrepresentable — the strata judge's roster, made unwritable);
22
+ * `p.output(...)` seals the recs and builds the output rules, whose `idb`
23
+ * folds any FINISHED stratum (recipe 25's form). The rec value `.rule`
24
+ * returns carries the params its rules used — thread it into the output's
25
+ * `idb` and the program's inferred `Params` stays exactly the params the
26
+ * rules use. Everything deeper — strata legality, signature sealing, the
27
+ * three oracles — is the ENGINE's judge, surfacing typed at prepare.
14
28
  */
15
29
 
16
30
  import * as errors from "@superbuilders/errors"
17
- import { phantom } from "#brand.ts"
18
- import { assertDeclarationOrderKey, type FieldData } from "#fields.ts"
19
- import type { AnyBodyItem, BindingsParams, MatchAtom, TermInput } from "#query/atom.ts"
20
- import { resolveBindings } from "#query/atom.ts"
21
- import type { AnyVar, Flatten, ItemParams, ParamsRecord, ParamsShape, QueryRegistry, Var } from "#query/scope.ts"
22
- import { isTerm, resolveFieldData, term } from "#query/scope.ts"
23
- import type { FieldRef } from "#relation.ts"
31
+ import type { RecData } from "#query/atom.ts"
32
+ import type {
33
+ AnyRuleValue,
34
+ HeadOf,
35
+ HeadShape,
36
+ OutputRuleScope,
37
+ ParamsOf,
38
+ ProgramState,
39
+ Query,
40
+ RawScope,
41
+ RecRef,
42
+ RecRuleScope,
43
+ RowOf,
44
+ RuleValue
45
+ } from "#query/lower.ts"
46
+ import { makeOutputRuleScope, makeQuery, makeRawScope } from "#query/lower.ts"
47
+ import type { Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
48
+ import { inferred } from "#query/scope.ts"
49
+ import type { Schema, SchemaRelations } from "#schema.ts"
24
50
 
25
- /** One declared head column: name plus the field description that types it. */
26
- interface PredicateColumn {
27
- readonly name: string
28
- readonly data: FieldData
29
- }
30
-
31
- /** One clause of a predicate: head projections (column order) and the body. */
32
- interface PredicateRuleData {
33
- readonly finds: readonly AnyVar[]
34
- readonly body: readonly AnyBodyItem[]
35
- }
36
-
37
- /** A predicate's runtime description; identity keys the dense `PredId` at lowering. */
38
- interface PredicateData {
39
- readonly name: string
40
- readonly columns: readonly PredicateColumn[]
41
- readonly rules: readonly PredicateRuleData[]
51
+ /**
52
+ * One recursive predicate HANDLE: `rec.rule(...)` attaches a clause and
53
+ * returns the SAME rec under a widened params type (the runtime data is
54
+ * shared — either handle is the self-reference; the returned one carries
55
+ * the rules' params for the output to thread) and under the head signature
56
+ * the FIRST rule sealed (every later rule derives the same head — the
57
+ * runtime alignment check's law), so a threaded handle's `idb` joins are
58
+ * arity- and domain-checked against the head.
59
+ */
60
+ interface Rec<
61
+ Rels extends SchemaRelations,
62
+ Name extends string,
63
+ P extends ParamsRecord,
64
+ Head extends HeadShape = undefined
65
+ > extends RecRef<Name, P, Head> {
66
+ rule<RV extends AnyRuleValue>(
67
+ build: (r: RecRuleScope<Rels, Name>) => RV
68
+ ): Rec<Rels, Name, Flatten<P & ParamsOf<RV>>, Head extends undefined ? HeadOf<RV> : Head>
69
+ readonly [inferred]?: { readonly params: P; readonly head: Head }
42
70
  }
43
71
 
44
- /** Any field reference, whatever its brand — the column-declaration position. */
45
- type AnyFieldRef = FieldRef<string, string, unknown>
72
+ /** One output-rule builder function. */
73
+ type OutputBuild<Rels extends SchemaRelations> = (r: OutputRuleScope<Rels>) => AnyRuleValue
46
74
 
47
- /** The columns record `$.predicate` takes: column name to typing field reference. */
48
- type PredicateColumnsInput = Readonly<Record<string, AnyFieldRef>>
75
+ /** A build function's rule value. */
76
+ type BuiltRule<F> = F extends (r: never) => infer RV ? RV : never
49
77
 
50
- /** Extracts a field reference's host value type. */
51
- type RefValue<T> = T extends { readonly [phantom]?: infer V } ? Exclude<V, undefined> : never
78
+ /** The union row of a tuple of output builds. */
79
+ type OutputRow<Builds extends readonly OutputBuild<SchemaRelations>[]> = RowOf<BuiltRule<Builds[number]>>
52
80
 
53
- /** The head's typed column record, derived from the declaration. */
54
- type ColumnValues<Cols> = { [K in keyof Cols]: RefValue<Cols[K]> }
81
+ /** The intersected params record of a tuple of output builds. */
82
+ type OutputParams<Builds extends readonly OutputBuild<SchemaRelations>[]> = ShapeOf<ParamsOf<BuiltRule<Builds[number]>>>
55
83
 
56
84
  /**
57
- * The `.match` bindings of a predicate atom: per head column, a term of
58
- * the column's brand; unmentioned columns are wildcards, exactly as
59
- * relation atoms.
85
+ * The program scope: declare recs, attach their rules, then declare the
86
+ * output — which seals the recs (a later `rec`/`rule` is a construction
87
+ * error) and returns the program as an ordinary query value.
60
88
  */
61
- type PredicateBindings<ColsV> = { readonly [K in keyof ColsV]?: TermInput<ColsV[K]> }
89
+ interface ProgramScope<Rels extends SchemaRelations> {
90
+ /** Declares one recursive predicate; declaration order = its dense `PredId`. */
91
+ rec<const Name extends string>(name: Name): Rec<Rels, Name, Record<never, never>>
92
+ /**
93
+ * Declares the output predicate (one rule per build; multiple rules =
94
+ * set union) and seals the program. Must be what the `program()`
95
+ * callback returns.
96
+ */
97
+ output<const Builds extends readonly OutputBuild<Rels>[]>(
98
+ ...builds: Builds
99
+ ): Query<Rels, OutputRow<Builds>, OutputParams<Builds>>
100
+ }
62
101
 
63
- /**
64
- * One rule of a predicate as the declaration callback returns it: `finds`
65
- * names the projected variable per head column (interior heads project
66
- * bound variables only — the creation quarantine; the engine's strata
67
- * judge enforces it), `body` is the clause's conjunction.
68
- */
69
- interface PredicateRuleInput<ColsV> {
70
- readonly finds: { readonly [K in keyof ColsV]: Var<ColsV[K]> }
71
- readonly body: readonly AnyBodyItem[]
102
+ /** The runtime rec-handle shape beneath the typed `Rec` face. */
103
+ interface RawRec<Name extends string> {
104
+ readonly name: Name
105
+ readonly data: RecData
106
+ rule(build: (r: RawScope) => RuleValue<never, never>): RawRec<Name>
72
107
  }
73
108
 
74
- /**
75
- * The predicate's own reference, passed INTO its rules callback — the
76
- * fixpoint spelling: `self.match({...})` inside a rule of the same
77
- * predicate is the recursive atom.
78
- */
79
- interface PredicateSelf<ColsV> {
80
- readonly data: PredicateData
81
- match<const B extends PredicateBindings<ColsV>>(bindings: B): MatchAtom<BindingsParams<B>>
109
+ /** Builds the runtime rec handle over shared rec data. */
110
+ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data: RecData): RawRec<Name> {
111
+ const rec: RawRec<Name> = {
112
+ name,
113
+ data,
114
+ rule(build) {
115
+ if (state.sealed) {
116
+ throw errors.new(
117
+ `rec ${name}: the program's output is already declared — recursive rules attach before p.output`
118
+ )
119
+ }
120
+ const built = build(makeRawScope({ kind: "rec", self: data }))
121
+ const head = data.rules[0]
122
+ if (head !== undefined) {
123
+ const declared = head.select.map(function columnName(column) {
124
+ return column.name
125
+ })
126
+ const candidate = built.rule.select.map(function columnName(column) {
127
+ return column.name
128
+ })
129
+ if (declared.join(", ") !== candidate.join(", ")) {
130
+ throw errors.new(
131
+ `rec ${name}: every rule derives the same head — rule 0 projects (${declared.join(", ")}), this rule projects (${candidate.join(", ")})`
132
+ )
133
+ }
134
+ }
135
+ data.rules.push(built.rule)
136
+ return makeRawRec<Name>(state, name, data)
137
+ }
138
+ }
139
+ Object.freeze(rec)
140
+ return rec
82
141
  }
83
142
 
84
143
  /**
85
- * A declared predicate. `.match({...})` uses it as a body atom — in
86
- * another predicate's rules or in the output rules; the phantom `P`
87
- * carries the params its own rules contributed, so a query's `Params`
88
- * type sees through predicates it only reaches transitively.
144
+ * The rec handles' trusted admission seam (the `refsComplete` pattern):
145
+ * the checkable fact the handle owns exactly the rec data it names — is
146
+ * verified before the raw handle is admitted at its typed face.
89
147
  */
90
- interface Predicate<ColsV, P extends ParamsRecord> {
91
- readonly data: PredicateData
92
- match<const B extends PredicateBindings<ColsV>>(bindings: B): MatchAtom<Flatten<P & BindingsParams<B>>>
148
+ function isRecHandle<Rels extends SchemaRelations, Name extends string, P extends ParamsRecord>(
149
+ data: RecData,
150
+ rec: RawRec<Name>
151
+ ): rec is RawRec<Name> & Rec<Rels, Name, P> {
152
+ return rec.data === data
93
153
  }
94
154
 
95
- /** The params contributed by a predicate's declared rules. */
96
- type PredicateParams<Rules extends readonly { readonly body: readonly AnyBodyItem[] }[]> = ParamsShape<
97
- ItemParams<Rules[number]["body"][number]>
98
- >
99
-
100
- /** Builds one predicate atom (shared by `self.match` and `.match`). */
101
- function predicateAtom(data: PredicateData, bindings: Readonly<Record<string, unknown>>): MatchAtom<never> {
102
- return Object.freeze({
103
- item: "atom" as const,
104
- negated: false,
105
- source: Object.freeze({ kind: "predicate" as const, pred: data }),
106
- bindings: resolveBindings(
107
- `predicate ${data.name}`,
108
- data.columns.map(function asField(column) {
109
- return { name: column.name, field: column.data }
110
- }),
111
- bindings
112
- )
113
- })
155
+ /** Builds one typed rec handle over shared rec data. */
156
+ function makeRec<Rels extends SchemaRelations, Name extends string, P extends ParamsRecord>(
157
+ state: ProgramState,
158
+ name: Name,
159
+ data: RecData
160
+ ): Rec<Rels, Name, P> {
161
+ const raw = makeRawRec<Name>(state, name, data)
162
+ if (!isRecHandle<Rels, Name, P>(data, raw)) {
163
+ throw errors.new(`rec ${name}: handle construction incomplete`)
164
+ }
165
+ return raw
114
166
  }
115
167
 
116
168
  /**
117
- * Declares one predicate in the scope (the `$.predicate` implementation):
118
- * resolves the head columns, runs the rules callback against the
119
- * predicate's own reference, verifies every rule projects a declared
120
- * scope variable per column, and registers the predicate in declaration
121
- * order (= its dense `PredId`).
169
+ * Builds a stratified program over a schema. The callback declares recs
170
+ * and their rules through the scope and MUST return `p.output(...)` the
171
+ * sealed program is an ordinary query value: `db.prepare` lowers it to
172
+ * the one `ProgramIr` shape the engine executes under the per-stratum
173
+ * fixpoint driver.
122
174
  */
123
- function makePredicate<
124
- const Cols extends PredicateColumnsInput,
125
- const Rules extends readonly PredicateRuleInput<ColumnValues<Cols>>[]
126
- >(
127
- registry: QueryRegistry,
128
- name: string,
129
- columns: Cols,
130
- rules: (self: PredicateSelf<ColumnValues<Cols>>) => Rules
131
- ): Predicate<ColumnValues<Cols>, PredicateParams<Rules>> {
132
- const ordered: PredicateColumn[] = []
133
- for (const [columnName, ref] of Object.entries(columns)) {
134
- assertDeclarationOrderKey(`predicate ${name} column`, columnName)
135
- ordered.push(
136
- Object.freeze({
137
- name: columnName,
138
- data: resolveFieldData(registry.theory, ref.relation, ref.field)
139
- })
140
- )
141
- }
142
- if (ordered.length === 0) {
143
- throw errors.new(`predicate ${name}: a predicate head needs at least one column`)
144
- }
145
- const ruleSlots: PredicateRuleData[] = []
146
- const data: PredicateData = Object.freeze({
147
- name,
148
- columns: Object.freeze(ordered),
149
- rules: ruleSlots
150
- })
151
- const self: PredicateSelf<ColumnValues<Cols>> = Object.freeze({
152
- data,
153
- match(bindings: Readonly<Record<string, unknown>>) {
154
- return predicateAtom(data, bindings)
155
- }
156
- })
157
- const declared = rules(self)
158
- for (const rule of declared) {
159
- const finds: AnyVar[] = []
160
- const record: Readonly<Record<string, unknown>> = Object.fromEntries(Object.entries(rule.finds))
161
- for (const column of ordered) {
162
- const found = record[column.name]
163
- if (!isTerm(found) || found[term] !== "var") {
175
+ function program<Rels extends SchemaRelations, Q extends Query<Rels, unknown, ParamsRecord>>(
176
+ theory: Schema<Rels>,
177
+ build: (p: ProgramScope<Rels>) => Q
178
+ ): Q {
179
+ const state: ProgramState = { recs: [], sealed: false }
180
+ const names = new Set<string>()
181
+ const made: { query: unknown } = { query: undefined }
182
+ const scope: ProgramScope<Rels> = {
183
+ rec<const Name extends string>(name: Name): Rec<Rels, Name, Record<never, never>> {
184
+ if (state.sealed) {
185
+ throw errors.new(`program: the output is already declared — rec ${name} would be unreachable`)
186
+ }
187
+ if (names.has(name)) {
164
188
  throw errors.new(
165
- `predicate ${name}: rule finds must project a scope variable for column ${column.name} (interior heads project bound variables only)`
189
+ `program: a rec named ${name} is already declared rec names are the self-recursion cut's identity`
166
190
  )
167
191
  }
168
- finds.push(found)
192
+ names.add(name)
193
+ const data: RecData = { name, rules: [] }
194
+ state.recs.push(data)
195
+ return makeRec<Rels, Name, Record<never, never>>(state, name, data)
196
+ },
197
+ output<const Builds extends readonly OutputBuild<Rels>[]>(
198
+ ...builds: Builds
199
+ ): Query<Rels, OutputRow<Builds>, OutputParams<Builds>> {
200
+ if (state.sealed) {
201
+ throw errors.new("program: output is declared once — multiple rules are multiple builds of the one output")
202
+ }
203
+ state.sealed = true
204
+ for (const rec of state.recs) {
205
+ if (rec.rules.length === 0) {
206
+ throw errors.new(
207
+ `program: rec ${rec.name} has no rules — a predicate with no defining clause seals no signature`
208
+ )
209
+ }
210
+ Object.freeze(rec.rules)
211
+ }
212
+ if (builds.length === 0) {
213
+ throw errors.new("program: the output needs at least one rule")
214
+ }
215
+ const rules = builds.map(function buildRule(buildOne) {
216
+ return buildOne(makeOutputRuleScope<Rels>(state)).rule
217
+ })
218
+ const q = makeQuery<Rels, OutputRow<Builds>, OutputParams<Builds>>(theory, state.recs, rules)
219
+ made.query = q
220
+ return q
169
221
  }
170
- ruleSlots.push(Object.freeze({ finds: Object.freeze(finds), body: Object.freeze([...rule.body]) }))
171
222
  }
172
- Object.freeze(ruleSlots)
173
- registry.predicates.push(data)
174
- return Object.freeze({
175
- data,
176
- match(bindings: Readonly<Record<string, unknown>>) {
177
- return predicateAtom(data, bindings)
178
- }
179
- })
223
+ Object.freeze(scope)
224
+ const result = build(scope)
225
+ if (made.query !== result) {
226
+ throw errors.new("program: the build callback must return p.output(...) — the sealed program IS the query value")
227
+ }
228
+ return result
180
229
  }
181
230
 
182
- export type {
183
- AnyFieldRef,
184
- ColumnValues,
185
- Predicate,
186
- PredicateBindings,
187
- PredicateColumn,
188
- PredicateColumnsInput,
189
- PredicateData,
190
- PredicateParams,
191
- PredicateRuleData,
192
- PredicateRuleInput,
193
- PredicateSelf
194
- }
195
- export { makePredicate }
231
+ export type { OutputBuild, ProgramScope, Rec }
232
+ export { program }
package/src/query/run.ts CHANGED
@@ -1,19 +1,22 @@
1
1
  /**
2
- * Prepared-query marshaling seams (PRD-08), the two rides every execution
3
- * takes — the typed params object down to the bridge's positional
4
- * `QueryParam[]` (declaration order = dense `ParamId`s, values tagged by
5
- * each param's declaring field), and answer rows (positional, head order)
6
- * back up to plain objects with branded values (the store is the proof
7
- * carrier: the engine computed the answer, so readback asserts the brand,
8
- * the same trust direction as fact readback in `#marshal.ts`). Answers are
9
- * SETS no order or limit exists anywhere; hosts sort. The `Prepared`
10
- * VALUE itself (no lifecycle, GC-reclaimed plan) lives in `#db.ts`.
2
+ * Prepared-query marshaling seams, the two rides every execution takes —
3
+ * the typed params object down to the bridge's positional `QueryParam[]`
4
+ * (registry order = dense `ParamId`s, values tagged by each param's
5
+ * ANCHORING use: the field position or comparison sibling that typed it,
6
+ * op-aware exactly as comparison literals tag), and answer rows
7
+ * (positional, head order) back up to plain objects of BARE structural
8
+ * values — the marshal boundary is pure both ways: the engine computed
9
+ * the answer under the prepared head, so a decoded row that carries every
10
+ * select column IS a row (the trusted read seam), and nothing is asserted
11
+ * on any value. Answers are SETS — no order or limit exists anywhere;
12
+ * hosts sort. The `Prepared` VALUE itself (no lifecycle, GC-reclaimed
13
+ * plan) lives in `#db.ts`.
11
14
  */
12
15
 
13
16
  import * as errors from "@superbuilders/errors"
14
17
  import type { FactValue, QueryParam, TaggedValue } from "#native.ts"
15
- import type { SelectColumn } from "#query/lower.ts"
16
- import { taggedLiteral } from "#query/lower.ts"
18
+ import type { SelectColumn } from "#query/atom.ts"
19
+ import { taggedCmpLiteral } from "#query/lower.ts"
17
20
  import type { ParamEntry } from "#query/scope.ts"
18
21
 
19
22
  /** The 13-bit Allen mask ceiling (`bumbledb/crates/bumbledb/src/allen.rs`: bits above the low 13 are unrepresentable). */
@@ -27,12 +30,22 @@ function wireMask(name: string, value: unknown): TaggedValue {
27
30
  return { kind: "allenMask", mask: value }
28
31
  }
29
32
 
33
+ /** Tags one supplied value-param cell by its anchoring use. */
34
+ function wireValue(entry: ParamEntry, context: string, value: unknown): TaggedValue {
35
+ if (entry.anchor === undefined) {
36
+ throw errors.new(
37
+ `param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
38
+ )
39
+ }
40
+ return taggedCmpLiteral(context, entry.anchor, value, entry.op)
41
+ }
42
+
30
43
  /**
31
44
  * Marshals the typed params object to the bridge's positional arguments,
32
- * in param declaration order (= the lowering's dense `ParamId`s). A
33
- * missing entry is a typed error naming the param; values tag by the
34
- * declaring field's structural type; a set param takes a readonly array
35
- * (the empty set is legal and matches nothing — the engine's rule).
45
+ * in registry order (= the lowering's dense `ParamId`s). A missing entry
46
+ * is a typed error naming the param; values tag by the anchoring use's
47
+ * structural type; a set param takes a readonly array (the empty set is
48
+ * legal and matches nothing — the engine's rule).
36
49
  */
37
50
  function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<string, unknown>>): QueryParam[] {
38
51
  return entries.map(function wireOne(entry): QueryParam {
@@ -43,30 +56,27 @@ function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<st
43
56
  if (entry.shape === "mask") {
44
57
  return wireMask(entry.name, value)
45
58
  }
46
- if (entry.data === undefined) {
47
- throw errors.new(`param ${entry.name}: registry entry carries no declaring field`)
48
- }
49
59
  if (entry.shape === "set") {
50
60
  if (!Array.isArray(value)) {
51
61
  throw errors.new(`param ${entry.name}: a set param binds a readonly array of values`)
52
62
  }
53
- const data = entry.data
54
63
  return {
55
64
  kind: "set",
56
65
  values: value.map(function wireElement(element, index) {
57
- return taggedLiteral(`param ${entry.name}[${index}]`, data, element)
66
+ return wireValue(entry, `param ${entry.name}[${index}]`, element)
58
67
  })
59
68
  }
60
69
  }
61
- return taggedLiteral(`param ${entry.name}`, entry.data, value)
70
+ return wireValue(entry, `param ${entry.name}`, value)
62
71
  })
63
72
  }
64
73
 
65
74
  /**
66
75
  * The read-side trusted seam of answers: a decoded row carrying every
67
76
  * select column IS a `Row` — the engine computed it under the prepared
68
- * head, so the brands are asserted, not re-derived (the `#marshal.ts`
69
- * trust direction).
77
+ * head, and the values are BARE structural values, so nothing is asserted
78
+ * beyond presence (the store is the proof carrier; no brand exists to
79
+ * re-derive).
70
80
  */
71
81
  function isAnswerRow<Row>(
72
82
  select: readonly SelectColumn[],
@@ -79,8 +89,8 @@ function isAnswerRow<Row>(
79
89
 
80
90
  /**
81
91
  * Decodes positional answer rows (column order = the program's head order
82
- * = the select record's written order) to named, branded, frozen row
83
- * objects.
92
+ * = the select's written order) to named, frozen row objects of bare
93
+ * structural values.
84
94
  */
85
95
  function decodeAnswers<Row>(select: readonly SelectColumn[], rows: FactValue[][]): Row[] {
86
96
  return rows.map(function decodeRow(row) {