@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/lower.ts
CHANGED
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import * as errors from "@superbuilders/errors"
|
|
29
|
-
import
|
|
30
|
-
import {
|
|
29
|
+
import { sealedFieldsOf } from "#closed.ts"
|
|
30
|
+
import type { AnyField, ClosedRoster } from "#fields.ts"
|
|
31
|
+
import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
|
|
31
32
|
import type { ClassRecordOf, SchemaClasses } from "#law.ts"
|
|
32
33
|
import type {
|
|
33
34
|
AtomIr,
|
|
@@ -68,7 +69,7 @@ import type {
|
|
|
68
69
|
SelectEntryData,
|
|
69
70
|
TreeData
|
|
70
71
|
} from "#query/atom.ts"
|
|
71
|
-
import { allen, and,
|
|
72
|
+
import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
|
|
72
73
|
import type {
|
|
73
74
|
ClassedField,
|
|
74
75
|
EnvShape,
|
|
@@ -88,13 +89,12 @@ import {
|
|
|
88
89
|
makeParam,
|
|
89
90
|
makeSetParam,
|
|
90
91
|
makeVar,
|
|
91
|
-
makeVars,
|
|
92
92
|
renderFieldKind,
|
|
93
93
|
term
|
|
94
94
|
} from "#query/scope.ts"
|
|
95
95
|
import type { CheckNameSelect, CheckSelect, RowOfNameSelect, RowOfSelect, SelectEntry } from "#query/select.ts"
|
|
96
96
|
import { argMax, argMin, count, countDistinct, max, min, pack, sum } from "#query/select.ts"
|
|
97
|
-
import type { FieldsShape
|
|
97
|
+
import type { FieldsShape } from "#relation.ts"
|
|
98
98
|
import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
|
|
99
99
|
|
|
100
100
|
/**
|
|
@@ -198,8 +198,6 @@ type CheckIdbVars<Env extends EnvShape, V, Head extends HeadShape = undefined> =
|
|
|
198
198
|
interface TermOps {
|
|
199
199
|
/** Declares/names one variable: typed by the field it first binds; reuse joins. */
|
|
200
200
|
readonly var: typeof makeVar
|
|
201
|
-
/** Mints several variables at once — `const { service, w } = r.vars("service", "w")`: each name typed exactly; duplicates refuse. */
|
|
202
|
-
readonly vars: typeof makeVars
|
|
203
201
|
/** Names one scalar parameter: typed by its use; the key of the execute params object. */
|
|
204
202
|
readonly param: typeof makeParam
|
|
205
203
|
/** Names one ∈-set parameter (the IR's `ParamSet`): bound to a readonly array at execution. */
|
|
@@ -215,7 +213,6 @@ interface TermOps {
|
|
|
215
213
|
readonly gt: typeof gt
|
|
216
214
|
readonly ge: typeof ge
|
|
217
215
|
readonly pointIn: typeof pointIn
|
|
218
|
-
readonly covers: typeof covers
|
|
219
216
|
readonly allen: typeof allen
|
|
220
217
|
readonly and: typeof and
|
|
221
218
|
readonly or: typeof or
|
|
@@ -431,7 +428,6 @@ interface QueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses
|
|
|
431
428
|
/** The frozen constructor vocabulary every rule builder spreads. */
|
|
432
429
|
const termOps: TermOps = Object.freeze({
|
|
433
430
|
var: makeVar,
|
|
434
|
-
vars: makeVars,
|
|
435
431
|
param: makeParam,
|
|
436
432
|
inSet: makeSetParam,
|
|
437
433
|
maskParam: makeMaskParam,
|
|
@@ -443,7 +439,6 @@ const termOps: TermOps = Object.freeze({
|
|
|
443
439
|
gt,
|
|
444
440
|
ge,
|
|
445
441
|
pointIn,
|
|
446
|
-
covers,
|
|
447
442
|
allen,
|
|
448
443
|
and,
|
|
449
444
|
or,
|
|
@@ -480,19 +475,54 @@ interface ResolvedBindings {
|
|
|
480
475
|
}
|
|
481
476
|
|
|
482
477
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
478
|
+
* Judges one membership ARRAY at a binding position — legal exactly at a
|
|
479
|
+
* CLOSED-reference field (the owner ruling: ordinary u64/str membership is
|
|
480
|
+
* spelled through `r.inSet` params; literal arrays are the closed
|
|
481
|
+
* vocabulary's spelling), holding ≥ 2 DISTINCT handle names (the
|
|
482
|
+
* degenerate sets are refusals: empty selects nothing, one element is the
|
|
483
|
+
* bare literal respelled, and a duplicate member is the same respelling in
|
|
484
|
+
* disguise — write each member once). The returned name is
|
|
485
|
+
* CONTENT-ADDRESSED (vocabulary + the member SET — the key sorts a copy,
|
|
486
|
+
* so two spellings of one set, reordered or not, share one dense
|
|
487
|
+
* `ParamId`); the members are shape-checked strings here and
|
|
488
|
+
* roster-verified at the one verification point (`taggedHandleId`) when
|
|
489
|
+
* the SDK supplies the set at execute — the same moment a bound `r.inSet`
|
|
490
|
+
* param's members are judged.
|
|
490
491
|
*/
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
492
|
+
function membershipSet(
|
|
493
|
+
context: string,
|
|
494
|
+
field: AnyField,
|
|
495
|
+
value: readonly unknown[]
|
|
496
|
+
): { readonly name: string; readonly members: readonly string[] } {
|
|
497
|
+
const roster = rosterOf(field)
|
|
498
|
+
if (roster === undefined) {
|
|
499
|
+
throw errors.new(
|
|
500
|
+
`${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
|
|
501
|
+
)
|
|
494
502
|
}
|
|
495
|
-
|
|
503
|
+
if (value.length === 0) {
|
|
504
|
+
throw errors.new(`${context}: an empty membership array selects nothing — write the query you mean`)
|
|
505
|
+
}
|
|
506
|
+
if (value.length === 1) {
|
|
507
|
+
throw errors.new(
|
|
508
|
+
`${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
const seen = new Set<string>()
|
|
512
|
+
const members = value.map(function memberName(member) {
|
|
513
|
+
if (typeof member !== "string") {
|
|
514
|
+
throw literalShapeError(context, `a ${roster.name} handle name (string)`, member)
|
|
515
|
+
}
|
|
516
|
+
if (seen.has(member)) {
|
|
517
|
+
throw errors.new(
|
|
518
|
+
`${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
seen.add(member)
|
|
522
|
+
return member
|
|
523
|
+
})
|
|
524
|
+
const key = [...members].sort()
|
|
525
|
+
return { name: `∈ ${roster.name} ${JSON.stringify(key)}`, members: Object.freeze(members) }
|
|
496
526
|
}
|
|
497
527
|
|
|
498
528
|
/**
|
|
@@ -514,7 +544,7 @@ function resolveBindings(
|
|
|
514
544
|
const vars: Array<{ readonly name: string; readonly slot: ClassedField }> = []
|
|
515
545
|
const uses: ParamUse[] = []
|
|
516
546
|
const relationClasses = classes[relation.name]
|
|
517
|
-
const ordered =
|
|
547
|
+
const ordered = sealedFieldsOf(relation)
|
|
518
548
|
for (const [fieldName, value] of Object.entries(bindings)) {
|
|
519
549
|
if (value === undefined) {
|
|
520
550
|
continue
|
|
@@ -539,14 +569,26 @@ function resolveBindings(
|
|
|
539
569
|
case "param": {
|
|
540
570
|
bound = Object.freeze({ kind: "param" as const, name: value.name })
|
|
541
571
|
uses.push(
|
|
542
|
-
Object.freeze({
|
|
572
|
+
Object.freeze({
|
|
573
|
+
name: value.name,
|
|
574
|
+
shape: "value" as const,
|
|
575
|
+
anchor: declared.field,
|
|
576
|
+
op: "binding" as const,
|
|
577
|
+
members: undefined
|
|
578
|
+
})
|
|
543
579
|
)
|
|
544
580
|
break
|
|
545
581
|
}
|
|
546
582
|
case "setParam": {
|
|
547
583
|
bound = Object.freeze({ kind: "setParam" as const, name: value.name })
|
|
548
584
|
uses.push(
|
|
549
|
-
Object.freeze({
|
|
585
|
+
Object.freeze({
|
|
586
|
+
name: value.name,
|
|
587
|
+
shape: "set" as const,
|
|
588
|
+
anchor: declared.field,
|
|
589
|
+
op: "binding" as const,
|
|
590
|
+
members: undefined
|
|
591
|
+
})
|
|
550
592
|
)
|
|
551
593
|
break
|
|
552
594
|
}
|
|
@@ -559,6 +601,18 @@ function resolveBindings(
|
|
|
559
601
|
`${context}.${fieldName}: the measure is not a field-typed value — it lives in comparisons and select entries`
|
|
560
602
|
)
|
|
561
603
|
}
|
|
604
|
+
} else if (Array.isArray(value)) {
|
|
605
|
+
const set = membershipSet(`${context}.${fieldName}`, declared.field, value)
|
|
606
|
+
bound = Object.freeze({ kind: "literalSet" as const, name: set.name, members: set.members })
|
|
607
|
+
uses.push(
|
|
608
|
+
Object.freeze({
|
|
609
|
+
name: set.name,
|
|
610
|
+
shape: "set" as const,
|
|
611
|
+
anchor: declared.field,
|
|
612
|
+
op: "binding" as const,
|
|
613
|
+
members: set.members
|
|
614
|
+
})
|
|
615
|
+
)
|
|
562
616
|
} else {
|
|
563
617
|
bound = Object.freeze({ kind: "literal" as const, value })
|
|
564
618
|
}
|
|
@@ -651,7 +705,8 @@ function sideUses(
|
|
|
651
705
|
name: side.name,
|
|
652
706
|
shape: side.kind === "param" ? ("value" as const) : ("set" as const),
|
|
653
707
|
anchor,
|
|
654
|
-
op
|
|
708
|
+
op,
|
|
709
|
+
members: undefined
|
|
655
710
|
})
|
|
656
711
|
)
|
|
657
712
|
}
|
|
@@ -671,7 +726,13 @@ function condDataOf(cond: AnyCond, varFields: Readonly<Record<string, ClassedFie
|
|
|
671
726
|
} else if (isTerm(maskValue) && maskValue[term] === "maskParam") {
|
|
672
727
|
mask = Object.freeze({ kind: "param" as const, name: maskValue.name })
|
|
673
728
|
uses.push(
|
|
674
|
-
Object.freeze({
|
|
729
|
+
Object.freeze({
|
|
730
|
+
name: maskValue.name,
|
|
731
|
+
shape: "mask" as const,
|
|
732
|
+
anchor: undefined,
|
|
733
|
+
op: "allen" as const,
|
|
734
|
+
members: undefined
|
|
735
|
+
})
|
|
675
736
|
)
|
|
676
737
|
} else {
|
|
677
738
|
throw errors.new("allen: the mask position takes a 13-bit mask number or a maskParam")
|
|
@@ -742,14 +803,27 @@ function isAggregateEntry(
|
|
|
742
803
|
return typeof value === "object" && value !== null && "agg" in value
|
|
743
804
|
}
|
|
744
805
|
|
|
745
|
-
/**
|
|
806
|
+
/**
|
|
807
|
+
* Classifies one select entry into its named answer column. The `closed`
|
|
808
|
+
* slice is resolved LATER, at rule completion (`completeRule`), where the
|
|
809
|
+
* rule's `varFields` are in hand — until then every column is provisionally
|
|
810
|
+
* bare.
|
|
811
|
+
*/
|
|
746
812
|
function selectColumnOf(entry: unknown): SelectColumn {
|
|
747
813
|
if (typeof entry === "string") {
|
|
748
|
-
return Object.freeze({
|
|
814
|
+
return Object.freeze({
|
|
815
|
+
name: entry,
|
|
816
|
+
entry: Object.freeze({ kind: "var" as const, over: entry }),
|
|
817
|
+
closed: undefined
|
|
818
|
+
})
|
|
749
819
|
}
|
|
750
820
|
if (isTerm(entry)) {
|
|
751
821
|
if (entry[term] === "duration") {
|
|
752
|
-
return Object.freeze({
|
|
822
|
+
return Object.freeze({
|
|
823
|
+
name: entry.name,
|
|
824
|
+
entry: Object.freeze({ kind: "measure" as const, over: entry.name }),
|
|
825
|
+
closed: undefined
|
|
826
|
+
})
|
|
753
827
|
}
|
|
754
828
|
throw errors.new(
|
|
755
829
|
`query select: a ${entry[term]} is not projectable — select takes variable names, duration(v), or aggregates`
|
|
@@ -768,7 +842,11 @@ function aggregateColumnOf(entry: {
|
|
|
768
842
|
readonly key: unknown
|
|
769
843
|
}): SelectColumn {
|
|
770
844
|
function column(name: string, agg: AggData): SelectColumn {
|
|
771
|
-
return Object.freeze({
|
|
845
|
+
return Object.freeze({
|
|
846
|
+
name,
|
|
847
|
+
entry: Object.freeze({ kind: "aggregate" as const, agg: Object.freeze(agg) }),
|
|
848
|
+
closed: undefined
|
|
849
|
+
})
|
|
772
850
|
}
|
|
773
851
|
const over = entry.over
|
|
774
852
|
switch (entry.agg) {
|
|
@@ -809,6 +887,26 @@ function aggregateColumnOf(entry: {
|
|
|
809
887
|
}
|
|
810
888
|
}
|
|
811
889
|
|
|
890
|
+
/**
|
|
891
|
+
* The orderable ban's pointed refusal (`docs/architecture/10-data-model.md`
|
|
892
|
+
* § orderability): a closed reference is equality-and-membership only —
|
|
893
|
+
* its declaration-id order is an encoding accident, so every
|
|
894
|
+
* order-comparison and fold position refuses it. The construction-time
|
|
895
|
+
* twin of the type tier's `OrderVarOk` exclusion, so the wall holds for
|
|
896
|
+
* untyped callers too (the engine cannot backstop this one: the wire IR
|
|
897
|
+
* carries plain u64s, no rosters).
|
|
898
|
+
*/
|
|
899
|
+
function closedOrderError(context: string, position: string, vocabulary: string): Error {
|
|
900
|
+
return errors.new(
|
|
901
|
+
`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (docs/architecture/10-data-model.md; equality, membership, and counting remain)`
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/** The comparison ops under the orderable ban (order roster + point membership — every order-comparison position). */
|
|
906
|
+
function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
|
|
907
|
+
return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
|
|
908
|
+
}
|
|
909
|
+
|
|
812
910
|
/** Requires a var name to be bound by a relation atom of the rule. */
|
|
813
911
|
function assertBound(context: string, varFields: Readonly<Record<string, ClassedField>>, name: string): ClassedField {
|
|
814
912
|
const slot = varFields[name]
|
|
@@ -841,7 +939,11 @@ function validateCond(context: string, varFields: Readonly<Record<string, Classe
|
|
|
841
939
|
if (cond.kind === "cmp") {
|
|
842
940
|
for (const side of [cond.lhs, cond.rhs]) {
|
|
843
941
|
if (side.kind === "var") {
|
|
844
|
-
assertBound(context, varFields, side.name)
|
|
942
|
+
const slot = assertBound(context, varFields, side.name)
|
|
943
|
+
const roster = rosterOf(slot.field)
|
|
944
|
+
if (isOrderOp(cond.op) && roster !== undefined) {
|
|
945
|
+
throw closedOrderError(context, `the ${cond.op} side ${side.name}`, roster.name)
|
|
946
|
+
}
|
|
845
947
|
}
|
|
846
948
|
if (side.kind === "measure") {
|
|
847
949
|
assertIntervalBound(context, varFields, side.name)
|
|
@@ -887,22 +989,63 @@ function validateColumn(
|
|
|
887
989
|
return
|
|
888
990
|
case "fold": {
|
|
889
991
|
if (typeof agg.over === "string") {
|
|
890
|
-
assertBound(`${context} select ${column.name}`, varFields, agg.over)
|
|
992
|
+
const slot = assertBound(`${context} select ${column.name}`, varFields, agg.over)
|
|
993
|
+
const roster = rosterOf(slot.field)
|
|
994
|
+
if (roster !== undefined) {
|
|
995
|
+
throw closedOrderError(`${context} select ${column.name}`, `the ${agg.fold} input ${agg.over}`, roster.name)
|
|
996
|
+
}
|
|
891
997
|
return
|
|
892
998
|
}
|
|
893
999
|
assertIntervalBound(`${context} select ${column.name}`, varFields, agg.over.duration)
|
|
894
1000
|
return
|
|
895
1001
|
}
|
|
896
|
-
case "arg":
|
|
1002
|
+
case "arg": {
|
|
897
1003
|
assertBound(`${context} select ${column.name}`, varFields, agg.over)
|
|
898
|
-
assertBound(`${context} select ${column.name}`, varFields, agg.key)
|
|
1004
|
+
const key = assertBound(`${context} select ${column.name}`, varFields, agg.key)
|
|
1005
|
+
const keyRoster = rosterOf(key.field)
|
|
1006
|
+
if (keyRoster !== undefined) {
|
|
1007
|
+
throw closedOrderError(
|
|
1008
|
+
`${context} select ${column.name}`,
|
|
1009
|
+
`the ${agg.direction} key ${agg.key}`,
|
|
1010
|
+
keyRoster.name
|
|
1011
|
+
)
|
|
1012
|
+
}
|
|
899
1013
|
return
|
|
1014
|
+
}
|
|
900
1015
|
case "pack":
|
|
901
1016
|
assertIntervalBound(`${context} select ${column.name}`, varFields, agg.over)
|
|
902
1017
|
return
|
|
903
1018
|
}
|
|
904
1019
|
}
|
|
905
1020
|
|
|
1021
|
+
/**
|
|
1022
|
+
* Resolves the roster one select column decodes through: a projected var,
|
|
1023
|
+
* or an Arg-carried payload, bound at a closed-referencing field carries
|
|
1024
|
+
* that field's roster (read off `varFields` — the same slot the domain
|
|
1025
|
+
* machinery reads), and `decodeAnswers` lifts the column's row ids back to
|
|
1026
|
+
* handle NAMES through it — the runtime twin of the row type's `Infer`
|
|
1027
|
+
* claim. Every other entry decodes bare: counts are counts, the measure
|
|
1028
|
+
* and `pack` are never closed, and a closed FOLD is banned outright
|
|
1029
|
+
* ({@link closedOrderError}) before this resolution runs.
|
|
1030
|
+
*/
|
|
1031
|
+
function selectClosedOf(
|
|
1032
|
+
varFields: Readonly<Record<string, ClassedField>>,
|
|
1033
|
+
entry: SelectEntryData
|
|
1034
|
+
): ClosedRoster | undefined {
|
|
1035
|
+
let over: string | undefined
|
|
1036
|
+
if (entry.kind === "var") {
|
|
1037
|
+
over = entry.over
|
|
1038
|
+
} else if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
1039
|
+
over = entry.agg.over
|
|
1040
|
+
} else {
|
|
1041
|
+
over = undefined
|
|
1042
|
+
}
|
|
1043
|
+
if (over === undefined) {
|
|
1044
|
+
return undefined
|
|
1045
|
+
}
|
|
1046
|
+
return rosterOf(varFields[over]?.field)
|
|
1047
|
+
}
|
|
1048
|
+
|
|
906
1049
|
/**
|
|
907
1050
|
* Completes one rule: classifies the select record (written order = answer
|
|
908
1051
|
* column order, names must be declaration-order-safe keys), and validates
|
|
@@ -969,7 +1112,15 @@ function completeRule(context: string, state: RuleBuildState, columns: readonly
|
|
|
969
1112
|
}
|
|
970
1113
|
return Object.freeze({
|
|
971
1114
|
items: state.items,
|
|
972
|
-
select: Object.freeze(
|
|
1115
|
+
select: Object.freeze(
|
|
1116
|
+
columns.map(function enrichColumn(column): SelectColumn {
|
|
1117
|
+
return Object.freeze({
|
|
1118
|
+
name: column.name,
|
|
1119
|
+
entry: column.entry,
|
|
1120
|
+
closed: selectClosedOf(state.varFields, column.entry)
|
|
1121
|
+
})
|
|
1122
|
+
})
|
|
1123
|
+
),
|
|
973
1124
|
varFields: state.varFields,
|
|
974
1125
|
paramUses: state.paramUses
|
|
975
1126
|
})
|
|
@@ -1091,10 +1242,12 @@ function makeRawScope(context: ChainContext): RawScope {
|
|
|
1091
1242
|
}
|
|
1092
1243
|
|
|
1093
1244
|
/**
|
|
1094
|
-
* The
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1245
|
+
* The rule builders' trusted admission seam — THE home of the
|
|
1246
|
+
* trusted-admission-seam pattern the other mint guards cite (the face,
|
|
1247
|
+
* class-map, axiom-readback, rec-handle, and query-value seams): the raw
|
|
1248
|
+
* builder is one runtime shape for every context, and this guard verifies
|
|
1249
|
+
* the checkable fact — the builder verbs exist — before the value is
|
|
1250
|
+
* admitted at its TYPED face. The type-level
|
|
1098
1251
|
* judgments (domain-equal joins, boundness, the recursion cut) live in the
|
|
1099
1252
|
* interfaces themselves; the runtime twin of every one of them is a
|
|
1100
1253
|
* construction-time validation in this module.
|
|
@@ -1132,6 +1285,11 @@ interface ProgramState {
|
|
|
1132
1285
|
sealed: boolean
|
|
1133
1286
|
}
|
|
1134
1287
|
|
|
1288
|
+
/** Renders one head column's closed slice for the rule-alignment check's diagnostics. */
|
|
1289
|
+
function renderClosedSlice(closed: ClosedRoster | undefined): string {
|
|
1290
|
+
return closed === undefined ? "a bare value" : `a ${closed.name} reference`
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1135
1293
|
/** Renders one head column's signature for the rule-alignment check. */
|
|
1136
1294
|
function headSignature(column: SelectColumn): string {
|
|
1137
1295
|
const entry = column.entry
|
|
@@ -1148,32 +1306,104 @@ function headSignature(column: SelectColumn): string {
|
|
|
1148
1306
|
return `${column.name}:${agg.op}`
|
|
1149
1307
|
}
|
|
1150
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* The classed slot one answer column's VALUES flow from, resolved through
|
|
1311
|
+
* the rule's own binding environment: a projected var's first-binding slot,
|
|
1312
|
+
* or an Arg-carried payload's (`argMax`/`argMin` carry `over` verbatim —
|
|
1313
|
+
* the same two shapes the closed slice lifts). Counts, folds, `pack` and
|
|
1314
|
+
* the measure derive numbers/intervals rather than carrying a slot's ids,
|
|
1315
|
+
* so they resolve no slot (`undefined`).
|
|
1316
|
+
*/
|
|
1317
|
+
function headSlotOf(rule: RuleData, column: SelectColumn): ClassedField | undefined {
|
|
1318
|
+
const entry = column.entry
|
|
1319
|
+
if (entry.kind === "var") {
|
|
1320
|
+
return rule.varFields[entry.over]
|
|
1321
|
+
}
|
|
1322
|
+
if (entry.kind === "aggregate" && entry.agg.op === "arg") {
|
|
1323
|
+
return rule.varFields[entry.agg.over]
|
|
1324
|
+
}
|
|
1325
|
+
return undefined
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/** The roster a param anchor carries: present exactly on a closed-reference field anchor (rides THE one `rosterOf` reader). */
|
|
1329
|
+
function anchorRosterOf(anchor: AnyField | "measure" | undefined): ClosedRoster | undefined {
|
|
1330
|
+
return anchor === "measure" ? undefined : rosterOf(anchor)
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/** Renders one param anchor's closedness for the registry's coherence diagnostics. */
|
|
1334
|
+
function renderParamAnchor(roster: ClosedRoster | undefined): string {
|
|
1335
|
+
return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1151
1338
|
/**
|
|
1152
1339
|
* Folds every rule's param uses (recs in declaration order first, output
|
|
1153
1340
|
* rules last — exactly the lowering walk) into the query's registry:
|
|
1154
1341
|
* first use mints the dense `ParamId`, the first FIELD-ANCHORED use types
|
|
1155
|
-
* the wire, and one name must keep one shape
|
|
1342
|
+
* the wire, and one name must keep one shape AND one closedness — every
|
|
1343
|
+
* anchored use of one name must agree on the roster (value identity), so a
|
|
1344
|
+
* param anchored at a closed reference is GUARANTEED to ride the one
|
|
1345
|
+
* roster-verification point (`taggedHandleId`) at execute; a name anchored
|
|
1346
|
+
* both at a closed reference and at a non-closed position (or at two
|
|
1347
|
+
* vocabularies) is refused here, because the wire would translate only the
|
|
1348
|
+
* first anchor's reading (the type tier intersects the uses to `never`;
|
|
1349
|
+
* this is its runtime twin for untyped callers). A param whose anchor is a
|
|
1350
|
+
* CLOSED reference must never sit in an order-comparison position — the
|
|
1351
|
+
* anchor types its value a handle name and the engine would order the
|
|
1352
|
+
* translated row ids, so the pairing is refused here too (the registry is
|
|
1353
|
+
* the one place a name's every use and its anchoring field meet).
|
|
1156
1354
|
*/
|
|
1157
1355
|
function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]): readonly ParamEntry[] {
|
|
1158
1356
|
const order: string[] = []
|
|
1159
|
-
const byName = new Map<
|
|
1357
|
+
const byName = new Map<
|
|
1358
|
+
string,
|
|
1359
|
+
{
|
|
1360
|
+
shape: ParamEntry["shape"]
|
|
1361
|
+
anchor: ParamEntry["anchor"]
|
|
1362
|
+
op: ParamEntry["op"]
|
|
1363
|
+
members: ParamEntry["members"]
|
|
1364
|
+
orderOp: "lt" | "le" | "gt" | "ge" | "pointIn" | undefined
|
|
1365
|
+
}
|
|
1366
|
+
>()
|
|
1160
1367
|
function fold(uses: readonly ParamUse[]): void {
|
|
1161
1368
|
for (const use of uses) {
|
|
1162
1369
|
const existing = byName.get(use.name)
|
|
1163
1370
|
if (existing === undefined) {
|
|
1164
1371
|
order.push(use.name)
|
|
1165
|
-
byName.set(use.name, {
|
|
1372
|
+
byName.set(use.name, {
|
|
1373
|
+
shape: use.shape,
|
|
1374
|
+
anchor: use.anchor,
|
|
1375
|
+
op: use.op,
|
|
1376
|
+
members: use.members,
|
|
1377
|
+
orderOp: isOrderOp(use.op) ? use.op : undefined
|
|
1378
|
+
})
|
|
1166
1379
|
continue
|
|
1167
1380
|
}
|
|
1381
|
+
if ((existing.members === undefined) !== (use.members === undefined)) {
|
|
1382
|
+
throw errors.new(
|
|
1383
|
+
`query param ${use.name} collides with a membership array's registry entry — name the param differently`
|
|
1384
|
+
)
|
|
1385
|
+
}
|
|
1168
1386
|
if (existing.shape !== use.shape) {
|
|
1169
1387
|
throw errors.new(
|
|
1170
1388
|
`query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`
|
|
1171
1389
|
)
|
|
1172
1390
|
}
|
|
1391
|
+
if (existing.anchor !== undefined && use.anchor !== undefined) {
|
|
1392
|
+
const registered = anchorRosterOf(existing.anchor)
|
|
1393
|
+
const anchored = anchorRosterOf(use.anchor)
|
|
1394
|
+
if (registered !== anchored) {
|
|
1395
|
+
throw errors.new(
|
|
1396
|
+
`query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`
|
|
1397
|
+
)
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1173
1400
|
if (existing.anchor === undefined && use.anchor !== undefined) {
|
|
1174
1401
|
existing.anchor = use.anchor
|
|
1175
1402
|
existing.op = use.op
|
|
1176
1403
|
}
|
|
1404
|
+
if (existing.orderOp === undefined && isOrderOp(use.op)) {
|
|
1405
|
+
existing.orderOp = use.op
|
|
1406
|
+
}
|
|
1177
1407
|
}
|
|
1178
1408
|
}
|
|
1179
1409
|
for (const rec of recs) {
|
|
@@ -1190,7 +1420,11 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
|
|
|
1190
1420
|
if (entry === undefined) {
|
|
1191
1421
|
throw errors.new(`query param ${name} lost its registry entry`)
|
|
1192
1422
|
}
|
|
1193
|
-
|
|
1423
|
+
const anchorRoster = anchorRosterOf(entry.anchor)
|
|
1424
|
+
if (entry.orderOp !== undefined && anchorRoster !== undefined) {
|
|
1425
|
+
throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, anchorRoster.name)
|
|
1426
|
+
}
|
|
1427
|
+
return Object.freeze({ name, shape: entry.shape, anchor: entry.anchor, op: entry.op, members: entry.members })
|
|
1194
1428
|
})
|
|
1195
1429
|
)
|
|
1196
1430
|
}
|
|
@@ -1221,6 +1455,36 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
|
|
|
1221
1455
|
`every rule of a query derives the same head — rule 0 selects (${signature}), rule ${index} selects (${candidate})`
|
|
1222
1456
|
)
|
|
1223
1457
|
}
|
|
1458
|
+
// The closed slice is part of the head too: one answer column decodes
|
|
1459
|
+
// through one roster, so a union whose rules bind a column at
|
|
1460
|
+
// different vocabularies (or one closed, one bare — the ids would
|
|
1461
|
+
// mistranslate silently) is refused pointed. Vocabulary identity is
|
|
1462
|
+
// value identity, the SDK's membership rule everywhere.
|
|
1463
|
+
rule.select.forEach(function verifyClosedSlice(column, position) {
|
|
1464
|
+
const lead = first.select[position]
|
|
1465
|
+
if (lead !== undefined && column.closed !== lead.closed) {
|
|
1466
|
+
throw errors.new(
|
|
1467
|
+
`every rule of a query derives the same head — the answer column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`
|
|
1468
|
+
)
|
|
1469
|
+
}
|
|
1470
|
+
// The law-class wall on the union head: one answer column is one
|
|
1471
|
+
// value space, so the classed slot each rule binds the column at
|
|
1472
|
+
// must join across rules — the SAME fieldJoins judgment every
|
|
1473
|
+
// join/eq/negated-atom position enforces. The SDK holds this wall
|
|
1474
|
+
// because the wire IR carries no domains: the engine cannot
|
|
1475
|
+
// backstop it, and without it a union mixes (say) Holder ids and
|
|
1476
|
+
// Account ids in one column the consumer reads as one id space.
|
|
1477
|
+
if (lead === undefined) {
|
|
1478
|
+
return
|
|
1479
|
+
}
|
|
1480
|
+
const leadSlot = headSlotOf(first, lead)
|
|
1481
|
+
const slot = headSlotOf(rule, column)
|
|
1482
|
+
if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
|
|
1483
|
+
throw errors.new(
|
|
1484
|
+
`every rule of a query derives the same head — the answer column ${lead.name} unions domain-unequal fields: bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in rule ${index} (a union column joins only class-equal slots; bare pairs only with bare)`
|
|
1485
|
+
)
|
|
1486
|
+
}
|
|
1487
|
+
})
|
|
1224
1488
|
})
|
|
1225
1489
|
const data: QueryData = Object.freeze({
|
|
1226
1490
|
recs: Object.freeze([...recs]),
|
|
@@ -1241,7 +1505,7 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
|
|
|
1241
1505
|
}
|
|
1242
1506
|
|
|
1243
1507
|
/**
|
|
1244
|
-
* The query values' trusted admission seam (the
|
|
1508
|
+
* The query values' trusted admission seam (the {@link isTypedScope} pattern):
|
|
1245
1509
|
* the checkable fact — the value was assembled over the identical theory —
|
|
1246
1510
|
* is verified before the raw value is admitted at its typed face.
|
|
1247
1511
|
*/
|
|
@@ -1287,43 +1551,31 @@ function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
|
|
|
1287
1551
|
return start
|
|
1288
1552
|
}
|
|
1289
1553
|
|
|
1290
|
-
/** The typed shape refusal of the literal tagger — a genuine failure, never data. */
|
|
1291
|
-
function literalShapeError(context: string, expected: string, value: unknown): Error {
|
|
1292
|
-
return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
/** Narrows an interval-shaped literal (a plain `{ start, end }` bigint pair). */
|
|
1296
|
-
function isIntervalShaped(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
|
|
1297
|
-
return (
|
|
1298
|
-
typeof value === "object" &&
|
|
1299
|
-
value !== null &&
|
|
1300
|
-
"start" in value &&
|
|
1301
|
-
"end" in value &&
|
|
1302
|
-
typeof value.start === "bigint" &&
|
|
1303
|
-
typeof value.end === "bigint"
|
|
1304
|
-
)
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
1554
|
/**
|
|
1308
|
-
* Tags one closed-reference literal: the
|
|
1309
|
-
*
|
|
1310
|
-
* make any
|
|
1311
|
-
* never handle
|
|
1555
|
+
* Tags one closed-reference literal: the handle NAME, verified against the
|
|
1556
|
+
* roster (the belt the wide fallback type cannot provide — structural
|
|
1557
|
+
* values make any string spellable here) and translated to its
|
|
1558
|
+
* declaration-order row id, tagged u64 — queries cross ids, never handle
|
|
1559
|
+
* names; the wire is untouched. THE single roster-verification point of
|
|
1560
|
+
* the query surface: atom-binding literals, comparison literals,
|
|
1561
|
+
* execute-time params, and membership-array members all reach it (never
|
|
1562
|
+
* duplicate the check per call site).
|
|
1312
1563
|
*/
|
|
1313
1564
|
function taggedHandleId(
|
|
1314
1565
|
context: string,
|
|
1315
1566
|
closed: { readonly name: string; readonly handles: readonly string[] },
|
|
1316
1567
|
value: unknown
|
|
1317
1568
|
): TaggedValue {
|
|
1318
|
-
if (typeof value !== "
|
|
1319
|
-
throw literalShapeError(context, `a ${closed.name} handle
|
|
1569
|
+
if (typeof value !== "string") {
|
|
1570
|
+
throw literalShapeError(context, `a ${closed.name} handle name (string)`, value)
|
|
1320
1571
|
}
|
|
1321
|
-
|
|
1572
|
+
const id = closed.handles.indexOf(value)
|
|
1573
|
+
if (id < 0) {
|
|
1322
1574
|
throw errors.new(
|
|
1323
|
-
`${context}:
|
|
1575
|
+
`${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
1324
1576
|
)
|
|
1325
1577
|
}
|
|
1326
|
-
return { kind: "u64", value }
|
|
1578
|
+
return { kind: "u64", value: BigInt(id) }
|
|
1327
1579
|
}
|
|
1328
1580
|
|
|
1329
1581
|
/**
|
|
@@ -1338,7 +1590,7 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
|
|
|
1338
1590
|
}
|
|
1339
1591
|
return { kind: "i64", value }
|
|
1340
1592
|
}
|
|
1341
|
-
if (
|
|
1593
|
+
if (isIntervalValue(value)) {
|
|
1342
1594
|
if (element === "u64") {
|
|
1343
1595
|
return { kind: "intervalU64", start: value.start, end: value.end }
|
|
1344
1596
|
}
|
|
@@ -1356,8 +1608,9 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
|
|
|
1356
1608
|
* tagged u64 after a roster verification.
|
|
1357
1609
|
*/
|
|
1358
1610
|
function taggedLiteral(context: string, field: AnyField, value: unknown): TaggedValue {
|
|
1359
|
-
|
|
1360
|
-
|
|
1611
|
+
const roster = rosterOf(field)
|
|
1612
|
+
if (roster !== undefined) {
|
|
1613
|
+
return taggedHandleId(context, roster, value)
|
|
1361
1614
|
}
|
|
1362
1615
|
switch (field.kind) {
|
|
1363
1616
|
case "bool": {
|
|
@@ -1382,6 +1635,18 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
|
|
|
1382
1635
|
if (typeof value !== "string") {
|
|
1383
1636
|
throw literalShapeError(context, "string", value)
|
|
1384
1637
|
}
|
|
1638
|
+
/**
|
|
1639
|
+
* The marshal's bijection law at the query seam (`marshal.ts`
|
|
1640
|
+
* cellOf): a lone surrogate would be lossily replaced with
|
|
1641
|
+
* U+FFFD at the bridge's UTF-8 crossing and silently match a
|
|
1642
|
+
* fact the typed write surface can never store — distinct JS
|
|
1643
|
+
* strings collapsing to one wire query. This is the single
|
|
1644
|
+
* seam every query string literal, string param
|
|
1645
|
+
* (`taggedCmpLiteral`), and membership member lowers through.
|
|
1646
|
+
*/
|
|
1647
|
+
if (!value.isWellFormed()) {
|
|
1648
|
+
throw literalShapeError(context, "well-formed string", value)
|
|
1649
|
+
}
|
|
1385
1650
|
return { kind: "string", value }
|
|
1386
1651
|
}
|
|
1387
1652
|
case "bytes": {
|
|
@@ -1399,11 +1664,11 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
|
|
|
1399
1664
|
* Tags one host literal at a COMPARISON or PARAM position, where the
|
|
1400
1665
|
* SIBLING anchors the type: a measure sibling is u64, an interval-field
|
|
1401
1666
|
* sibling contributes its element domain (so both a point literal in
|
|
1402
|
-
* `
|
|
1667
|
+
* `pointIn` and a `span` literal in `allen` tag correctly), a scalar
|
|
1403
1668
|
* sibling its own type. At `pointIn` the operand order is interval-left,
|
|
1404
1669
|
* point-right (`ir::CmpOp::PointIn`), so an interval-shaped literal
|
|
1405
|
-
* beside a scalar element-typed sibling is the LEGAL
|
|
1406
|
-
* `
|
|
1670
|
+
* beside a scalar element-typed sibling is the LEGAL interval operand of
|
|
1671
|
+
* `pointIn(t, span(...))` and tags as the interval of the sibling's
|
|
1407
1672
|
* element domain; under every other operator an interval shape against a
|
|
1408
1673
|
* scalar sibling stays refused (the engine's IllegalComparison — the
|
|
1409
1674
|
* bug-hunt fix, preserved op-aware).
|
|
@@ -1420,14 +1685,14 @@ function taggedCmpLiteral(
|
|
|
1420
1685
|
}
|
|
1421
1686
|
return { kind: "u64", value }
|
|
1422
1687
|
}
|
|
1423
|
-
if (
|
|
1688
|
+
if (rosterOf(sibling) === undefined && sibling.kind === "interval") {
|
|
1424
1689
|
return taggedAtElementDomain(context, sibling.element, value)
|
|
1425
1690
|
}
|
|
1426
1691
|
if (
|
|
1427
1692
|
op === "pointIn" &&
|
|
1428
|
-
|
|
1693
|
+
rosterOf(sibling) === undefined &&
|
|
1429
1694
|
(sibling.kind === "u64" || sibling.kind === "i64") &&
|
|
1430
|
-
|
|
1695
|
+
isIntervalValue(value)
|
|
1431
1696
|
) {
|
|
1432
1697
|
return taggedAtElementDomain(context, sibling.kind, value)
|
|
1433
1698
|
}
|
|
@@ -1477,7 +1742,7 @@ function paramIdOf(ctx: LowerContext, name: string): number {
|
|
|
1477
1742
|
* Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
|
|
1478
1743
|
* same edb source — its ordinal is its record-declaration slot exactly like
|
|
1479
1744
|
* an ordinary relation's — with field ordinals over the SEALED shape: `id`
|
|
1480
|
-
* at 0, each payload column at its declared index + 1 (`
|
|
1745
|
+
* at 0, each payload column at its declared index + 1 (`sealedFieldsOf`
|
|
1481
1746
|
* carries the shift; the lowering golden pins it).
|
|
1482
1747
|
*/
|
|
1483
1748
|
function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
@@ -1491,7 +1756,7 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
|
1491
1756
|
if (relationId === undefined) {
|
|
1492
1757
|
throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`)
|
|
1493
1758
|
}
|
|
1494
|
-
const ordered =
|
|
1759
|
+
const ordered = sealedFieldsOf(atom.relation)
|
|
1495
1760
|
const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
|
|
1496
1761
|
const ordinal = ordered.findIndex(function byName(candidate) {
|
|
1497
1762
|
return candidate.name === binding.field
|
|
@@ -1504,7 +1769,12 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
|
|
|
1504
1769
|
return { source: { kind: "edb", relation: relationId }, bindings }
|
|
1505
1770
|
}
|
|
1506
1771
|
|
|
1507
|
-
/**
|
|
1772
|
+
/**
|
|
1773
|
+
* Lowers one binding term. A membership ARRAY (`literalSet`) lowers to the
|
|
1774
|
+
* existing param-set term over its content-addressed registry entry — the
|
|
1775
|
+
* program IR is byte-identical to the same set spelled `r.inSet`; the SDK
|
|
1776
|
+
* supplies the translated member set itself at execute (`wireParams`).
|
|
1777
|
+
*/
|
|
1508
1778
|
function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEntry, ids: VarIds): TermIr {
|
|
1509
1779
|
const bound = binding.term
|
|
1510
1780
|
switch (bound.kind) {
|
|
@@ -1514,6 +1784,8 @@ function lowerBindingTerm(ctx: LowerContext, context: string, binding: BindingEn
|
|
|
1514
1784
|
return { kind: "param", param: paramIdOf(ctx, bound.name) }
|
|
1515
1785
|
case "setParam":
|
|
1516
1786
|
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
|
|
1787
|
+
case "literalSet":
|
|
1788
|
+
return { kind: "paramSet", param: paramIdOf(ctx, bound.name) }
|
|
1517
1789
|
case "literal":
|
|
1518
1790
|
return { kind: "literal", value: taggedLiteral(context, binding.data, bound.value) }
|
|
1519
1791
|
}
|