@bjornpagen/bumbledb 0.19.1 → 0.20.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 (73) hide show
  1. package/COOKBOOK.md +2 -0
  2. package/README.md +1 -0
  3. package/dist/closed.d.ts +9 -9
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +27 -18
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +8 -1
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +6 -0
  10. package/dist/db.js.map +1 -1
  11. package/dist/face.d.ts +18 -23
  12. package/dist/face.d.ts.map +1 -1
  13. package/dist/face.js.map +1 -1
  14. package/dist/fields.d.ts +54 -8
  15. package/dist/fields.d.ts.map +1 -1
  16. package/dist/fields.js +28 -1
  17. package/dist/fields.js.map +1 -1
  18. package/dist/index.d.ts +6 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/judgment.d.ts +13 -0
  23. package/dist/judgment.d.ts.map +1 -0
  24. package/dist/judgment.js +11 -0
  25. package/dist/judgment.js.map +1 -0
  26. package/dist/law.d.ts +2 -1
  27. package/dist/law.d.ts.map +1 -1
  28. package/dist/law.js.map +1 -1
  29. package/dist/marshal.d.ts +2 -2
  30. package/dist/marshal.d.ts.map +1 -1
  31. package/dist/marshal.js.map +1 -1
  32. package/dist/native.d.ts +168 -3
  33. package/dist/native.d.ts.map +1 -1
  34. package/dist/native.js +86 -2
  35. package/dist/native.js.map +1 -1
  36. package/dist/query/atom.d.ts +10 -7
  37. package/dist/query/atom.d.ts.map +1 -1
  38. package/dist/query/atom.js +1 -1
  39. package/dist/query/atom.js.map +1 -1
  40. package/dist/query/find.d.ts +1 -2
  41. package/dist/query/find.d.ts.map +1 -1
  42. package/dist/query/find.js.map +1 -1
  43. package/dist/query/lower.d.ts +3 -39
  44. package/dist/query/lower.d.ts.map +1 -1
  45. package/dist/query/lower.js +48 -19
  46. package/dist/query/lower.js.map +1 -1
  47. package/dist/query/scope.d.ts +43 -38
  48. package/dist/query/scope.d.ts.map +1 -1
  49. package/dist/query/scope.js +24 -15
  50. package/dist/query/scope.js.map +1 -1
  51. package/dist/spec.d.ts +1 -1
  52. package/dist/spec.d.ts.map +1 -1
  53. package/dist/spec.js.map +1 -1
  54. package/dist/statements.d.ts +1 -1
  55. package/dist/statements.d.ts.map +1 -1
  56. package/dist/statements.js +2 -2
  57. package/dist/statements.js.map +1 -1
  58. package/package.json +5 -4
  59. package/src/closed.ts +85 -51
  60. package/src/db.ts +15 -0
  61. package/src/face.ts +18 -24
  62. package/src/fields.ts +85 -7
  63. package/src/index.ts +43 -1
  64. package/src/judgment.ts +25 -0
  65. package/src/law.ts +2 -1
  66. package/src/marshal.ts +3 -3
  67. package/src/native.ts +320 -12
  68. package/src/query/atom.ts +6 -19
  69. package/src/query/find.ts +1 -16
  70. package/src/query/lower.ts +51 -32
  71. package/src/query/scope.ts +84 -65
  72. package/src/spec.ts +0 -1
  73. package/src/statements.ts +3 -4
@@ -1,6 +1,6 @@
1
1
  import * as errors from "@superbuilders/errors"
2
2
  import { sealedFieldsOf } from "#closed.ts"
3
- import type { AnyField, ClosedRoster } from "#fields.ts"
3
+ import type { AnyClosedRoster, AnyField } from "#fields.ts"
4
4
  import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
5
5
  import type { ClassRecordOf, SchemaClasses } from "#law.ts"
6
6
  import type {
@@ -61,6 +61,7 @@ import type {
61
61
  import {
62
62
  fieldAntiJoins,
63
63
  fieldJoins,
64
+ headFieldJoins,
64
65
  inferred,
65
66
  isTerm,
66
67
  makeParam,
@@ -85,9 +86,6 @@ interface RuleValue<Row, P extends ParamsRecord, Head extends HeadShape = undefi
85
86
 
86
87
  type AnyRuleValue = RuleValue<unknown, ParamsRecord, HeadShape>
87
88
 
88
- type HeadOf<T> =
89
- InferredOf<T> extends { readonly head: infer H extends Readonly<Record<string, ClassedField>> } ? H : undefined
90
-
91
89
  type InteriorBindingOk<V> = V extends AnyVar ? true : false
92
90
 
93
91
  type CheckInteriorBindings<B> = {
@@ -1171,7 +1169,7 @@ function makeRecRuleScope<Rels extends SchemaRelations, Classes extends SchemaCl
1171
1169
  return raw
1172
1170
  }
1173
1171
 
1174
- function renderClosedSlice(closed: ClosedRoster | undefined): string {
1172
+ function renderClosedSlice(closed: AnyClosedRoster | undefined): string {
1175
1173
  return closed === undefined ? "a bare value" : `a ${closed.name} reference`
1176
1174
  }
1177
1175
 
@@ -1187,7 +1185,7 @@ function headSignature(column: FindColumn): string {
1187
1185
  return `${column.name}:${agg.op}`
1188
1186
  }
1189
1187
 
1190
- function renderParamAnchor(roster: ClosedRoster | undefined): string {
1188
+ function renderParamAnchor(roster: AnyClosedRoster | undefined): string {
1191
1189
  return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`
1192
1190
  }
1193
1191
 
@@ -1307,7 +1305,15 @@ interface RawQuery {
1307
1305
  reach(name: string, arms: never): never
1308
1306
  }
1309
1307
 
1310
- function assertAlignedHeads(label: string, rules: readonly RuleData[]): void {
1308
+ /**
1309
+ * Verifies every rule derives the same head and returns the MERGED head:
1310
+ * the meet of the rules' column slots. A head column joins class-equal
1311
+ * slots, and — the anti-join law's head twin — same-identity u64 wire
1312
+ * with at least one side bare; when the bare arm admits the pair, the
1313
+ * merged column's class claim demotes to bare, so a consumer joining
1314
+ * the head cannot inherit provenance a rule never proved.
1315
+ */
1316
+ function alignedHeadOf(label: string, rules: readonly RuleData[]): readonly FindColumn[] {
1311
1317
  const first = rules[0]
1312
1318
  if (first === undefined) {
1313
1319
  throw errors.new(`${label} needs at least one rule`)
@@ -1330,13 +1336,33 @@ function assertAlignedHeads(label: string, rules: readonly RuleData[]): void {
1330
1336
  if (lead === undefined) {
1331
1337
  return
1332
1338
  }
1333
- if (lead.slot !== undefined && column.slot !== undefined && !fieldJoins(lead.slot, column.slot)) {
1339
+ if (lead.slot !== undefined && column.slot !== undefined && !headFieldJoins(lead.slot, column.slot)) {
1334
1340
  throw errors.new(
1335
- `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins only class-equal slots; bare pairs only with bare)`
1341
+ `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
1336
1342
  )
1337
1343
  }
1338
1344
  })
1339
1345
  })
1346
+ const merged = first.finds.map(function meetColumn(lead, position) {
1347
+ const slot = lead.slot
1348
+ if (slot === undefined || slot.class === undefined) {
1349
+ return lead
1350
+ }
1351
+ const demoted = rules.some(function bareElsewhere(rule) {
1352
+ const column = rule.finds[position]
1353
+ return column !== undefined && column.slot !== undefined && column.slot.class !== slot.class
1354
+ })
1355
+ if (!demoted) {
1356
+ return lead
1357
+ }
1358
+ return Object.freeze({
1359
+ name: lead.name,
1360
+ entry: lead.entry,
1361
+ closed: lead.closed,
1362
+ slot: Object.freeze({ field: slot.field, class: undefined })
1363
+ })
1364
+ })
1365
+ return Object.freeze(merged)
1340
1366
  }
1341
1367
 
1342
1368
  function afterMainError(what: string): Error {
@@ -1351,7 +1377,7 @@ function makeRawQuery(
1351
1377
  rec: RecData | undefined,
1352
1378
  rules: readonly RuleData[]
1353
1379
  ): RawQuery {
1354
- assertAlignedHeads("a query", rules)
1380
+ const mergedFinds = alignedHeadOf("a query", rules)
1355
1381
  const first = rules[0]
1356
1382
  if (first === undefined) {
1357
1383
  throw errors.new("a query needs at least one rule")
@@ -1366,7 +1392,7 @@ function makeRawQuery(
1366
1392
  kind: "cq" as const,
1367
1393
  interiors: frozenInteriors,
1368
1394
  rules: frozenRules,
1369
- finds: first.finds,
1395
+ finds: mergedFinds,
1370
1396
  params
1371
1397
  })
1372
1398
  : Object.freeze({
@@ -1374,7 +1400,7 @@ function makeRawQuery(
1374
1400
  interiors: frozenInteriors,
1375
1401
  rec,
1376
1402
  rules: frozenRules,
1377
- finds: first.finds,
1403
+ finds: mergedFinds,
1378
1404
  params
1379
1405
  })
1380
1406
  const value: RawQuery = {
@@ -1416,12 +1442,8 @@ function collectInterior<Rels extends SchemaRelations, Classes extends SchemaCla
1416
1442
  const rules = builds.map(function buildRule(buildOne) {
1417
1443
  return buildOne(makeInteriorRuleScope<Rels, Classes>(theory, env, name)).rule
1418
1444
  })
1419
- assertAlignedHeads(`interior ${name}`, rules)
1420
- const first = rules[0]
1421
- if (first === undefined) {
1422
- throw errors.new(`query: interior ${name} needs at least one rule`)
1423
- }
1424
- return Object.freeze({ name, finds: first.finds, rules: Object.freeze(rules) })
1445
+ const mergedFinds = alignedHeadOf(`interior ${name}`, rules)
1446
+ return Object.freeze({ name, finds: mergedFinds, rules: Object.freeze(rules) })
1425
1447
  }
1426
1448
 
1427
1449
  function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>(
@@ -1442,22 +1464,27 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
1442
1464
  const base = baseBuilds.map(function buildBase(buildOne) {
1443
1465
  return buildOne(makeRecRuleScope<Rels, Classes>(theory, baseEnv, handle, "rec-base")).rule
1444
1466
  })
1445
- assertAlignedHeads(`rec ${name}`, base)
1467
+ const baseFinds = alignedHeadOf(`rec ${name}`, base)
1446
1468
  const first = base[0]
1447
1469
  if (first === undefined) {
1448
1470
  throw errors.new(`query: rec ${name} has no base arms`)
1449
1471
  }
1450
- const firstFind = first.finds[0]
1472
+ const firstFind = baseFinds[0]
1451
1473
  if (firstFind === undefined) {
1452
1474
  throw errors.new(`query: rec ${name} has no head`)
1453
1475
  }
1454
- const finds: RecHead["finds"] = [firstFind, ...first.finds.slice(1)]
1476
+ const finds: RecHead["finds"] = [firstFind, ...baseFinds.slice(1)]
1455
1477
  const head: RecHead = Object.freeze({ name, finds })
1456
1478
  const recEnv: DerivedEnv = { interiors, rec: head }
1457
1479
  const rec = recBuilds.map(function buildRec(buildOne) {
1458
1480
  return buildOne(makeRecRuleScope<Rels, Classes>(theory, recEnv, head, "rec-arm")).rule
1459
1481
  })
1460
- assertAlignedHeads(`rec ${name}`, [...base, ...rec])
1482
+ const mergedAll = alignedHeadOf(`rec ${name}`, [...base, ...rec])
1483
+ const mergedFirst = mergedAll[0]
1484
+ if (mergedFirst === undefined) {
1485
+ throw errors.new(`query: rec ${name} has no head`)
1486
+ }
1487
+ const sealedFinds: RecHead["finds"] = [mergedFirst, ...mergedAll.slice(1)]
1461
1488
  const firstRec = rec[0]
1462
1489
  if (firstRec === undefined) {
1463
1490
  throw errors.new(`query: rec ${name} has no rec arms`)
@@ -1466,7 +1493,7 @@ function collectRec<Rels extends SchemaRelations, Classes extends SchemaClasses>
1466
1493
  const sealedRec: RecData["rec"] = [firstRec, ...rec.slice(1)]
1467
1494
  const recData: RecData = Object.freeze({
1468
1495
  name,
1469
- finds,
1496
+ finds: sealedFinds,
1470
1497
  base: sealedBase,
1471
1498
  rec: sealedRec
1472
1499
  })
@@ -1949,11 +1976,6 @@ function lowerQuery(q: AnyQuery): ParsedQuery {
1949
1976
  export type {
1950
1977
  AnyQuery,
1951
1978
  AnyRuleValue,
1952
- HeadOf,
1953
- HeadShape,
1954
- InteriorRuleChain,
1955
- InteriorRuleScope,
1956
- ParamsOf,
1957
1979
  Query,
1958
1980
  QueryData,
1959
1981
  QueryParams,
@@ -1963,12 +1985,9 @@ export type {
1963
1985
  QueryRuleChain,
1964
1986
  QueryRuleScope,
1965
1987
  QueryStart,
1966
- RawChain,
1967
- RawScope,
1968
1988
  RecRuleChain,
1969
1989
  RecRuleScope,
1970
- RowOf,
1971
1990
  RuleValue,
1972
1991
  TermOps
1973
1992
  }
1974
- export { lowerQuery, makeRawScope, query, taggedCmpLiteral, taggedLiteral }
1993
+ export { lowerQuery, query, taggedCmpLiteral }
@@ -1,8 +1,9 @@
1
1
  import * as errors from "@superbuilders/errors"
2
2
  import type { AnyClosed } from "#closed.ts"
3
3
  import { sealedFieldsOf } from "#closed.ts"
4
- import type { AnyField, Infer } from "#fields.ts"
5
- import { rosterOf } from "#fields.ts"
4
+ import type { AnyField, Infer, SignatureOf } from "#fields.ts"
5
+ import { rosterOf, signaturesAgree } from "#fields.ts"
6
+ import type { Same, SameLen } from "#judgment.ts"
6
7
  import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
7
8
  import type { QueryParam } from "#native.ts"
8
9
  import type { AnyRelation, RelationFields } from "#relation.ts"
@@ -19,7 +20,7 @@ type MatchFields<R extends MatchOwner> = R extends AnyClosed
19
20
  ? RelationFields<R>
20
21
  : never
21
22
 
22
- interface Var<F extends AnyField = AnyField, RN extends string = string, K extends string = string> {
23
+ interface Var<F extends AnyField, RN extends string, K extends string> {
23
24
  readonly [term]: "var"
24
25
  readonly owner: MatchOwner & { readonly name: RN }
25
26
  readonly column: K
@@ -27,19 +28,19 @@ interface Var<F extends AnyField = AnyField, RN extends string = string, K exten
27
28
  readonly label: string
28
29
  }
29
30
 
30
- type AnyVar = Var
31
+ type AnyVar = Var<AnyField, string, string>
31
32
 
32
- interface Param<Name extends string = string> {
33
+ interface Param<Name extends string> {
33
34
  readonly [term]: "param"
34
35
  readonly name: Name
35
36
  }
36
37
 
37
- interface SetParam<Name extends string = string> {
38
+ interface SetParam<Name extends string> {
38
39
  readonly [term]: "setParam"
39
40
  readonly name: Name
40
41
  }
41
42
 
42
- type AnyTerm = Var | Param | SetParam
43
+ type AnyTerm = AnyVar | Param<string> | SetParam<string>
43
44
 
44
45
  function isTerm(value: unknown): value is AnyTerm {
45
46
  return typeof value === "object" && value !== null && term in value
@@ -147,84 +148,94 @@ type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never)
147
148
 
148
149
  type ShapeOf<U> = [U] extends [never] ? Record<never, never> : Flatten<UnionToIntersection<U>>
149
150
 
150
- type WidthOf<F extends AnyField> = F extends { readonly width: infer W } ? W : undefined
151
-
152
- type ElementOf<F extends AnyField> = F extends { readonly element: infer E } ? E : undefined
151
+ /**
152
+ * A slot's identity for the positive-join judgment: the field's structural
153
+ * signature ({@link SignatureOf} — the ONE interpreter, shared with the
154
+ * face pairing wall) plus the slot's law class.
155
+ */
156
+ type SlotSignature<S extends ClassedField> = readonly [SignatureOf<S["field"]>, S["class"]]
153
157
 
154
- type RosterOf<F extends AnyField> = F extends {
155
- readonly closed: { readonly name: infer N extends string; readonly handles: readonly (infer H extends string)[] }
156
- }
157
- ? readonly [N, H]
158
- : undefined
159
-
160
- type JoinOk<A extends ClassedField, B extends ClassedField> = [
161
- A["field"]["kind"],
162
- A["class"],
163
- WidthOf<A["field"]>,
164
- ElementOf<A["field"]>,
165
- RosterOf<A["field"]>
166
- ] extends [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>, RosterOf<B["field"]>]
167
- ? [B["field"]["kind"], B["class"], WidthOf<B["field"]>, ElementOf<B["field"]>, RosterOf<B["field"]>] extends [
168
- A["field"]["kind"],
169
- A["class"],
170
- WidthOf<A["field"]>,
171
- ElementOf<A["field"]>,
172
- RosterOf<A["field"]>
173
- ]
174
- ? true
175
- : false
176
- : false
158
+ type JoinOk<A extends ClassedField, B extends ClassedField> = Same<SlotSignature<A>, SlotSignature<B>>
177
159
 
178
160
  type U64Wire<F extends AnyField> = F extends { readonly kind: "u64" }
179
- ? RosterOf<F> extends undefined
180
- ? true
181
- : false
161
+ ? F extends { readonly closed: unknown }
162
+ ? false
163
+ : true
182
164
  : false
183
165
 
166
+ type ClosedHandles<F extends AnyField> = F extends {
167
+ readonly closed: { readonly handles: infer H extends readonly string[] }
168
+ }
169
+ ? H
170
+ : never
171
+
172
+ /**
173
+ * Two closed ids anti-join when their handle vectors carry the same Peano
174
+ * length ({@link SameLen}: zero equals zero, successor recurses on
175
+ * successor). A bare field has no vector and proves nothing.
176
+ */
177
+ type ClosedIdOk<A extends AnyField, B extends AnyField> = [ClosedHandles<A>] extends [never]
178
+ ? false
179
+ : [ClosedHandles<B>] extends [never]
180
+ ? false
181
+ : SameLen<ClosedHandles<A>, ClosedHandles<B>>
182
+
184
183
  /**
185
184
  * Anti-join class safety: class-equal slots, plus same-identity u64
186
- * (fresh mint, foreign-key copy, or bare u64) when one side is bare.
187
- * Two distinct generators stay refused. Closed-roster u64 is not a
188
- * wire identity.
185
+ * (fresh mint, foreign-key copy, or bare u64) when one side is bare,
186
+ * plus two closed-id fields whose handle tuples have the same length.
187
+ * Two distinct generators stay refused.
189
188
  */
190
189
  type AntiJoinOk<A extends ClassedField, B extends ClassedField> =
191
190
  JoinOk<A, B> extends true
192
191
  ? true
193
- : U64Wire<A["field"]> extends true
194
- ? U64Wire<B["field"]> extends true
195
- ? [A["class"]] extends [undefined]
196
- ? true
197
- : [B["class"]] extends [undefined]
192
+ : ClosedIdOk<A["field"], B["field"]> extends true
193
+ ? true
194
+ : U64Wire<A["field"]> extends true
195
+ ? U64Wire<B["field"]> extends true
196
+ ? [A["class"]] extends [undefined]
198
197
  ? true
199
- : false
198
+ : [B["class"]] extends [undefined]
199
+ ? true
200
+ : false
201
+ : false
200
202
  : false
201
- : false
202
203
 
203
204
  function fieldJoins(a: ClassedField, b: ClassedField): boolean {
204
- const widthA = "width" in a.field ? a.field.width : undefined
205
- const widthB = "width" in b.field ? b.field.width : undefined
206
- const elementA = "element" in a.field ? a.field.element : undefined
207
- const elementB = "element" in b.field ? b.field.element : undefined
208
- const rosterA = rosterOf(a.field)
209
- const rosterB = rosterOf(b.field)
210
- return (
211
- a.field.kind === b.field.kind &&
212
- a.class === b.class &&
213
- widthA === widthB &&
214
- elementA === elementB &&
215
- rosterA === rosterB
216
- )
205
+ return a.class === b.class && signaturesAgree(a.field, b.field)
217
206
  }
218
207
 
219
208
  function u64Wire(field: AnyField): boolean {
220
209
  return field.kind === "u64" && rosterOf(field) === undefined
221
210
  }
222
211
 
212
+ function closedIdAntiJoins(a: AnyField, b: AnyField): boolean {
213
+ const rosterA = rosterOf(a)
214
+ const rosterB = rosterOf(b)
215
+ return rosterA !== undefined && rosterB !== undefined && rosterA.handles.length === rosterB.handles.length
216
+ }
217
+
218
+ function u64BareWirePair(a: ClassedField, b: ClassedField): boolean {
219
+ return u64Wire(a.field) && u64Wire(b.field) && (a.class === undefined || b.class === undefined)
220
+ }
221
+
223
222
  function fieldAntiJoins(a: ClassedField, b: ClassedField): boolean {
224
- if (fieldJoins(a, b)) {
223
+ if (fieldJoins(a, b) || closedIdAntiJoins(a.field, b.field)) {
225
224
  return true
226
225
  }
227
- return u64Wire(a.field) && u64Wire(b.field) && (a.class === undefined || b.class === undefined)
226
+ return u64BareWirePair(a, b)
227
+ }
228
+
229
+ /**
230
+ * Union-head class safety, the anti-join law's head twin: class-equal
231
+ * slots, plus same-identity u64 wire when one side is bare. A head cell
232
+ * is a value that flows to the caller either way — the class is
233
+ * provenance, not wire shape — and the merged head carries the meet:
234
+ * a bare rule demotes the column's class claim to bare, so downstream
235
+ * joins cannot inherit provenance a rule never proved.
236
+ */
237
+ function headFieldJoins(a: ClassedField, b: ClassedField): boolean {
238
+ return fieldJoins(a, b) || u64BareWirePair(a, b)
228
239
  }
229
240
 
230
241
  function renderFieldKind(slot: ClassedField): string {
@@ -257,7 +268,6 @@ interface ParamEntry {
257
268
 
258
269
  export type {
259
270
  AntiJoinOk,
260
- AnyTerm,
261
271
  AnyVar,
262
272
  ClassedField,
263
273
  ExactVars,
@@ -266,7 +276,6 @@ export type {
266
276
  JoinOk,
267
277
  MatchFields,
268
278
  MatchOwner,
269
- MintClassOf,
270
279
  MintSlotOf,
271
280
  Param,
272
281
  ParamEntry,
@@ -274,8 +283,18 @@ export type {
274
283
  ParamValueAt,
275
284
  SetParam,
276
285
  ShapeOf,
277
- UnionToIntersection,
278
286
  Var,
279
287
  VarsOf
280
288
  }
281
- export { fieldAntiJoins, fieldJoins, inferred, isTerm, makeParam, makeSetParam, renderFieldKind, term, v }
289
+ export {
290
+ fieldAntiJoins,
291
+ fieldJoins,
292
+ headFieldJoins,
293
+ inferred,
294
+ isTerm,
295
+ makeParam,
296
+ makeSetParam,
297
+ renderFieldKind,
298
+ term,
299
+ v
300
+ }
package/src/spec.ts CHANGED
@@ -233,7 +233,6 @@ function renderWeight(weight: WeightSpec): string {
233
233
  export type {
234
234
  CapacityBoundSpec,
235
235
  CapacityWindowSpec,
236
- ClosedSpec,
237
236
  FieldSpec,
238
237
  LiteralSetSpec,
239
238
  LiteralSpec,
package/src/statements.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  } from "#capacity.ts"
13
13
  import { isClosedMember, sealedFieldOf } from "#closed.ts"
14
14
  import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
15
- import { type ClosedRoster, rosterOf } from "#fields.ts"
15
+ import { type AnyClosedRoster, rosterOf, rostersAgree } from "#fields.ts"
16
16
  import type { AnyRelation, RelationFields } from "#relation.ts"
17
17
  import { type CapacityWindowSpec, renderCapacityWindow, renderWeight, type WeightSpec } from "#spec.ts"
18
18
 
@@ -67,7 +67,7 @@ interface KeyStatement<R extends AnyRelation, Projection extends readonly string
67
67
  readonly data: KeyData<R, Projection>
68
68
  }
69
69
 
70
- function renderRosterSide(roster: ClosedRoster | undefined): string {
70
+ function renderRosterSide(roster: AnyClosedRoster | undefined): string {
71
71
  return roster === undefined ? "a bare column" : `a ${roster.name} reference`
72
72
  }
73
73
 
@@ -96,7 +96,7 @@ function assertRosterAgreement(source: FaceData, target: FaceData, statement: St
96
96
  }
97
97
  const sourceRoster = rosterOf(sealedFieldOf(source.owner, fieldName))
98
98
  const targetRoster = rosterOf(sealedFieldOf(target.owner, targetField))
99
- if (sourceRoster !== targetRoster) {
99
+ if (!rostersAgree(sourceRoster, targetRoster)) {
100
100
  throw errors.new(
101
101
  `${source.owner.name}.${fieldName} is ${renderRosterSide(sourceRoster)} but ${target.owner.name}.${targetField} is ${renderRosterSide(targetRoster)} — closedness rides the descriptor: a closed reference is spelled with the vocabulary's own id descriptor (one meaning, one spelling), so faces pair closed-with-closed through one roster or bare-with-bare, never across — ${renderStatement(statement)}`
102
102
  )
@@ -312,7 +312,6 @@ export type {
312
312
  ContainmentData,
313
313
  KeyData,
314
314
  KeyStatement,
315
- MirrorsData,
316
315
  Statement,
317
316
  StatementData
318
317
  }