@bjornpagen/bumbledb 0.4.0 → 0.6.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 +191 -92
  2. package/README.md +9 -7
  3. package/dist/closed.d.ts +30 -2
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +66 -20
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +33 -6
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +89 -72
  10. package/dist/db.js.map +1 -1
  11. package/dist/exhume.d.ts.map +1 -1
  12. package/dist/exhume.js +1 -14
  13. package/dist/exhume.js.map +1 -1
  14. package/dist/face.d.ts +1 -1
  15. package/dist/face.d.ts.map +1 -1
  16. package/dist/face.js +2 -1
  17. package/dist/face.js.map +1 -1
  18. package/dist/fields.d.ts +18 -1
  19. package/dist/fields.d.ts.map +1 -1
  20. package/dist/fields.js +44 -16
  21. package/dist/fields.js.map +1 -1
  22. package/dist/index.d.ts +18 -10
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +13 -6
  25. package/dist/index.js.map +1 -1
  26. package/dist/law.d.ts +2 -1
  27. package/dist/law.d.ts.map +1 -1
  28. package/dist/law.js +15 -14
  29. package/dist/law.js.map +1 -1
  30. package/dist/lower.d.ts.map +1 -1
  31. package/dist/lower.js +1 -7
  32. package/dist/lower.js.map +1 -1
  33. package/dist/marshal.d.ts.map +1 -1
  34. package/dist/marshal.js +15 -27
  35. package/dist/marshal.js.map +1 -1
  36. package/dist/native.d.ts +21 -2
  37. package/dist/native.d.ts.map +1 -1
  38. package/dist/native.js +20 -3
  39. package/dist/native.js.map +1 -1
  40. package/dist/order.d.ts +36 -0
  41. package/dist/order.d.ts.map +1 -0
  42. package/dist/order.js +135 -0
  43. package/dist/order.js.map +1 -0
  44. package/dist/query/atom.d.ts +140 -206
  45. package/dist/query/atom.d.ts.map +1 -1
  46. package/dist/query/atom.js +33 -52
  47. package/dist/query/atom.js.map +1 -1
  48. package/dist/query/find.d.ts +116 -0
  49. package/dist/query/find.d.ts.map +1 -0
  50. package/dist/query/{select.js → find.js} +22 -22
  51. package/dist/query/find.js.map +1 -0
  52. package/dist/query/lower.d.ts +124 -162
  53. package/dist/query/lower.d.ts.map +1 -1
  54. package/dist/query/lower.js +462 -507
  55. package/dist/query/lower.js.map +1 -1
  56. package/dist/query/predicate.d.ts +22 -14
  57. package/dist/query/predicate.d.ts.map +1 -1
  58. package/dist/query/predicate.js +45 -20
  59. package/dist/query/predicate.js.map +1 -1
  60. package/dist/query/run.d.ts +3 -3
  61. package/dist/query/run.d.ts.map +1 -1
  62. package/dist/query/run.js +10 -11
  63. package/dist/query/run.js.map +1 -1
  64. package/dist/query/scope.d.ts +124 -83
  65. package/dist/query/scope.d.ts.map +1 -1
  66. package/dist/query/scope.js +77 -70
  67. package/dist/query/scope.js.map +1 -1
  68. package/dist/relation.d.ts +9 -22
  69. package/dist/relation.d.ts.map +1 -1
  70. package/dist/relation.js +9 -28
  71. package/dist/relation.js.map +1 -1
  72. package/dist/schema.d.ts.map +1 -1
  73. package/dist/schema.js +7 -31
  74. package/dist/schema.js.map +1 -1
  75. package/dist/statements.d.ts +5 -2
  76. package/dist/statements.d.ts.map +1 -1
  77. package/dist/statements.js +30 -35
  78. package/dist/statements.js.map +1 -1
  79. package/package.json +2 -5
  80. package/src/closed.ts +73 -28
  81. package/src/db.ts +126 -88
  82. package/src/exhume.ts +1 -15
  83. package/src/face.ts +4 -3
  84. package/src/fields.ts +58 -16
  85. package/src/index.ts +19 -13
  86. package/src/law.ts +15 -14
  87. package/src/lower.ts +2 -9
  88. package/src/marshal.ts +15 -31
  89. package/src/native.ts +22 -4
  90. package/src/order.ts +156 -0
  91. package/src/query/atom.ts +186 -276
  92. package/src/query/find.ts +212 -0
  93. package/src/query/lower.ts +614 -749
  94. package/src/query/predicate.ts +47 -20
  95. package/src/query/run.ts +11 -13
  96. package/src/query/scope.ts +165 -132
  97. package/src/relation.ts +9 -51
  98. package/src/schema.ts +7 -33
  99. package/src/statements.ts +33 -38
  100. package/dist/query/select.d.ts +0 -128
  101. package/dist/query/select.d.ts.map +0 -1
  102. package/dist/query/select.js.map +0 -1
  103. package/src/query/select.ts +0 -215
@@ -5,26 +5,34 @@
5
5
  *
6
6
  * program(S, (p) => {
7
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")))
8
+ * reach.rule((r) => {
9
+ * const n = v(Node)
10
+ * return r.match(Node, { id: n.id }).where(r.eq(n.id, r.param("root"))).find({ c: n.id })
11
+ * })
12
+ * reach.rule((r) => {
13
+ * const e = v(Parent)
14
+ * return r.match(Parent, { child: e.child, parent: e.parent }).idb(reach, { c: e.parent }).find({ c: e.child })
15
+ * })
16
+ * return p.output((r) => {
17
+ * const post = v(Posting)
18
+ * return r.match(Posting, { account: post.account, minor: post.minor })
19
+ * .idb(reach, { c: post.account }).find({ total: r.sum(post.minor) })
20
+ * })
14
21
  * })
15
22
  *
16
23
  * `p.rec(name)` declares one recursive predicate (declaration order = its
17
24
  * dense `PredId`); `rec.rule(...)` attaches one clause — its builder's
18
25
  * `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);
26
+ * type-level boundary: mutual recursion is unwritable) and its `find` head
27
+ * projects bound variables only (aggregation/measure through a cycle is
28
+ * unrepresentable — the strata judge's roster, made unwritable);
22
29
  * `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.
30
+ * folds any FINISHED stratum by NAMED record over its head keys (recipe
31
+ * 25's form). The rec value `.rule` returns carries the params its rules
32
+ * used thread it into the output's `idb` and the program's inferred
33
+ * `Params` stays exactly the params the rules use. Everything deeper —
34
+ * strata legality, signature sealing, the three oracles — is the ENGINE's
35
+ * judge, surfacing typed at prepare.
28
36
  */
29
37
 
30
38
  import * as errors from "@superbuilders/errors"
@@ -46,7 +54,7 @@ import type {
46
54
  } from "#query/lower.ts"
47
55
  import { makeOutputRuleScope, makeQuery, makeRawScope } from "#query/lower.ts"
48
56
  import type { Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
49
- import { inferred } from "#query/scope.ts"
57
+ import { fieldJoins, inferred, renderFieldKind } from "#query/scope.ts"
50
58
  import type { Schema, SchemaRelations } from "#schema.ts"
51
59
 
52
60
  /**
@@ -123,13 +131,13 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
123
131
  `rec ${name}: the program's output is already declared — recursive rules attach before p.output`
124
132
  )
125
133
  }
126
- const built = build(makeRawScope({ kind: "rec", self: data, classes: state.classes }))
134
+ const built = build(makeRawScope({ kind: "rec", self: data, classes: state.classes, theory: state.theory }))
127
135
  const head = data.rules[0]
128
136
  if (head !== undefined) {
129
- const declared = head.select.map(function columnName(column) {
137
+ const declared = head.finds.map(function columnName(column) {
130
138
  return column.name
131
139
  })
132
- const candidate = built.rule.select.map(function columnName(column) {
140
+ const candidate = built.rule.finds.map(function columnName(column) {
133
141
  return column.name
134
142
  })
135
143
  if (declared.join(", ") !== candidate.join(", ")) {
@@ -137,6 +145,24 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
137
145
  `rec ${name}: every rule derives the same head — rule 0 projects (${declared.join(", ")}), this rule projects (${candidate.join(", ")})`
138
146
  )
139
147
  }
148
+ // The law-class wall on the sealed head: names alone do not
149
+ // align value spaces. Every rule must bind each head column
150
+ // at a classed mint slot that JOINS rule 0's (the sealing rule
151
+ // — the one slot every downstream idb pairing class-checks
152
+ // against), under the same fieldJoins judgment every reuse
153
+ // site enforces; otherwise a later rule feeds (say) bare
154
+ // weights into a column the idb joins as Node ids.
155
+ built.rule.finds.forEach(function verifyHeadSlot(column, position) {
156
+ const lead = head.finds[position]
157
+ if (lead === undefined) {
158
+ return
159
+ }
160
+ if (lead.slot !== undefined && column.slot !== undefined && !fieldJoins(lead.slot, column.slot)) {
161
+ throw errors.new(
162
+ `rec ${name}: every rule derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in this rule (a head column joins only class-equal slots; bare pairs only with bare)`
163
+ )
164
+ }
165
+ })
140
166
  }
141
167
  data.rules.push(built.rule)
142
168
  return makeRawRec<Name>(state, name, data)
@@ -147,7 +173,8 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
147
173
  }
148
174
 
149
175
  /**
150
- * The rec handles' trusted admission seam (the `refsComplete` pattern):
176
+ * The rec handles' trusted admission seam (the pattern's home is
177
+ * `isTypedScope` in query/lower.ts):
151
178
  * the checkable fact — the handle owns exactly the rec data it names — is
152
179
  * verified before the raw handle is admitted at its typed face.
153
180
  */
@@ -186,7 +213,7 @@ function program<
186
213
  Classes extends SchemaClasses,
187
214
  Q extends Query<Rels, unknown, ParamsRecord, Classes>
188
215
  >(theory: Schema<Rels, Classes>, build: (p: ProgramScope<Rels, Classes>) => Q): Q {
189
- const state: ProgramState = { recs: [], classes: theory.classes, sealed: false }
216
+ const state: ProgramState = { recs: [], classes: theory.classes, theory, sealed: false }
190
217
  const names = new Set<string>()
191
218
  const made: { query: unknown } = { query: undefined }
192
219
  const scope: ProgramScope<Rels, Classes> = {
package/src/query/run.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * select column IS a row (the trusted read seam), and nothing is asserted
11
11
  * on any value. A CLOSED answer column decodes id → handle NAME through
12
12
  * the marshal's one bijection (`handleOf` — the same read half every fact
13
- * decode rides; the column's roster rides `SelectColumn.closed`), so query
13
+ * decode rides; the column's roster rides `FindColumn.closed`), so query
14
14
  * rows speak the vocabulary exactly as scans and gets do. Answers are
15
15
  * SETS — no order or limit exists anywhere; hosts sort. The `Prepared`
16
16
  * VALUE itself (no lifecycle, GC-reclaimed plan) lives in `#db.ts`.
@@ -19,13 +19,11 @@
19
19
  import * as errors from "@superbuilders/errors"
20
20
  import { handleOf } from "#marshal.ts"
21
21
  import type { FactValue, QueryParam, TaggedValue } from "#native.ts"
22
- import type { SelectColumn } from "#query/atom.ts"
22
+ import type { FindColumn } from "#query/atom.ts"
23
+ import { ALLEN_ALL_BITS } from "#query/atom.ts"
23
24
  import { taggedCmpLiteral } from "#query/lower.ts"
24
25
  import type { ParamEntry } from "#query/scope.ts"
25
26
 
26
- /** The 13-bit Allen mask ceiling (`bumbledb/crates/bumbledb/src/allen.rs`: bits above the low 13 are unrepresentable). */
27
- const ALLEN_ALL_BITS = (1 << 13) - 1
28
-
29
27
  /** Tags one supplied mask-param value. */
30
28
  function wireMask(name: string, value: unknown): TaggedValue {
31
29
  if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > ALLEN_ALL_BITS) {
@@ -96,10 +94,10 @@ function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<st
96
94
  * re-derive).
97
95
  */
98
96
  function isAnswerRow<Row>(
99
- select: readonly SelectColumn[],
97
+ finds: readonly FindColumn[],
100
98
  decoded: Readonly<Record<string, FactValue>>
101
99
  ): decoded is Readonly<Record<string, FactValue>> & Row {
102
- return select.every(function present(column) {
100
+ return finds.every(function present(column) {
103
101
  return decoded[column.name] !== undefined
104
102
  })
105
103
  }
@@ -111,13 +109,13 @@ function isAnswerRow<Row>(
111
109
  * NAME through the marshal's bijection — an out-of-roster id is the same
112
110
  * pointed throw a fact decode gives, never a silent fallback.
113
111
  */
114
- function decodeAnswers<Row>(select: readonly SelectColumn[], rows: FactValue[][]): Row[] {
112
+ function decodeAnswers<Row>(finds: readonly FindColumn[], rows: FactValue[][]): Row[] {
115
113
  return rows.map(function decodeRow(row) {
116
- if (row.length !== select.length) {
117
- throw errors.new(`query answer arity ${row.length} does not match the ${select.length} select columns`)
114
+ if (row.length !== finds.length) {
115
+ throw errors.new(`query answer arity ${row.length} does not match the ${finds.length} find columns`)
118
116
  }
119
117
  const decoded: Record<string, FactValue> = {}
120
- select.forEach(function decodeCell(column, ordinal) {
118
+ finds.forEach(function decodeCell(column, ordinal) {
121
119
  const cell = row[ordinal]
122
120
  if (cell === undefined) {
123
121
  throw errors.new(`query answer cell ${ordinal} (${column.name}) is absent`)
@@ -126,8 +124,8 @@ function decodeAnswers<Row>(select: readonly SelectColumn[], rows: FactValue[][]
126
124
  column.closed === undefined ? cell : handleOf(`query answer column ${column.name}`, column.closed, cell)
127
125
  })
128
126
  Object.freeze(decoded)
129
- if (!isAnswerRow<Row>(select, decoded)) {
130
- throw errors.new("query answer row is not a complete select record")
127
+ if (!isAnswerRow<Row>(finds, decoded)) {
128
+ throw errors.new("query answer row is not a complete find record")
131
129
  }
132
130
  return decoded
133
131
  })
@@ -1,26 +1,42 @@
1
1
  /**
2
- * Query scope terms, LAW-TYPED edition: string-named variables and
3
- * parameters as plain frozen values. A `Var` is a NAME — it is typed by the
4
- * field slot it first binds (the descriptor AND the slot's law-computed
5
- * CLASS, read off the schema's class map through the rule builder's
6
- * environment), reuse of the name within one rule IS the join, and a
7
- * class-mismatched reuse is a compile error: a var joins only class-equal
8
- * slots, and BARE PAIRS ONLY WITH BARE (ruling 3 a slot in no law has no
9
- * class and never joins a classed slot; the deliberate sum-domain pointer
10
- * stays legal against other bare slots). Params are query-global by name
11
- * and typed BY USE: the field position or comparison sibling that anchors
12
- * a param types it, the query's inferred `Params` object is exactly the
13
- * params the rules use, and a param value that no rule uses simply never
14
- * registers — the query executes under its own inferred type (the
15
- * bug-hunt law). This module also owns the environment/typing utilities
16
- * the whole surface shares: the env shape (var name → classed slot), the
17
- * class-equality judgment {@link JoinOk} with its runtime twin
18
- * {@link fieldJoins}, and the record-folding helpers `Params` and `Row`
19
- * inference ride.
2
+ * Query scope terms, REFERENCE-IDENTITY edition: a query variable is an
3
+ * OBJECT, minted fresh by {@link v} over a relation's statically-known
4
+ * columns. `v(relation)` returns a record of fresh variables one per
5
+ * column, each typed at mint by its column's descriptor AND the mint
6
+ * coordinate (the owner relation name and the column name), so
7
+ * destructuring preserves every literal and every class
8
+ * (`const { id, holder } = v(Account)`). Variable IDENTITY is the object
9
+ * reference: reusing the same var value across binding positions IS the
10
+ * join, and a name-collision join is unrepresentable (two `v()` calls mint
11
+ * two distinct batches, so two same-named vars are two variables). Params
12
+ * stay STRING-named their names are the execute() params object's runtime
13
+ * keys, an honest load-bearing channel, not a lie.
14
+ *
15
+ * THE DESIGN THEOREM. {@link JoinOk} is an EQUALITY (kind, class, width,
16
+ * element, roster), so judging every binding position against the
17
+ * variable's MINT slot ({@link MintSlotOf}) makes all cross-binding joins
18
+ * mutually class-equal by transitivity the env/sibling checks the
19
+ * name-keyed edition needed are subsumed, deleted rather than ported. The
20
+ * one check representation cannot carry is BOUNDNESS (is this var positively
21
+ * bound in this rule): TypeScript types cannot see object identity, so
22
+ * boundness moves from the type tier to construction-time walls only — an
23
+ * explicit essential-vs-accidental concession; every runtime twin is
24
+ * preserved.
25
+ *
26
+ * This module also owns the environment/typing utilities the whole surface
27
+ * shares: the join descriptor {@link ClassedField}, the mint-slot machinery
28
+ * ({@link MintSlotOf}/{@link MintClassOf}), the class-equality judgment
29
+ * {@link JoinOk} with its runtime twin {@link fieldJoins}, and the
30
+ * record-folding helpers `Params` and `Row` inference ride.
20
31
  */
21
32
 
22
33
  import * as errors from "@superbuilders/errors"
34
+ import type { AnyClosed } from "#closed.ts"
35
+ import { sealedFieldsOf } from "#closed.ts"
23
36
  import type { AnyField, Infer } from "#fields.ts"
37
+ import { rosterOf } from "#fields.ts"
38
+ import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
39
+ import type { AnyRelation, RelationFields } from "#relation.ts"
24
40
 
25
41
  /**
26
42
  * The runtime discriminant of query term values. Host literals (bigints,
@@ -38,17 +54,47 @@ const term: unique symbol = Symbol("bumbledb.query.term")
38
54
  const inferred: unique symbol = Symbol("bumbledb.query.inferred")
39
55
 
40
56
  /**
41
- * A query variable — a NAME. Its type comes from the field it first binds
42
- * in the rule (the builder's environment); reusing the name joins, and a
43
- * cross-domain reuse is a compile error. Identity is the name, strictly
44
- * rule-scoped: the same name in two rules names two unrelated variables
45
- * (exactly as the IR scopes `VarId`).
57
+ * What a query atom matches over: an ordinary relation or a CLOSED
58
+ * vocabulary (ψ query atoms the engine folds a resolvable closed atom
59
+ * into a plan-constant member set at prepare, or joins the L1-resident
60
+ * virtual image when the shape does not fold; the SDK never pre-folds and
61
+ * never knows which transparency is the contract).
46
62
  */
47
- interface Var<Name extends string = string> {
63
+ type MatchOwner = AnyRelation | AnyClosed
64
+
65
+ /**
66
+ * The matchable field block of an atom owner: a relation's declared
67
+ * fields; a closed relation's SEALED shape — the synthetic `id` (the
68
+ * value's OWN roster-carrying descriptor, at its precise type) first, then
69
+ * the declared payload columns read through the typed `columns` carrier.
70
+ * The runtime twin is `sealedFieldsOf` in `#closed.ts`.
71
+ */
72
+ type MatchFields<R extends MatchOwner> = R extends AnyClosed
73
+ ? { readonly id: R["id"] } & R["columns"]
74
+ : R extends AnyRelation
75
+ ? RelationFields<R>
76
+ : never
77
+
78
+ /**
79
+ * A query variable — an OBJECT minted by {@link v}. Identity is the object
80
+ * reference: reuse of the same value across binding positions is the join,
81
+ * strictly rule-scoped (each rule numbers its own dense `VarId`s). The type
82
+ * carries the mint COORDINATE — `RN` the owner relation name literal, `K`
83
+ * the column name literal — and `F` the mint descriptor, so the mint slot
84
+ * (descriptor + law-computed class) is recoverable at every binding
85
+ * position for the join judgment.
86
+ */
87
+ interface Var<F extends AnyField = AnyField, RN extends string = string, K extends string = string> {
48
88
  readonly [term]: "var"
49
- readonly name: Name
89
+ readonly owner: MatchOwner & { readonly name: RN }
90
+ readonly column: K
91
+ readonly field: F
92
+ readonly label: string
50
93
  }
51
94
 
95
+ /** Any query variable, whatever its descriptor and mint coordinate. */
96
+ type AnyVar = Var
97
+
52
98
  /**
53
99
  * A scalar query parameter — `r.param("root")`. The name is the key of the
54
100
  * typed params object `execute` takes; the type is the element type of the
@@ -86,14 +132,13 @@ interface MaskParam<Name extends string = string> {
86
132
  /**
87
133
  * The measure of an interval-typed variable (`ir::Term::Measure`):
88
134
  * `|[s, e)| = e − s`, u64 — legal as one side of an order comparison, as a
89
- * select entry, and as the input of `sum`/`min`/`max`; every other position
90
- * is unwritable, exactly as the IR rejects it typed. A ray has no finite
91
- * measure the engine's `MeasureOfRay` execution error; exclude rays first
92
- * (`allen` against a bounded window).
135
+ * find entry, and as the input of `sum`/`min`/`max`; every other position
136
+ * is unwritable, exactly as the IR rejects it typed. Carries the interval
137
+ * variable it measures BY REFERENCE.
93
138
  */
94
- interface Duration<Name extends string = string> {
139
+ interface Duration<V extends AnyVar = AnyVar> {
95
140
  readonly [term]: "duration"
96
- readonly name: Name
141
+ readonly over: V
97
142
  }
98
143
 
99
144
  /** Any scope term value. */
@@ -104,61 +149,55 @@ function isTerm(value: unknown): value is AnyTerm {
104
149
  return typeof value === "object" && value !== null && term in value
105
150
  }
106
151
 
107
- /** Builds one variable term. */
108
- function makeVar<const Name extends string>(name: Name): Var<Name> {
109
- const value: Var<Name> = { [term]: "var", name }
110
- return Object.freeze(value)
152
+ /**
153
+ * The record of fresh variables `v(owner)` mints — one per statically-known
154
+ * column, each typed by its column's descriptor and mint coordinate.
155
+ */
156
+ type VarsOf<R extends MatchOwner> = {
157
+ readonly [K in keyof MatchFields<R> & string]: Var<MatchFields<R>[K], R["name"], K>
111
158
  }
112
159
 
113
- /** The record `makeVars` mints: one own frozen `Var<Name>` per requested name, each typed exactly. */
114
- type VarsRecord<Names extends string> = { readonly [N in Names]: Var<N> }
115
-
116
160
  /**
117
- * The trusted seam of the vars mint: every requested name reads back as an
118
- * own var term of exactly that nameverified before the record is
119
- * admitted at the {@link VarsRecord} type (a name riding the
120
- * object-protocol accessor instead of an own definition would fail exactly
121
- * this check).
161
+ * The trusted admission seam of the variable-record mint (the pattern's
162
+ * home is `isTypedScope` in `#query/lower.ts`): the checkable fact one own
163
+ * enumerable variable per sealed column is verified before the record is
164
+ * admitted at its computed {@link VarsOf} type.
122
165
  */
123
- function varsMinted<Names extends string>(
124
- record: Readonly<Record<string, unknown>>,
125
- names: readonly Names[]
126
- ): record is Readonly<Record<string, unknown>> & VarsRecord<Names> {
127
- return names.every(function varMinted(name) {
128
- if (!Object.hasOwn(record, name)) {
129
- return false
130
- }
131
- const value = record[name]
132
- return isTerm(value) && value[term] === "var" && value.name === name
166
+ function varsMinted<R extends MatchOwner>(owner: R, record: Readonly<Record<string, AnyVar>>): record is VarsOf<R> {
167
+ return sealedFieldsOf(owner).every(function columnMinted(declared) {
168
+ return Object.hasOwn(record, declared.name)
133
169
  })
134
170
  }
135
171
 
136
172
  /**
137
- * Mints several variables at once `const { service, w } = r.vars("service",
138
- * "w")`: tuple-to-object, each name typed exactly (`Var<"service">`),
139
- * inference identical to the one-at-a-time `r.var` spelling (one lowering,
140
- * two entry flavors). Each key is defined as an OWN property (a name like
141
- * `"__proto__"` is a record key like any other, never a prototype write),
142
- * and a duplicate name in one call is a construction error: each name mints
143
- * one variable write it once and reuse the binding.
173
+ * Mints a FRESH batch of query variables over an atom owner's
174
+ * statically-known columns one variable per sealed column
175
+ * (`sealedFieldsOf`: a closed owner mints `id` first, then payload columns),
176
+ * each frozen and each defined by OWN-property definition (object-protocol
177
+ * column names must work, the `closed()` precedent). Every `v()` call mints
178
+ * new objects, so two batches are two variables; property access within one
179
+ * batch is stable by construction (the record is an eager frozen record,
180
+ * never a Proxy). Variable identity is the object reference: destructure
181
+ * what you need (`const { id, holder } = v(Account)`) and reuse a value
182
+ * across binding positions to join.
144
183
  */
145
- function makeVars<const Names extends readonly string[]>(...names: Names): VarsRecord<Names[number]> {
146
- const out: Record<string, unknown> = {}
147
- const seen = new Set<string>()
148
- for (const name of names) {
149
- if (seen.has(name)) {
150
- throw errors.new(
151
- `vars: duplicate name ${name} — each name mints one variable; write it once and reuse the binding`
152
- )
153
- }
154
- seen.add(name)
155
- Object.defineProperty(out, name, { value: makeVar(name), enumerable: true })
184
+ function v<R extends MatchOwner>(owner: R): VarsOf<R> {
185
+ const record: Record<string, AnyVar> = {}
186
+ for (const declared of sealedFieldsOf(owner)) {
187
+ const variable: AnyVar = Object.freeze({
188
+ [term]: "var" as const,
189
+ owner,
190
+ column: declared.name,
191
+ field: declared.field,
192
+ label: `${owner.name}.${declared.name}`
193
+ })
194
+ Object.defineProperty(record, declared.name, { value: variable, enumerable: true })
156
195
  }
157
- Object.freeze(out)
158
- if (!varsMinted<Names[number]>(out, names)) {
159
- throw errors.new("vars: variable minting incomplete")
196
+ Object.freeze(record)
197
+ if (!varsMinted(owner, record)) {
198
+ throw errors.new(`v(${owner.name}): variable-record minting incomplete`)
160
199
  }
161
- return out
200
+ return record
162
201
  }
163
202
 
164
203
  /** Builds one scalar-parameter term. */
@@ -179,19 +218,17 @@ function makeMaskParam<const Name extends string>(name: Name): MaskParam<Name> {
179
218
  return Object.freeze(value)
180
219
  }
181
220
 
182
- /** Builds one measure term over an interval-typed variable's name. */
183
- function makeDuration<const Name extends string>(name: Name): Duration<Name> {
184
- const value: Duration<Name> = { [term]: "duration", name }
221
+ /** Builds one measure term over an interval-typed variable reference. */
222
+ function makeDuration<const V extends AnyVar>(over: V): Duration<V> {
223
+ const value: Duration<V> = { [term]: "duration", over }
185
224
  return Object.freeze(value)
186
225
  }
187
226
 
188
227
  /**
189
228
  * One bound field slot: the field's descriptor plus the slot's
190
229
  * law-computed CLASS (`undefined` = bare — the slot is in no law). The one
191
- * shape the rule environment carries per variable, at the TYPE level (env
192
- * entries hold the schema type's class-map lookups) and at RUNTIME alike
193
- * (the rule's `varFields` record holds exactly this shape, read off the
194
- * schema value's frozen class map) — one shape, two tiers, one walk.
230
+ * shape every join judgment compares, at the TYPE level (a variable's mint
231
+ * slot, a binding position's slot) and at RUNTIME alike.
195
232
  */
196
233
  interface ClassedField {
197
234
  readonly field: AnyField
@@ -199,11 +236,25 @@ interface ClassedField {
199
236
  }
200
237
 
201
238
  /**
202
- * A rule's typing environment: variable name the classed slot it first
203
- * bound. Purely a TYPE the runtime twin is the rule's `varFields`
204
- * record, and the two are built by the same walk.
239
+ * A variable's law-computed CLASS at the TYPE level: its column's class,
240
+ * read off the schema type's class map through the mint coordinate the
241
+ * variable carries (`RN.K`). `undefined` = bare.
242
+ */
243
+ type MintClassOf<Classes extends SchemaClasses, V> =
244
+ V extends Var<AnyField, infer RN extends string, infer K extends string>
245
+ ? ClassLookup<ClassRecordOf<Classes, RN>, K>
246
+ : never
247
+
248
+ /**
249
+ * A variable's MINT slot: the descriptor it was minted at plus its
250
+ * law-computed class. The one slot every binding position judges against —
251
+ * because {@link JoinOk} is an equality, judging each position against the
252
+ * mint slot makes every cross-binding join transitively class-equal.
205
253
  */
206
- type EnvShape = Record<string, ClassedField>
254
+ type MintSlotOf<Classes extends SchemaClasses, V extends AnyVar> = {
255
+ readonly field: V["field"]
256
+ readonly class: MintClassOf<Classes, V>
257
+ }
207
258
 
208
259
  /** A params object type — what `execute` takes and inference carries. */
209
260
  type ParamsRecord = Readonly<Record<string, unknown>>
@@ -228,7 +279,9 @@ type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : u
228
279
  /** Reads an interval descriptor's element kind; `undefined` on scalar kinds. */
229
280
  type ElementOf<F extends AnyField> = F extends { readonly element: infer E } ? E : undefined
230
281
 
231
- /** Reads a closed reference's handle union; `undefined` on every non-closed kind (the roster IS descriptor structure). */
282
+ /**
283
+ * Reads a closed reference's handle union; `undefined` on every non-closed kind (the roster IS descriptor structure).
284
+ */
232
285
  type RosterOf<F extends AnyField> = F extends {
233
286
  readonly closed: { readonly handles: readonly (infer H extends string)[] }
234
287
  }
@@ -239,13 +292,10 @@ type RosterOf<F extends AnyField> = F extends {
239
292
  * The join judgment: two bound slots join iff their descriptors' structure
240
293
  * agrees (kind, width label, interval element, and the closed ROSTER — a
241
294
  * closed reference pairs only with the same vocabulary, never with a bare
242
- * u64: the roster keys every closed judgment downstream, so a join across
243
- * it would decode/order/translate incoherently by binding order) AND their
244
- * law-computed classes agree same class name joins, and bare
245
- * (`undefined`) pairs only with bare (ruling 3: a field in no law has no
246
- * class; a bare↔classed pairing refuses). The class names come off the
247
- * SCHEMA type's class map — the statements are the typing; no descriptor
248
- * label beyond the roster exists to compare.
295
+ * u64) AND their law-computed classes agree same class name joins, and
296
+ * bare (`undefined`) pairs only with bare (ruling 3). The class names come
297
+ * off the SCHEMA type's class map; no descriptor label beyond the roster
298
+ * exists to compare.
249
299
  */
250
300
  type JoinOk<A extends ClassedField, B extends ClassedField> = [
251
301
  A["field"]["kind"],
@@ -269,18 +319,17 @@ type JoinOk<A extends ClassedField, B extends ClassedField> = [
269
319
  * The runtime twin of {@link JoinOk}: two bound slots join iff descriptor
270
320
  * structure and class agree — the same comparison the type tier makes,
271
321
  * judged on the honest runtime values (the descriptor, the roster by VALUE
272
- * IDENTITY vocabulary identity is value identity and the schema
273
- * value's frozen class map). The rule builders throw through this on a
274
- * class-unequal variable reuse, so the wall holds for untyped callers too,
275
- * not only where the compiler can see.
322
+ * IDENTITY, and the schema value's frozen class map). The rule builders
323
+ * throw through this on a class-unequal reuse, so the wall holds for untyped
324
+ * callers too.
276
325
  */
277
326
  function fieldJoins(a: ClassedField, b: ClassedField): boolean {
278
327
  const widthA = "width" in a.field ? a.field.width : undefined
279
328
  const widthB = "width" in b.field ? b.field.width : undefined
280
329
  const elementA = "element" in a.field ? a.field.element : undefined
281
330
  const elementB = "element" in b.field ? b.field.element : undefined
282
- const rosterA = "closed" in a.field ? a.field.closed : undefined
283
- const rosterB = "closed" in b.field ? b.field.closed : undefined
331
+ const rosterA = rosterOf(a.field)
332
+ const rosterB = rosterOf(b.field)
284
333
  return (
285
334
  a.field.kind === b.field.kind &&
286
335
  a.class === b.class &&
@@ -293,15 +342,15 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
293
342
  /**
294
343
  * Renders one bound slot for join-mismatch diagnostics — the structural
295
344
  * kind in the schema grammar's spelling (a closed reference names its
296
- * vocabulary: the roster is part of the structure being compared) plus the
297
- * slot's law-computed class (`u64 in class Holder.id`; a lawless slot
298
- * renders `bare`).
345
+ * vocabulary) plus the slot's law-computed class (`u64 in class Holder.id`;
346
+ * a lawless slot renders `bare`).
299
347
  */
300
348
  function renderFieldKind(slot: ClassedField): string {
301
349
  const field = slot.field
302
350
  let base: string = field.kind
303
- if ("closed" in field) {
304
- base = `u64 referencing ${field.closed.name}`
351
+ const roster = rosterOf(field)
352
+ if (roster !== undefined) {
353
+ base = `u64 referencing ${roster.name}`
305
354
  }
306
355
  if (field.kind === "bytes") {
307
356
  base = `bytes<${field.width}>`
@@ -316,11 +365,7 @@ function renderFieldKind(slot: ClassedField): string {
316
365
  * What a PARAM anchored at field `F` accepts at execution: the field's
317
366
  * bare value type, exactly — at a CLOSED-reference field that is the
318
367
  * handle-name union (`"DirectPass" | "Failed"`), translated name → row id
319
- * at execute through the one roster-verification point
320
- * (`taggedHandleId`). At an interval field the engine resolves the
321
- * bivalent anchor to the INTERVAL reading (value equality) — the point
322
- * reading of a param is spelled `pointIn(r.param(...), w)`, whose sibling
323
- * anchors it element-typed.
368
+ * at execute through the one roster-verification point (`taggedHandleId`).
324
369
  */
325
370
  type ParamValueAt<F extends AnyField> = Infer<F>
326
371
 
@@ -331,13 +376,9 @@ type InferredOf<T> = T extends { readonly [inferred]?: infer S } ? Exclude<S, un
331
376
  * One registered parameter of a query, as the wire marshal reads it: the
332
377
  * name, the wire shape, the field descriptor (or the measure) that anchored
333
378
  * it, and the comparison op the anchor came from (`"binding"` for atom
334
- * positions) the op keeps literal tagging op-aware at `pointIn`
335
- * (the bug-hunt fix, preserved). `anchor` is `undefined` only on a query
336
- * built but not yet anchored by any rule; lowering and the wire both refuse
337
- * that state typed. `members` is present exactly on a MEMBERSHIP-ARRAY
338
- * entry (a literal set at a closed field, folded into the program): the
339
- * SDK itself translates and supplies the set at every execute — the entry
340
- * is never read from, and never demanded of, the host's params object.
379
+ * positions). `anchor` is `undefined` only on a query built but not yet
380
+ * anchored by any rule. `members` is present exactly on a MEMBERSHIP-ARRAY
381
+ * entry.
341
382
  */
342
383
  interface ParamEntry {
343
384
  readonly name: string
@@ -349,13 +390,17 @@ interface ParamEntry {
349
390
 
350
391
  export type {
351
392
  AnyTerm,
393
+ AnyVar,
352
394
  ClassedField,
353
395
  Duration,
354
- EnvShape,
355
396
  Flatten,
356
397
  InferredOf,
357
398
  JoinOk,
358
399
  MaskParam,
400
+ MatchFields,
401
+ MatchOwner,
402
+ MintClassOf,
403
+ MintSlotOf,
359
404
  Param,
360
405
  ParamEntry,
361
406
  ParamsRecord,
@@ -364,18 +409,6 @@ export type {
364
409
  ShapeOf,
365
410
  UnionToIntersection,
366
411
  Var,
367
- VarsRecord
368
- }
369
- export {
370
- fieldJoins,
371
- inferred,
372
- isTerm,
373
- makeDuration,
374
- makeMaskParam,
375
- makeParam,
376
- makeSetParam,
377
- makeVar,
378
- makeVars,
379
- renderFieldKind,
380
- term
412
+ VarsOf
381
413
  }
414
+ export { fieldJoins, inferred, isTerm, makeDuration, makeMaskParam, makeParam, makeSetParam, renderFieldKind, term, v }