@bjornpagen/bumbledb 0.19.2 → 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.
@@ -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> = {
@@ -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 }
@@ -215,11 +215,27 @@ function closedIdAntiJoins(a: AnyField, b: AnyField): boolean {
215
215
  return rosterA !== undefined && rosterB !== undefined && rosterA.handles.length === rosterB.handles.length
216
216
  }
217
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
+
218
222
  function fieldAntiJoins(a: ClassedField, b: ClassedField): boolean {
219
223
  if (fieldJoins(a, b) || closedIdAntiJoins(a.field, b.field)) {
220
224
  return true
221
225
  }
222
- 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)
223
239
  }
224
240
 
225
241
  function renderFieldKind(slot: ClassedField): string {
@@ -252,7 +268,6 @@ interface ParamEntry {
252
268
 
253
269
  export type {
254
270
  AntiJoinOk,
255
- AnyTerm,
256
271
  AnyVar,
257
272
  ClassedField,
258
273
  ExactVars,
@@ -261,7 +276,6 @@ export type {
261
276
  JoinOk,
262
277
  MatchFields,
263
278
  MatchOwner,
264
- MintClassOf,
265
279
  MintSlotOf,
266
280
  Param,
267
281
  ParamEntry,
@@ -269,8 +283,18 @@ export type {
269
283
  ParamValueAt,
270
284
  SetParam,
271
285
  ShapeOf,
272
- UnionToIntersection,
273
286
  Var,
274
287
  VarsOf
275
288
  }
276
- 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
@@ -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
  }