@bjornpagen/bumbledb 0.3.0 → 0.5.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.
- package/COOKBOOK.md +246 -92
- package/README.md +31 -16
- package/dist/closed.d.ts +80 -75
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +102 -127
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +37 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +95 -55
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +1 -14
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +40 -40
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +9 -17
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +46 -15
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +58 -29
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +2 -1
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +15 -14
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +1 -7
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +33 -6
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +75 -26
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +21 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +20 -3
- package/dist/native.js.map +1 -1
- package/dist/order.d.ts +36 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +135 -0
- package/dist/order.js.map +1 -0
- package/dist/query/atom.d.ts +76 -28
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +12 -16
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +5 -8
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +308 -72
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +34 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +15 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +27 -8
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +37 -29
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +19 -47
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +17 -29
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +41 -38
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -31
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +3 -2
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +13 -4
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +75 -8
- package/dist/statements.js.map +1 -1
- package/package.json +2 -5
- package/src/closed.ts +144 -206
- package/src/db.ts +143 -68
- package/src/exhume.ts +1 -15
- package/src/face.ts +38 -48
- package/src/fields.ts +103 -49
- package/src/index.ts +11 -10
- package/src/law.ts +15 -14
- package/src/lower.ts +2 -9
- package/src/marshal.ts +82 -31
- package/src/native.ts +22 -4
- package/src/order.ts +156 -0
- package/src/query/atom.ts +70 -35
- package/src/query/lower.ts +354 -82
- package/src/query/predicate.ts +39 -4
- package/src/query/run.ts +27 -9
- package/src/query/scope.ts +52 -70
- package/src/relation.ts +54 -68
- package/src/schema.ts +7 -33
- package/src/spec.ts +3 -2
- package/src/statements.ts +82 -8
package/src/query/predicate.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import * as errors from "@superbuilders/errors"
|
|
31
31
|
import type { SchemaClasses } from "#law.ts"
|
|
32
|
-
import type { RecData } from "#query/atom.ts"
|
|
32
|
+
import type { RecData, RuleData, SelectColumn } from "#query/atom.ts"
|
|
33
33
|
import type {
|
|
34
34
|
AnyRuleValue,
|
|
35
35
|
HeadOf,
|
|
@@ -45,8 +45,8 @@ import type {
|
|
|
45
45
|
RuleValue
|
|
46
46
|
} from "#query/lower.ts"
|
|
47
47
|
import { makeOutputRuleScope, makeQuery, makeRawScope } from "#query/lower.ts"
|
|
48
|
-
import type { Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
|
|
49
|
-
import { inferred } from "#query/scope.ts"
|
|
48
|
+
import type { ClassedField, Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
|
|
49
|
+
import { fieldJoins, inferred, renderFieldKind } from "#query/scope.ts"
|
|
50
50
|
import type { Schema, SchemaRelations } from "#schema.ts"
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -112,6 +112,20 @@ interface RawRec<Name extends string> {
|
|
|
112
112
|
rule(build: (r: RawScope) => RuleValue<never, never>): RawRec<Name>
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* The classed slot one rec head column binds, through the rule's own
|
|
117
|
+
* environment: a rec head projects bound variable NAMES only (the strata
|
|
118
|
+
* roster's unwritability), so every column is a projected var and its
|
|
119
|
+
* slot is the var's first positive binding.
|
|
120
|
+
*/
|
|
121
|
+
function recHeadSlotOf(rule: RuleData, column: SelectColumn): ClassedField | undefined {
|
|
122
|
+
const entry = column.entry
|
|
123
|
+
if (entry.kind === "var") {
|
|
124
|
+
return rule.varFields[entry.over]
|
|
125
|
+
}
|
|
126
|
+
return undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
/** Builds the runtime rec handle over shared rec data. */
|
|
116
130
|
function makeRawRec<Name extends string>(state: ProgramState, name: Name, data: RecData): RawRec<Name> {
|
|
117
131
|
const rec: RawRec<Name> = {
|
|
@@ -137,6 +151,26 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
|
|
|
137
151
|
`rec ${name}: every rule derives the same head — rule 0 projects (${declared.join(", ")}), this rule projects (${candidate.join(", ")})`
|
|
138
152
|
)
|
|
139
153
|
}
|
|
154
|
+
// The law-class wall on the sealed head: names alone do not
|
|
155
|
+
// align value spaces. Every rule must bind each head column
|
|
156
|
+
// at a slot that JOINS rule 0's (the sealing rule — the one
|
|
157
|
+
// slot every downstream idb pairing class-checks against),
|
|
158
|
+
// under the same fieldJoins judgment every reuse site
|
|
159
|
+
// enforces; otherwise a later rule feeds (say) bare weights
|
|
160
|
+
// into a column the idb joins as Node ids.
|
|
161
|
+
built.rule.select.forEach(function verifyHeadSlot(column, position) {
|
|
162
|
+
const lead = head.select[position]
|
|
163
|
+
if (lead === undefined) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
const leadSlot = recHeadSlotOf(head, lead)
|
|
167
|
+
const slot = recHeadSlotOf(built.rule, column)
|
|
168
|
+
if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
|
|
169
|
+
throw errors.new(
|
|
170
|
+
`rec ${name}: every rule derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in this rule (a head column joins only class-equal slots; bare pairs only with bare)`
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
140
174
|
}
|
|
141
175
|
data.rules.push(built.rule)
|
|
142
176
|
return makeRawRec<Name>(state, name, data)
|
|
@@ -147,7 +181,8 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
|
|
|
147
181
|
}
|
|
148
182
|
|
|
149
183
|
/**
|
|
150
|
-
* The rec handles' trusted admission seam (the
|
|
184
|
+
* The rec handles' trusted admission seam (the pattern's home is
|
|
185
|
+
* `isTypedScope` in query/lower.ts):
|
|
151
186
|
* the checkable fact — the handle owns exactly the rec data it names — is
|
|
152
187
|
* verified before the raw handle is admitted at its typed face.
|
|
153
188
|
*/
|
package/src/query/run.ts
CHANGED
|
@@ -8,20 +8,22 @@
|
|
|
8
8
|
* values — the marshal boundary is pure both ways: the engine computed
|
|
9
9
|
* the answer under the prepared head, so a decoded row that carries every
|
|
10
10
|
* select column IS a row (the trusted read seam), and nothing is asserted
|
|
11
|
-
* on any value.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* on any value. A CLOSED answer column decodes id → handle NAME through
|
|
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
|
|
14
|
+
* rows speak the vocabulary exactly as scans and gets do. Answers are
|
|
15
|
+
* SETS — no order or limit exists anywhere; hosts sort. The `Prepared`
|
|
16
|
+
* VALUE itself (no lifecycle, GC-reclaimed plan) lives in `#db.ts`.
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import * as errors from "@superbuilders/errors"
|
|
20
|
+
import { handleOf } from "#marshal.ts"
|
|
17
21
|
import type { FactValue, QueryParam, TaggedValue } from "#native.ts"
|
|
18
22
|
import type { SelectColumn } from "#query/atom.ts"
|
|
23
|
+
import { ALLEN_ALL_BITS } from "#query/atom.ts"
|
|
19
24
|
import { taggedCmpLiteral } from "#query/lower.ts"
|
|
20
25
|
import type { ParamEntry } from "#query/scope.ts"
|
|
21
26
|
|
|
22
|
-
/** The 13-bit Allen mask ceiling (`bumbledb/crates/bumbledb/src/allen.rs`: bits above the low 13 are unrepresentable). */
|
|
23
|
-
const ALLEN_ALL_BITS = (1 << 13) - 1
|
|
24
|
-
|
|
25
27
|
/** Tags one supplied mask-param value. */
|
|
26
28
|
function wireMask(name: string, value: unknown): TaggedValue {
|
|
27
29
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > ALLEN_ALL_BITS) {
|
|
@@ -45,10 +47,23 @@ function wireValue(entry: ParamEntry, context: string, value: unknown): TaggedVa
|
|
|
45
47
|
* in registry order (= the lowering's dense `ParamId`s). A missing entry
|
|
46
48
|
* is a typed error naming the param; values tag by the anchoring use's
|
|
47
49
|
* structural type; a set param takes a readonly array (the empty set is
|
|
48
|
-
* legal and matches nothing — the engine's rule).
|
|
50
|
+
* legal and matches nothing — the engine's rule). A MEMBERSHIP-ARRAY
|
|
51
|
+
* entry (`members` present — a literal set folded into the program) is
|
|
52
|
+
* supplied by the SDK itself: each handle name rides the one
|
|
53
|
+
* roster-verification point (`taggedHandleId`, through `wireValue`) and
|
|
54
|
+
* crosses as the same `{ kind: "set", values }` a bound `r.inSet` param
|
|
55
|
+
* crosses as — the host's params object is never consulted for it.
|
|
49
56
|
*/
|
|
50
57
|
function wireParams(entries: readonly ParamEntry[], supplied: Readonly<Record<string, unknown>>): QueryParam[] {
|
|
51
58
|
return entries.map(function wireOne(entry): QueryParam {
|
|
59
|
+
if (entry.members !== undefined) {
|
|
60
|
+
return {
|
|
61
|
+
kind: "set",
|
|
62
|
+
values: entry.members.map(function wireMember(member, index) {
|
|
63
|
+
return wireValue(entry, `membership array ${entry.name}[${index}]`, member)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
52
67
|
const value = supplied[entry.name]
|
|
53
68
|
if (value === undefined) {
|
|
54
69
|
throw errors.new(`execute params object is missing param ${entry.name}`)
|
|
@@ -90,7 +105,9 @@ function isAnswerRow<Row>(
|
|
|
90
105
|
/**
|
|
91
106
|
* Decodes positional answer rows (column order = the program's head order
|
|
92
107
|
* = the select's written order) to named, frozen row objects of bare
|
|
93
|
-
* structural values.
|
|
108
|
+
* structural values. A closed column lifts its row id back to the handle
|
|
109
|
+
* NAME through the marshal's bijection — an out-of-roster id is the same
|
|
110
|
+
* pointed throw a fact decode gives, never a silent fallback.
|
|
94
111
|
*/
|
|
95
112
|
function decodeAnswers<Row>(select: readonly SelectColumn[], rows: FactValue[][]): Row[] {
|
|
96
113
|
return rows.map(function decodeRow(row) {
|
|
@@ -103,7 +120,8 @@ function decodeAnswers<Row>(select: readonly SelectColumn[], rows: FactValue[][]
|
|
|
103
120
|
if (cell === undefined) {
|
|
104
121
|
throw errors.new(`query answer cell ${ordinal} (${column.name}) is absent`)
|
|
105
122
|
}
|
|
106
|
-
decoded[column.name] =
|
|
123
|
+
decoded[column.name] =
|
|
124
|
+
column.closed === undefined ? cell : handleOf(`query answer column ${column.name}`, column.closed, cell)
|
|
107
125
|
})
|
|
108
126
|
Object.freeze(decoded)
|
|
109
127
|
if (!isAnswerRow<Row>(select, decoded)) {
|
package/src/query/scope.ts
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* inference ride.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import * as errors from "@superbuilders/errors"
|
|
23
22
|
import type { AnyField, Infer } from "#fields.ts"
|
|
23
|
+
import { rosterOf } from "#fields.ts"
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* The runtime discriminant of query term values. Host literals (bigints,
|
|
@@ -110,57 +110,6 @@ function makeVar<const Name extends string>(name: Name): Var<Name> {
|
|
|
110
110
|
return Object.freeze(value)
|
|
111
111
|
}
|
|
112
112
|
|
|
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
|
-
/**
|
|
117
|
-
* The trusted seam of the vars mint: every requested name reads back as an
|
|
118
|
-
* own var term of exactly that name — verified 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).
|
|
122
|
-
*/
|
|
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
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
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.
|
|
144
|
-
*/
|
|
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 })
|
|
156
|
-
}
|
|
157
|
-
Object.freeze(out)
|
|
158
|
-
if (!varsMinted<Names[number]>(out, names)) {
|
|
159
|
-
throw errors.new("vars: variable minting incomplete")
|
|
160
|
-
}
|
|
161
|
-
return out
|
|
162
|
-
}
|
|
163
|
-
|
|
164
113
|
/** Builds one scalar-parameter term. */
|
|
165
114
|
function makeParam<const Name extends string>(name: Name): Param<Name> {
|
|
166
115
|
const value: Param<Name> = { [term]: "param", name }
|
|
@@ -228,25 +177,38 @@ type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : u
|
|
|
228
177
|
/** Reads an interval descriptor's element kind; `undefined` on scalar kinds. */
|
|
229
178
|
type ElementOf<F extends AnyField> = F extends { readonly element: infer E } ? E : undefined
|
|
230
179
|
|
|
180
|
+
/** Reads a closed reference's handle union; `undefined` on every non-closed kind (the roster IS descriptor structure). */
|
|
181
|
+
type RosterOf<F extends AnyField> = F extends {
|
|
182
|
+
readonly closed: { readonly handles: readonly (infer H extends string)[] }
|
|
183
|
+
}
|
|
184
|
+
? H
|
|
185
|
+
: undefined
|
|
186
|
+
|
|
231
187
|
/**
|
|
232
188
|
* The join judgment: two bound slots join iff their descriptors' structure
|
|
233
|
-
* agrees (kind, width label, interval element
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
189
|
+
* agrees (kind, width label, interval element, and the closed ROSTER — a
|
|
190
|
+
* closed reference pairs only with the same vocabulary, never with a bare
|
|
191
|
+
* u64: the roster keys every closed judgment downstream, so a join across
|
|
192
|
+
* it would decode/order/translate incoherently by binding order) AND their
|
|
193
|
+
* law-computed classes agree — same class name joins, and bare
|
|
194
|
+
* (`undefined`) pairs only with bare (ruling 3: a field in no law has no
|
|
195
|
+
* class; a bare↔classed pairing refuses). The class names come off the
|
|
196
|
+
* SCHEMA type's class map — the statements are the typing; no descriptor
|
|
197
|
+
* label beyond the roster exists to compare.
|
|
238
198
|
*/
|
|
239
199
|
type JoinOk<A extends ClassedField, B extends ClassedField> = [
|
|
240
200
|
A["field"]["kind"],
|
|
241
201
|
A["class"],
|
|
242
202
|
WidthOf<A["field"]>,
|
|
243
|
-
ElementOf<A["field"]
|
|
244
|
-
|
|
245
|
-
|
|
203
|
+
ElementOf<A["field"]>,
|
|
204
|
+
RosterOf<A["field"]>
|
|
205
|
+
] extends [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>, RosterOf<B["field"]>]
|
|
206
|
+
? [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>, RosterOf<B["field"]>] extends [
|
|
246
207
|
A["field"]["kind"],
|
|
247
208
|
A["class"],
|
|
248
209
|
WidthOf<A["field"]>,
|
|
249
|
-
ElementOf<A["field"]
|
|
210
|
+
ElementOf<A["field"]>,
|
|
211
|
+
RosterOf<A["field"]>
|
|
250
212
|
]
|
|
251
213
|
? true
|
|
252
214
|
: false
|
|
@@ -255,7 +217,8 @@ type JoinOk<A extends ClassedField, B extends ClassedField> = [
|
|
|
255
217
|
/**
|
|
256
218
|
* The runtime twin of {@link JoinOk}: two bound slots join iff descriptor
|
|
257
219
|
* structure and class agree — the same comparison the type tier makes,
|
|
258
|
-
* judged on the honest runtime values (the descriptor
|
|
220
|
+
* judged on the honest runtime values (the descriptor, the roster by VALUE
|
|
221
|
+
* IDENTITY — vocabulary identity is value identity — and the schema
|
|
259
222
|
* value's frozen class map). The rule builders throw through this on a
|
|
260
223
|
* class-unequal variable reuse, so the wall holds for untyped callers too,
|
|
261
224
|
* not only where the compiler can see.
|
|
@@ -265,17 +228,31 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
|
|
|
265
228
|
const widthB = "width" in b.field ? b.field.width : undefined
|
|
266
229
|
const elementA = "element" in a.field ? a.field.element : undefined
|
|
267
230
|
const elementB = "element" in b.field ? b.field.element : undefined
|
|
268
|
-
|
|
231
|
+
const rosterA = rosterOf(a.field)
|
|
232
|
+
const rosterB = rosterOf(b.field)
|
|
233
|
+
return (
|
|
234
|
+
a.field.kind === b.field.kind &&
|
|
235
|
+
a.class === b.class &&
|
|
236
|
+
widthA === widthB &&
|
|
237
|
+
elementA === elementB &&
|
|
238
|
+
rosterA === rosterB
|
|
239
|
+
)
|
|
269
240
|
}
|
|
270
241
|
|
|
271
242
|
/**
|
|
272
243
|
* Renders one bound slot for join-mismatch diagnostics — the structural
|
|
273
|
-
* kind in the schema grammar's spelling
|
|
274
|
-
*
|
|
244
|
+
* kind in the schema grammar's spelling (a closed reference names its
|
|
245
|
+
* vocabulary: the roster is part of the structure being compared) plus the
|
|
246
|
+
* slot's law-computed class (`u64 in class Holder.id`; a lawless slot
|
|
247
|
+
* renders `bare`).
|
|
275
248
|
*/
|
|
276
249
|
function renderFieldKind(slot: ClassedField): string {
|
|
277
250
|
const field = slot.field
|
|
278
251
|
let base: string = field.kind
|
|
252
|
+
const roster = rosterOf(field)
|
|
253
|
+
if (roster !== undefined) {
|
|
254
|
+
base = `u64 referencing ${roster.name}`
|
|
255
|
+
}
|
|
279
256
|
if (field.kind === "bytes") {
|
|
280
257
|
base = `bytes<${field.width}>`
|
|
281
258
|
}
|
|
@@ -287,7 +264,10 @@ function renderFieldKind(slot: ClassedField): string {
|
|
|
287
264
|
|
|
288
265
|
/**
|
|
289
266
|
* What a PARAM anchored at field `F` accepts at execution: the field's
|
|
290
|
-
* bare value type, exactly
|
|
267
|
+
* bare value type, exactly — at a CLOSED-reference field that is the
|
|
268
|
+
* handle-name union (`"DirectPass" | "Failed"`), translated name → row id
|
|
269
|
+
* at execute through the one roster-verification point
|
|
270
|
+
* (`taggedHandleId`). At an interval field the engine resolves the
|
|
291
271
|
* bivalent anchor to the INTERVAL reading (value equality) — the point
|
|
292
272
|
* reading of a param is spelled `pointIn(r.param(...), w)`, whose sibling
|
|
293
273
|
* anchors it element-typed.
|
|
@@ -304,13 +284,17 @@ type InferredOf<T> = T extends { readonly [inferred]?: infer S } ? Exclude<S, un
|
|
|
304
284
|
* positions) — the op keeps literal tagging op-aware at `pointIn`
|
|
305
285
|
* (the bug-hunt fix, preserved). `anchor` is `undefined` only on a query
|
|
306
286
|
* built but not yet anchored by any rule; lowering and the wire both refuse
|
|
307
|
-
* that state typed.
|
|
287
|
+
* that state typed. `members` is present exactly on a MEMBERSHIP-ARRAY
|
|
288
|
+
* entry (a literal set at a closed field, folded into the program): the
|
|
289
|
+
* SDK itself translates and supplies the set at every execute — the entry
|
|
290
|
+
* is never read from, and never demanded of, the host's params object.
|
|
308
291
|
*/
|
|
309
292
|
interface ParamEntry {
|
|
310
293
|
readonly name: string
|
|
311
294
|
readonly shape: "value" | "set" | "mask"
|
|
312
295
|
readonly anchor: AnyField | "measure" | undefined
|
|
313
296
|
readonly op: "binding" | "eq" | "ne" | "lt" | "le" | "gt" | "ge" | "pointIn" | "allen"
|
|
297
|
+
readonly members: readonly string[] | undefined
|
|
314
298
|
}
|
|
315
299
|
|
|
316
300
|
export type {
|
|
@@ -329,8 +313,7 @@ export type {
|
|
|
329
313
|
SetParam,
|
|
330
314
|
ShapeOf,
|
|
331
315
|
UnionToIntersection,
|
|
332
|
-
Var
|
|
333
|
-
VarsRecord
|
|
316
|
+
Var
|
|
334
317
|
}
|
|
335
318
|
export {
|
|
336
319
|
fieldJoins,
|
|
@@ -341,7 +324,6 @@ export {
|
|
|
341
324
|
makeParam,
|
|
342
325
|
makeSetParam,
|
|
343
326
|
makeVar,
|
|
344
|
-
makeVars,
|
|
345
327
|
renderFieldKind,
|
|
346
328
|
term
|
|
347
329
|
}
|
package/src/relation.ts
CHANGED
|
@@ -2,51 +2,60 @@
|
|
|
2
2
|
* `relation()` — the ordinary-relation half of the theory's signature. A
|
|
3
3
|
* relation value is a frozen plain object carrying its name, its ordered
|
|
4
4
|
* field descriptors (declaration order = ordinal ids, the macro's law),
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* and — since selections are the relation's own vocabulary — `where()`,
|
|
6
|
+
* which resolves a selection into lowered bindings eagerly (handles
|
|
7
|
+
* verified against their roster at construction). Fields are addressed by
|
|
8
|
+
* NAME everywhere — statements (`on(R, "holder")`), selections, and match
|
|
9
|
+
* records all spell the field's own name, checked by type
|
|
10
|
+
* (`FaceFields`/`MatchShape`). `Fact<>`/`InsertFact<>` are the inferred
|
|
11
|
+
* row object types at BARE structural value types (no brands): fresh
|
|
12
|
+
* fields are optional on insert input (omit-to-mint) and present on read
|
|
11
13
|
* (resupply-to-preserve-identity), typed exactly.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
import * as errors from "@superbuilders/errors"
|
|
15
|
-
import type { OneOf } from "#face.ts"
|
|
16
17
|
import { type AnyField, assertDeclarationOrderKey, type Infer, literalOf } from "#fields.ts"
|
|
17
|
-
import type
|
|
18
|
+
import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
18
19
|
|
|
19
20
|
/** Flattens an intersection into one displayed object type (hover legibility). */
|
|
20
21
|
type Flatten<T> = { [K in keyof T]: T[K] }
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Resolves one selection entry to its lowered literal set: a plain ARRAY
|
|
25
|
+
* (detected by `Array.isArray` — no field's value type is an array;
|
|
26
|
+
* `Uint8Array` is not one) becomes a disjunctive set, anything else the
|
|
27
|
+
* bare literal. The degenerate sets are construction errors, each
|
|
28
|
+
* self-locating (`context` names the relation and field) — the empty set
|
|
29
|
+
* selects nothing, the one-element set is the bare literal respelled, and
|
|
30
|
+
* a DUPLICATE literal (judged on the canonical rendering — the engine's
|
|
31
|
+
* own duplicate test, reached here first so its index-speak twin at
|
|
32
|
+
* `Db.create` stays unreachable from this surface) is the same respelling
|
|
33
|
+
* in disguise (the canonical-utterance law; the old set combinator's
|
|
34
|
+
* signature made the length degenerates unwritable, and the refusals here
|
|
35
|
+
* are that law's runtime seat). The lowered set — `{ kind: "many",
|
|
36
|
+
* literals }` — is byte-identical to what the combinator produced, so no
|
|
37
|
+
* fingerprint moves.
|
|
28
38
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return Object.freeze(literalOf(field, literal))
|
|
39
|
+
function resolveEntry(context: string, field: AnyField, entry: unknown): LiteralSetSpec {
|
|
40
|
+
if (Array.isArray(entry)) {
|
|
41
|
+
if (entry.length < 2) {
|
|
42
|
+
throw errors.new(
|
|
43
|
+
entry.length === 0
|
|
44
|
+
? `${context}: an empty literal set selects nothing — write the selection you mean`
|
|
45
|
+
: `${context}: a one-element literal set is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
const seen = new Set<string>()
|
|
49
|
+
const literals: LiteralSpec[] = entry.map(function lowerSetLiteral(literal: unknown) {
|
|
50
|
+
const lowered = Object.freeze(literalOf(field, literal))
|
|
51
|
+
const rendered = renderLiteral(lowered)
|
|
52
|
+
if (seen.has(rendered)) {
|
|
53
|
+
throw errors.new(
|
|
54
|
+
`${context}: the literal set spells ${rendered} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
seen.add(rendered)
|
|
58
|
+
return lowered
|
|
50
59
|
})
|
|
51
60
|
return Object.freeze({ kind: "many", literals: Object.freeze(literals) })
|
|
52
61
|
}
|
|
@@ -78,7 +87,9 @@ function resolveSelection(
|
|
|
78
87
|
if (declared === undefined) {
|
|
79
88
|
throw errors.new(`relation ${name} has no field ${fieldName}`)
|
|
80
89
|
}
|
|
81
|
-
bindings.push(
|
|
90
|
+
bindings.push(
|
|
91
|
+
Object.freeze({ field: fieldName, set: resolveEntry(`relation ${name}.${fieldName}`, declared.field, entry) })
|
|
92
|
+
)
|
|
82
93
|
}
|
|
83
94
|
if (bindings.length === 0) {
|
|
84
95
|
throw errors.new(
|
|
@@ -91,22 +102,6 @@ function resolveSelection(
|
|
|
91
102
|
/** The field block of a relation: field name to field descriptor. */
|
|
92
103
|
type FieldsShape = Record<string, AnyField>
|
|
93
104
|
|
|
94
|
-
/**
|
|
95
|
-
* A typed field reference (`Account.fields.holder`) — the value statements,
|
|
96
|
-
* selections, and queries address a field through. Purely positional
|
|
97
|
-
* (relation name + field name); the field's descriptor is read off the
|
|
98
|
-
* relation's schema type structurally.
|
|
99
|
-
*/
|
|
100
|
-
interface FieldRef<Rel extends string, Name extends string> {
|
|
101
|
-
readonly relation: Rel
|
|
102
|
-
readonly field: Name
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** The typed field-reference record of a relation. */
|
|
106
|
-
type FieldRefs<RName extends string, Fields extends FieldsShape> = {
|
|
107
|
-
readonly [K in keyof Fields & string]: FieldRef<RName, K>
|
|
108
|
-
}
|
|
109
|
-
|
|
110
105
|
/** One declared field: name plus its descriptor, in declaration order. */
|
|
111
106
|
interface RelationField {
|
|
112
107
|
readonly name: string
|
|
@@ -131,13 +126,15 @@ interface SelectionBinding {
|
|
|
131
126
|
|
|
132
127
|
/**
|
|
133
128
|
* The `where()` argument: per field, a bare structural literal of that
|
|
134
|
-
* field's value type (a closed
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
129
|
+
* field's value type (a closed reference's literal IS its handle name —
|
|
130
|
+
* `"Savings"`, verified against the roster at construction), a plain
|
|
131
|
+
* ARRAY of such literals read disjunctively — `kind: ["Checking",
|
|
132
|
+
* "Savings"]` — or a `span(start, end)` interval literal. Membership is
|
|
133
|
+
* an array, never an operator (the drizzle law); equality-only by
|
|
134
|
+
* construction: no operator parameter exists anywhere.
|
|
138
135
|
*/
|
|
139
136
|
type SelectionInput<Fields extends FieldsShape> = {
|
|
140
|
-
readonly [K in keyof Fields]?: Infer<Fields[K]> |
|
|
137
|
+
readonly [K in keyof Fields]?: Infer<Fields[K]> | readonly Infer<Fields[K]>[]
|
|
141
138
|
}
|
|
142
139
|
|
|
143
140
|
/** A relation with a selection applied — what `on()` consumes as a σ-carrying source. */
|
|
@@ -150,7 +147,6 @@ interface Selected<Name extends string, Fields extends FieldsShape> {
|
|
|
150
147
|
interface Relation<Name extends string, Fields extends FieldsShape> {
|
|
151
148
|
readonly name: Name
|
|
152
149
|
readonly data: RelationData
|
|
153
|
-
readonly fields: FieldRefs<Name, Fields>
|
|
154
150
|
where(selection: SelectionInput<Fields>): Selected<Name, Fields>
|
|
155
151
|
}
|
|
156
152
|
|
|
@@ -208,14 +204,6 @@ function relation<const Name extends string, Fields extends FieldsShape>(
|
|
|
208
204
|
ordered.push(Object.freeze({ name: fieldName, field }))
|
|
209
205
|
}
|
|
210
206
|
const data: RelationData = Object.freeze({ name, fields: Object.freeze(ordered) })
|
|
211
|
-
const refs: Record<string, unknown> = {}
|
|
212
|
-
for (const declared of ordered) {
|
|
213
|
-
refs[declared.name] = Object.freeze({ relation: name, field: declared.name })
|
|
214
|
-
}
|
|
215
|
-
Object.freeze(refs)
|
|
216
|
-
if (!refsComplete<Name, Fields>(refs, fields)) {
|
|
217
|
-
throw errors.new(`relation ${name}: field-reference construction incomplete`)
|
|
218
|
-
}
|
|
219
207
|
const holder: { value: Relation<Name, Fields> | undefined } = { value: undefined }
|
|
220
208
|
function where(selection: SelectionInput<Fields>): Selected<Name, Fields> {
|
|
221
209
|
const owner = holder.value
|
|
@@ -227,7 +215,7 @@ function relation<const Name extends string, Fields extends FieldsShape>(
|
|
|
227
215
|
selection: resolveSelection(name, ordered, Object.entries(selection))
|
|
228
216
|
})
|
|
229
217
|
}
|
|
230
|
-
const value: Relation<Name, Fields> = Object.freeze({ name, data,
|
|
218
|
+
const value: Relation<Name, Fields> = Object.freeze({ name, data, where })
|
|
231
219
|
holder.value = value
|
|
232
220
|
return value
|
|
233
221
|
}
|
|
@@ -236,8 +224,6 @@ export type {
|
|
|
236
224
|
AnyRelation,
|
|
237
225
|
AnySelected,
|
|
238
226
|
Fact,
|
|
239
|
-
FieldRef,
|
|
240
|
-
FieldRefs,
|
|
241
227
|
FieldsShape,
|
|
242
228
|
FreshKeys,
|
|
243
229
|
InsertFact,
|
package/src/schema.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
import * as errors from "@superbuilders/errors"
|
|
13
13
|
import type { AnyClosed } from "#closed.ts"
|
|
14
|
+
import { isClosedMember, sealedFieldOf } from "#closed.ts"
|
|
14
15
|
import type { FaceData } from "#face.ts"
|
|
15
|
-
import {
|
|
16
|
+
import { assertDeclarationOrderKey, rosterOf } from "#fields.ts"
|
|
16
17
|
import { type ClassesOf, classesComplete, computeClasses, type LawfulStatements, type SchemaClasses } from "#law.ts"
|
|
17
18
|
import type { AnyRelation } from "#relation.ts"
|
|
18
19
|
import type { LiteralSetSpec, LiteralSpec } from "#spec.ts"
|
|
@@ -34,7 +35,7 @@ function collectImplied(name: string, relations: SchemaRelations): Set<string> {
|
|
|
34
35
|
`schema ${name}: record key ${recordKey} holds relation ${member.name} — the key must equal the relation's declared name`
|
|
35
36
|
)
|
|
36
37
|
}
|
|
37
|
-
if (
|
|
38
|
+
if (isClosedMember(member)) {
|
|
38
39
|
implied.add(`${member.name}(id) -> ${member.name}`)
|
|
39
40
|
continue
|
|
40
41
|
}
|
|
@@ -75,33 +76,6 @@ function verifyMembership(name: string, relations: SchemaRelations, statement: S
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
/** Finds a face's field descriptor by name, across both relation kinds. */
|
|
79
|
-
function faceField(face: FaceData, fieldName: string): AnyField | undefined {
|
|
80
|
-
const data = face.owner.data
|
|
81
|
-
if ("handles" in data) {
|
|
82
|
-
const column = data.columns.find(function byName(candidate) {
|
|
83
|
-
return candidate.name === fieldName
|
|
84
|
-
})
|
|
85
|
-
return column?.field
|
|
86
|
-
}
|
|
87
|
-
const declared = data.fields.find(function byName(candidate) {
|
|
88
|
-
return candidate.name === fieldName
|
|
89
|
-
})
|
|
90
|
-
return declared?.field
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* The roster a field resolves handles through: present exactly on a closed
|
|
95
|
-
* reference descriptor (the structural `closed` property — S1's
|
|
96
|
-
* `ClosedIdField`), absent on every other field kind.
|
|
97
|
-
*/
|
|
98
|
-
function rosterOf(field: AnyField | undefined): ClosedRoster | undefined {
|
|
99
|
-
if (field !== undefined && "closed" in field) {
|
|
100
|
-
return field.closed
|
|
101
|
-
}
|
|
102
|
-
return undefined
|
|
103
|
-
}
|
|
104
|
-
|
|
105
79
|
/** Flattens one binding's literal set into its literals. */
|
|
106
80
|
function bindingLiterals(set: LiteralSetSpec): readonly LiteralSpec[] {
|
|
107
81
|
if (set.kind === "one") {
|
|
@@ -122,7 +96,7 @@ function verifyBindingHandles(
|
|
|
122
96
|
binding: { readonly field: string; readonly set: LiteralSetSpec },
|
|
123
97
|
rendered: string
|
|
124
98
|
): void {
|
|
125
|
-
const roster = rosterOf(
|
|
99
|
+
const roster = rosterOf(sealedFieldOf(face.owner, binding.field))
|
|
126
100
|
for (const literal of bindingLiterals(binding.set)) {
|
|
127
101
|
if (literal.kind !== "handle") {
|
|
128
102
|
continue
|
|
@@ -177,7 +151,7 @@ function closedTargetOf(statements: readonly Statement[], owner: string, field:
|
|
|
177
151
|
source.projection[0] === field &&
|
|
178
152
|
target.projection.length === 1 &&
|
|
179
153
|
target.projection[0] === "id" &&
|
|
180
|
-
|
|
154
|
+
isClosedMember(target.owner)
|
|
181
155
|
) {
|
|
182
156
|
return target.owner.name
|
|
183
157
|
}
|
|
@@ -227,11 +201,11 @@ function verifyClosedReferenceBinding(
|
|
|
227
201
|
if (!spellsHandle) {
|
|
228
202
|
return
|
|
229
203
|
}
|
|
230
|
-
const roster = rosterOf(
|
|
204
|
+
const roster = rosterOf(sealedFieldOf(face.owner, binding.field))
|
|
231
205
|
if (roster === undefined) {
|
|
232
206
|
return
|
|
233
207
|
}
|
|
234
|
-
if (
|
|
208
|
+
if (isClosedMember(face.owner) && binding.field === "id") {
|
|
235
209
|
return
|
|
236
210
|
}
|
|
237
211
|
const resolved = closedTargetOf(statements, face.owner.name, binding.field)
|
package/src/spec.ts
CHANGED
|
@@ -62,8 +62,9 @@ type LiteralSpec =
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* One σ binding's right side: a single literal or a literal set (read
|
|
65
|
-
* disjunctively). The SDK's selection
|
|
66
|
-
*
|
|
65
|
+
* disjunctively). The SDK's selection resolver refuses the degenerate sets
|
|
66
|
+
* (a membership array needs two members — the empty set selects nothing,
|
|
67
|
+
* the one-element set is the bare literal respelled), so a lowered `many`
|
|
67
68
|
* always carries ≥ 2 literals.
|
|
68
69
|
*/
|
|
69
70
|
type LiteralSetSpec =
|