@bjornpagen/bumbledb 0.4.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.
Files changed (92) hide show
  1. package/COOKBOOK.md +181 -47
  2. package/README.md +4 -2
  3. package/dist/closed.d.ts +30 -2
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +66 -20
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +33 -6
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +87 -70
  10. package/dist/db.js.map +1 -1
  11. package/dist/exhume.d.ts.map +1 -1
  12. package/dist/exhume.js +1 -14
  13. package/dist/exhume.js.map +1 -1
  14. package/dist/face.d.ts +1 -1
  15. package/dist/face.d.ts.map +1 -1
  16. package/dist/face.js +2 -1
  17. package/dist/face.js.map +1 -1
  18. package/dist/fields.d.ts +18 -1
  19. package/dist/fields.d.ts.map +1 -1
  20. package/dist/fields.js +44 -16
  21. package/dist/fields.js.map +1 -1
  22. package/dist/index.d.ts +11 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +8 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/law.d.ts +2 -1
  27. package/dist/law.d.ts.map +1 -1
  28. package/dist/law.js +15 -14
  29. package/dist/law.js.map +1 -1
  30. package/dist/lower.d.ts.map +1 -1
  31. package/dist/lower.js +1 -7
  32. package/dist/lower.js.map +1 -1
  33. package/dist/marshal.d.ts.map +1 -1
  34. package/dist/marshal.js +15 -27
  35. package/dist/marshal.js.map +1 -1
  36. package/dist/native.d.ts +21 -2
  37. package/dist/native.d.ts.map +1 -1
  38. package/dist/native.js +20 -3
  39. package/dist/native.js.map +1 -1
  40. package/dist/order.d.ts +36 -0
  41. package/dist/order.d.ts.map +1 -0
  42. package/dist/order.js +135 -0
  43. package/dist/order.js.map +1 -0
  44. package/dist/query/atom.d.ts +18 -15
  45. package/dist/query/atom.d.ts.map +1 -1
  46. package/dist/query/atom.js +9 -16
  47. package/dist/query/atom.js.map +1 -1
  48. package/dist/query/lower.d.ts +5 -8
  49. package/dist/query/lower.d.ts.map +1 -1
  50. package/dist/query/lower.js +87 -76
  51. package/dist/query/lower.js.map +1 -1
  52. package/dist/query/predicate.d.ts.map +1 -1
  53. package/dist/query/predicate.js +34 -2
  54. package/dist/query/predicate.js.map +1 -1
  55. package/dist/query/run.d.ts.map +1 -1
  56. package/dist/query/run.js +1 -2
  57. package/dist/query/run.js.map +1 -1
  58. package/dist/query/scope.d.ts +2 -16
  59. package/dist/query/scope.d.ts.map +1 -1
  60. package/dist/query/scope.js +7 -47
  61. package/dist/query/scope.js.map +1 -1
  62. package/dist/relation.d.ts +9 -22
  63. package/dist/relation.d.ts.map +1 -1
  64. package/dist/relation.js +9 -28
  65. package/dist/relation.js.map +1 -1
  66. package/dist/schema.d.ts.map +1 -1
  67. package/dist/schema.js +7 -31
  68. package/dist/schema.js.map +1 -1
  69. package/dist/statements.d.ts +5 -2
  70. package/dist/statements.d.ts.map +1 -1
  71. package/dist/statements.js +30 -35
  72. package/dist/statements.js.map +1 -1
  73. package/package.json +2 -5
  74. package/src/closed.ts +73 -28
  75. package/src/db.ts +122 -84
  76. package/src/exhume.ts +1 -15
  77. package/src/face.ts +4 -3
  78. package/src/fields.ts +58 -16
  79. package/src/index.ts +10 -8
  80. package/src/law.ts +15 -14
  81. package/src/lower.ts +2 -9
  82. package/src/marshal.ts +15 -31
  83. package/src/native.ts +22 -4
  84. package/src/order.ts +156 -0
  85. package/src/query/atom.ts +13 -21
  86. package/src/query/lower.ts +90 -91
  87. package/src/query/predicate.ts +39 -4
  88. package/src/query/run.ts +1 -3
  89. package/src/query/scope.ts +7 -59
  90. package/src/relation.ts +9 -51
  91. package/src/schema.ts +7 -33
  92. package/src/statements.ts +33 -38
package/COOKBOOK.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The cookbook — modeling intuition as schemas, in TypeScript
2
2
 
3
- The bumbledb engine's 29 cookbook recipes (`bumbledb/docs/cookbook.md`),
3
+ The bumbledb engine's 30 cookbook recipes (`bumbledb/docs/cookbook.md`),
4
4
  translated to this SDK's structural API. **This document is illustrative,
5
5
  never normative**: where a recipe and an engine architecture chapter disagree,
6
6
  the chapter wins (`docs/architecture/README.md` rule 5) — the SDK is the same
@@ -37,9 +37,11 @@ import {
37
37
  abandon,
38
38
  allen,
39
39
  bool,
40
+ by,
40
41
  bytes,
41
42
  closed,
42
43
  contained,
44
+ desc,
43
45
  eq,
44
46
  i64,
45
47
  interval,
@@ -116,10 +118,11 @@ const Uptime = schema("Uptime", { Service, Outage }, [
116
118
  key(Outage, ["service", "window"])
117
119
  ])
118
120
 
119
- // down at instant t — `r.vars` mints the rule's whole variable scope at
120
- // once, and shorthand punning binds same-named columns:
121
+ // down at instant t — `r.var` names each variable (typed by the field it
122
+ // first binds), and shorthand punning binds same-named columns:
121
123
  const downAt = query(Uptime).rule((r) => {
122
- const { service, window } = r.vars("service", "window")
124
+ const service = r.var("service")
125
+ const window = r.var("window")
123
126
  return r
124
127
  .match(Outage, { service, window })
125
128
  .where(pointIn(r.param("t"), window))
@@ -127,7 +130,8 @@ const downAt = query(Uptime).rule((r) => {
127
130
  })
128
131
  // overlapping an incident window (one Allen mask, no operator zoo):
129
132
  const overlapping = query(Uptime).rule((r) => {
130
- const { service, window } = r.vars("service", "window")
133
+ const service = r.var("service")
134
+ const window = r.var("window")
131
135
  return r
132
136
  .match(Outage, { service, window })
133
137
  .where(allen(window, ALLEN.intersects, r.param("incident")))
@@ -135,7 +139,8 @@ const overlapping = query(Uptime).rule((r) => {
135
139
  })
136
140
  // total downtime per service (the denotation's one arithmetic):
137
141
  const downtime = query(Uptime).rule((r) => {
138
- const { service, window } = r.vars("service", "window")
142
+ const service = r.var("service")
143
+ const window = r.var("window")
139
144
  return r.match(Outage, { service, window }).select("service", r.sum(r.duration("window")))
140
145
  })
141
146
  ```
@@ -215,7 +220,7 @@ const Optionality = schema("Optionality", { Business, MailingAddress }, [
215
220
 
216
221
  // Negation is plain anti-join (no null branch exists in any operator):
217
222
  const unaddressed = query(Optionality).rule((r) => {
218
- const { b } = r.vars("b")
223
+ const b = r.var("b")
219
224
  return r
220
225
  .match(Business, { id: b })
221
226
  .where(not(MailingAddress, { business: b }))
@@ -255,7 +260,10 @@ const Money = schema("Money", { Currency, Account, Posting }, [
255
260
  // silently. Bind the fresh id: set semantics would collapse two equal
256
261
  // (account, currency, minor) postings without it.
257
262
  const totals = query(Money).rule((r) => {
258
- const { id, account, currency, minor } = r.vars("id", "account", "currency", "minor")
263
+ const id = r.var("id")
264
+ const account = r.var("account")
265
+ const currency = r.var("currency")
266
+ const minor = r.var("minor")
259
267
  return r.match(Posting, { id, account, currency, minor }).select("account", "currency", r.sum("minor"))
260
268
  })
261
269
  ```
@@ -292,7 +300,7 @@ const Content = schema("Content", { Region, Document, Replica }, [
292
300
 
293
301
  // a bytes param self-encodes (Uint8Array by inference):
294
302
  const byDigest = query(Content).rule((r) => {
295
- const { id } = r.vars("id")
303
+ const id = r.var("id")
296
304
  return r.match(Document, { id, payload: r.param("digest") }).select("id")
297
305
  })
298
306
  ```
@@ -334,7 +342,7 @@ const Tickets = schema("Tickets", { Priority, Ticket }, [
334
342
  // that drifts without a rebuild is an ordinary relation — a vocabulary is
335
343
  // never written, only declared.
336
344
  const urgent = query(Tickets).rule((r) => {
337
- const { t } = r.vars("t")
345
+ const t = r.var("t")
338
346
  return r.match(Ticket, { id: t, priority: "Urgent" }).select("t")
339
347
  })
340
348
 
@@ -343,7 +351,7 @@ const urgent = query(Tickets).rule((r) => {
343
351
  // ∈-set param, `r.inSet`); the array folds to the same wire set the param
344
352
  // spelling crosses. In `.where()` selections arrays work at EVERY field kind.
345
353
  const actionable = query(Tickets).rule((r) => {
346
- const { t } = r.vars("t")
354
+ const t = r.var("t")
347
355
  return r.match(Ticket, { id: t, priority: ["Normal", "Urgent"] }).select("t")
348
356
  })
349
357
  ```
@@ -388,7 +396,8 @@ const Review = schema("Review", { Kind, Attempt, Certificate }, [
388
396
  // exactly like an ordinary one, and the atom folds at prepare into a
389
397
  // plan-constant handle set on its sibling.
390
398
  const masteredAttempts = query(Review).rule((r) => {
391
- const { a, k } = r.vars("a", "k")
399
+ const a = r.var("a")
400
+ const k = r.var("k")
392
401
  return r
393
402
  .match(Attempt, { id: a, kind: k })
394
403
  .match(Kind, { id: k, mastered: true })
@@ -451,7 +460,8 @@ const Oncall = schema("Oncall", { Severity, Incident, Escalation }, [
451
460
 
452
461
  // who is being paged — the same ψ, on the read side:
453
462
  const paged = query(Oncall).rule((r) => {
454
- const { i, s } = r.vars("i", "s")
463
+ const i = r.var("i")
464
+ const s = r.var("s")
455
465
  return r
456
466
  .match(Escalation, { incident: i, severity: s })
457
467
  .match(Severity, { id: s, pages: true })
@@ -468,7 +478,8 @@ plus pointwise keys realizes exact partition
468
478
  (`lean/Bumbledb/Dependencies.lean: exact_partition_iff`), and the mixed-width
469
479
  interval positions type by element domain
470
480
  (`lean/Bumbledb/Schema.lean: Value.points_one_tag_u64`); ordering the result
471
- remains a host presentation step.
481
+ remains a host presentation step — the SDK ships the comparator (`by`/`desc`,
482
+ keys as data); the engine never orders.
472
483
 
473
484
  The linked-list verdict: successor pointers are control flow smuggled into
474
485
  data. Order is a value. The idiomatic ordered collection is an interval
@@ -496,12 +507,21 @@ const Playlists = schema("Playlists", { Playlist, Extent, Slot }, [
496
507
 
497
508
  // Positional access is membership — "what plays at position ?pos":
498
509
  const playingAt = query(Playlists).rule((r) => {
499
- const { slot, track } = r.vars("slot", "track")
510
+ const slot = r.var("slot")
511
+ const track = r.var("track")
500
512
  return r
501
513
  .match(Slot, { playlist: r.param("list"), slot, track })
502
514
  .where(pointIn(r.param("pos"), slot))
503
515
  .select("track")
504
516
  })
517
+
518
+ // Answers are SETS — the host sorts them, and the SDK ships the comparator:
519
+ // sort keys as data, a bare name ascending, `desc(...)` the flip; intervals
520
+ // order by (start, end). Limit is the language's own `.slice(0, n)`.
521
+ const inPlayOrder = [
522
+ { slot: { start: 1n, end: 2n }, track: "b" },
523
+ { slot: { start: 0n, end: 1n }, track: "a" }
524
+ ].sort(by("slot", "track"))
505
525
  ```
506
526
 
507
527
  Middle insert is honest about its cost: making room at position `k` shifts
@@ -548,7 +568,8 @@ const Ast = schema("Ast", { Kind, Node, Lit, Add, Parent }, [
548
568
  ])
549
569
 
550
570
  const lhsLiteral = query(Ast).rule((r) => {
551
- const { l, v } = r.vars("l", "v")
571
+ const l = r.var("l")
572
+ const v = r.var("v")
552
573
  return r
553
574
  .match(Add, { node: r.param("n"), lhs: l })
554
575
  .match(Lit, { node: l, value: v })
@@ -583,7 +604,8 @@ const Graph = schema("Graph", { Person, Repo, Follows, Maintains }, [
583
604
  // live in the "Person.id" class, so the reuse is lawful); `lt` keeps each
584
605
  // pair once:
585
606
  const mutual = query(Graph).rule((r) => {
586
- const { a, b } = r.vars("a", "b")
607
+ const a = r.var("a")
608
+ const b = r.var("b")
587
609
  return r
588
610
  .match(Follows, { follower: a, followee: b })
589
611
  .match(Follows, { follower: b, followee: a })
@@ -621,7 +643,11 @@ const Ecs = schema("Ecs", { Entity, Transform, Velocity, Renderable }, [
621
643
 
622
644
  // The physics join is the component intersection:
623
645
  const physics = query(Ecs).rule((r) => {
624
- const { entity, x, y, dx, dy } = r.vars("entity", "x", "y", "dx", "dy")
646
+ const entity = r.var("entity")
647
+ const x = r.var("x")
648
+ const y = r.var("y")
649
+ const dx = r.var("dx")
650
+ const dy = r.var("dy")
625
651
  return r
626
652
  .match(Transform, { entity, x, y })
627
653
  .match(Velocity, { entity, dx, dy })
@@ -661,7 +687,8 @@ const Orders = schema("Orders", { State, Order, Placement, Shipment }, [
661
687
  ])
662
688
 
663
689
  const shipped = query(Orders).rule((r) => {
664
- const { id, carrier } = r.vars("id", "carrier")
690
+ const id = r.var("id")
691
+ const carrier = r.var("carrier")
665
692
  return r
666
693
  .match(Order, { id, state: "Shipped" })
667
694
  .match(Shipment, { order: id, carrier })
@@ -731,14 +758,16 @@ const Calendar = schema("Calendar", { Rsvp, Arm, Person, Room, Event, Attendance
731
758
  ])
732
759
 
733
760
  const roomConflicts = query(Calendar).rule((r) => {
734
- const { room, span } = r.vars("room", "span")
761
+ const room = r.var("room")
762
+ const span = r.var("span")
735
763
  return r
736
764
  .match(Booking, { room, span })
737
765
  .where(allen(span, ALLEN.intersects, r.param("want")))
738
766
  .select("room", "span")
739
767
  })
740
768
  const personLoad = query(Calendar).rule((r) => {
741
- const { person, span } = r.vars("person", "span")
769
+ const person = r.var("person")
770
+ const span = r.var("span")
742
771
  return r
743
772
  .match(Claim, { person, span })
744
773
  .where(allen(span, ALLEN.intersects, r.param("window")))
@@ -774,7 +803,8 @@ const Pricing = schema("Pricing", { Policy, Version }, [
774
803
 
775
804
  // in force on date t — one membership probe:
776
805
  const inForce = query(Pricing).rule((r) => {
777
- const { rate_bps, valid } = r.vars("rate_bps", "valid")
806
+ const rate_bps = r.var("rate_bps")
807
+ const valid = r.var("valid")
778
808
  return r
779
809
  .match(Version, { policy: r.param("p"), rate_bps, valid })
780
810
  .where(pointIn(r.param("t"), valid))
@@ -782,7 +812,9 @@ const inForce = query(Pricing).rule((r) => {
782
812
  })
783
813
  // clean successions (half-open makes MEETS exact, no ±1 fudge):
784
814
  const successions = query(Pricing).rule((r) => {
785
- const { p, a, b } = r.vars("p", "a", "b")
815
+ const p = r.var("p")
816
+ const a = r.var("a")
817
+ const b = r.var("b")
786
818
  return r
787
819
  .match(Version, { policy: p, valid: a })
788
820
  .match(Version, { policy: p, valid: b })
@@ -816,7 +848,8 @@ const Payroll = schema("Payroll", { FiscalYear, PayPeriod }, [
816
848
 
817
849
  // the period holding date t:
818
850
  const holding = query(Payroll).rule((r) => {
819
- const { seq, span } = r.vars("seq", "span")
851
+ const seq = r.var("seq")
852
+ const span = r.var("span")
820
853
  return r
821
854
  .match(PayPeriod, { year: r.param("y"), seq, span })
822
855
  .where(pointIn(r.param("t"), span))
@@ -864,7 +897,9 @@ const Tax = schema("Tax", { Status, Regime, Bracket, Residency, Earned }, [
864
897
  // owed is host arithmetic over the bracket walk — arithmetic beyond the
865
898
  // measure is refused (the ledger).
866
899
  const marginal = query(Tax).rule((r) => {
867
- const { reg, b, rate_bps } = r.vars("reg", "b", "rate_bps")
900
+ const reg = r.var("reg")
901
+ const b = r.var("b")
902
+ const rate_bps = r.var("rate_bps")
868
903
  return r
869
904
  .match(Regime, { id: reg, year: r.param("y"), status: r.param("s") })
870
905
  .match(Bracket, { regime: reg, income: b, rate_bps })
@@ -896,17 +931,21 @@ const FreeTime = schema("FreeTime", { Person, Claim }, [
896
931
 
897
932
  // busy time, coalesced (adjacent segments merge — the half-open law):
898
933
  const busy = query(FreeTime).rule((r) => {
899
- const { person, span } = r.vars("person", "span")
934
+ const person = r.var("person")
935
+ const span = r.var("span")
900
936
  return r.match(Claim, { person, span }).select("person", r.pack("span"))
901
937
  })
902
938
  // raw claimed time (overlaps double-count — often the wrong question):
903
939
  const claimed = query(FreeTime).rule((r) => {
904
- const { person, span } = r.vars("person", "span")
940
+ const person = r.var("person")
941
+ const span = r.var("span")
905
942
  return r.match(Claim, { person, span }).select("person", r.sum(r.duration("span")))
906
943
  })
907
944
  // Coalesced totals = the two-query composition (pack, then a host fold) —
908
945
  // aggregates never nest; free time (gaps) is the two-line host walk over
909
- // sorted packed answers both refusals recorded in the ledger.
946
+ // sorted packed answers (`rows.sort(by("person", "span"))` the
947
+ // keys-as-data comparator; limit is the language's own `.slice`) — both
948
+ // refusals recorded in the ledger.
910
949
  ```
911
950
 
912
951
  ## The write side
@@ -940,12 +979,16 @@ const Ledger = schema("Ledger", { Account, JournalEntry, Posting }, [
940
979
 
941
980
  // balances (bind the fresh id — set semantics collapses duplicates):
942
981
  const balances = query(Ledger).rule((r) => {
943
- const { id, account, minor } = r.vars("id", "account", "minor")
982
+ const id = r.var("id")
983
+ const account = r.var("account")
984
+ const minor = r.var("minor")
944
985
  return r.match(Posting, { id, account, minor }).select("account", r.sum("minor"))
945
986
  })
946
987
  // double-entry audit (host asserts every total is 0 — discipline, not schema):
947
988
  const doubleEntry = query(Ledger).rule((r) => {
948
- const { id, entry, minor } = r.vars("id", "entry", "minor")
989
+ const id = r.var("id")
990
+ const entry = r.var("entry")
991
+ const minor = r.var("minor")
949
992
  return r.match(Posting, { id, entry, minor }).select("entry", r.sum("minor"))
950
993
  })
951
994
  ```
@@ -960,8 +1003,11 @@ final-state point reads need no earlier witness.
960
1003
  The generation witness: read the model, propose a delta, commit iff the model
961
1004
  you read is still the model. In the SDK the whole loop is `db.writeWitnessed`
962
1005
  — retry on movement is built in (every generation move is self-inflicted by
963
- the host's own interleaved writes), and `abandon(payload)` declines to commit
964
- without issuing anything.
1006
+ the host's own interleaved writes), capped at 64 attempts: a callback that
1007
+ itself issues a plain `db.write` each try re-moves the generation it is about
1008
+ to witness, and past the cap that interleave hazard throws the typed
1009
+ `ErrWitnessedLivelock` instead of spinning forever. `abandon(payload)`
1010
+ declines to commit without issuing anything.
965
1011
 
966
1012
  ```ts
967
1013
  const State = closed("State", ["Queued", "Running", "Done"])
@@ -978,7 +1024,8 @@ const Jobs = schema("Jobs", { State, Job, Lease }, [
978
1024
 
979
1025
  // update-where's premise — "still Queued" is the witness:
980
1026
  const stillQueued = query(Jobs).rule((r) => {
981
- const { id, payload } = r.vars("id", "payload")
1027
+ const id = r.var("id")
1028
+ const payload = r.var("payload")
982
1029
  return r.match(Job, { id, state: "Queued", payload }).select("id", "payload")
983
1030
  })
984
1031
 
@@ -1035,7 +1082,8 @@ const Rollup = schema("Rollup", { Arm, Claim, BusySpan }, [
1035
1082
  // against sources it didn't actually read. The deriving query (pack IS the
1036
1083
  // coalesce):
1037
1084
  const deriving = query(Rollup).rule((r) => {
1038
- const { person, span } = r.vars("person", "span")
1085
+ const person = r.var("person")
1086
+ const span = r.var("span")
1039
1087
  return r.match(Claim, { person, span, arm: "Busy" }).select("person", r.pack("span"))
1040
1088
  })
1041
1089
  ```
@@ -1069,14 +1117,16 @@ const Payments = schema("Payments", { Kind, Payment, Card, Ach }, [
1069
1117
  // provably disjoint, so the executor elides cross-rule dedup — the free lunch.
1070
1118
  const wholeDu = query(Payments)
1071
1119
  .rule((r) => {
1072
- const { id, n } = r.vars("id", "n")
1120
+ const id = r.var("id")
1121
+ const n = r.var("n")
1073
1122
  return r
1074
1123
  .match(Payment, { id, kind: "Card" })
1075
1124
  .match(Card, { payment: id, last4: n })
1076
1125
  .select("id", "n")
1077
1126
  })
1078
1127
  .rule((r) => {
1079
- const { id, n } = r.vars("id", "n")
1128
+ const id = r.var("id")
1129
+ const n = r.var("n")
1080
1130
  return r
1081
1131
  .match(Payment, { id, kind: "Ach" })
1082
1132
  .match(Ach, { payment: id, routing: n })
@@ -1148,7 +1198,7 @@ const Closure = schema("Closure", { Node, Parent }, [
1148
1198
 
1149
1199
  // The loop's one query — the frontier's children, one ∈-set probe:
1150
1200
  const step = query(Closure).rule((r) => {
1151
- const { c } = r.vars("c")
1201
+ const c = r.var("c")
1152
1202
  return r.match(Parent, { child: c, parent: r.inSet("frontier") }).select("c")
1153
1203
  })
1154
1204
  ```
@@ -1192,21 +1242,22 @@ const reach = program(Closure, (p) => {
1192
1242
  const rec = p.rec("reach")
1193
1243
  const seeded = rec
1194
1244
  .rule((r) => {
1195
- const { c } = r.vars("c")
1245
+ const c = r.var("c")
1196
1246
  return r
1197
1247
  .match(Node, { id: c })
1198
1248
  .where(eq(c, r.param("root")))
1199
1249
  .select("c")
1200
1250
  })
1201
1251
  .rule((r) => {
1202
- const { c, parent } = r.vars("c", "parent")
1252
+ const c = r.var("c")
1253
+ const parent = r.var("parent")
1203
1254
  return r
1204
1255
  .match(Parent, { child: c, parent })
1205
1256
  .idb(rec, parent)
1206
1257
  .select("c")
1207
1258
  })
1208
1259
  return p.output((r) => {
1209
- const { c } = r.vars("c")
1260
+ const c = r.var("c")
1210
1261
  return r.match(Node, { id: c }).idb(seeded, c).select("c")
1211
1262
  })
1212
1263
  })
@@ -1249,13 +1300,14 @@ const Accounts = schema("Accounts", { Account, AccountParent, Posting }, [
1249
1300
  // The two queries the host rollup composes:
1250
1301
  // the frontier step (recipe 24's loop, verbatim):
1251
1302
  const frontierStep = query(Accounts).rule((r) => {
1252
- const { c } = r.vars("c")
1303
+ const c = r.var("c")
1253
1304
  return r.match(AccountParent, { child: c, parent: r.inSet("frontier") }).select("c")
1254
1305
  })
1255
1306
  // the rollup over the accumulated subtree (bind the fresh id — recipe
1256
1307
  // 19's discipline, spent again; equal postings to one account both count):
1257
1308
  const subtreeRollup = query(Accounts).rule((r) => {
1258
- const { id, minor } = r.vars("id", "minor")
1309
+ const id = r.var("id")
1310
+ const minor = r.var("minor")
1259
1311
  return r.match(Posting, { id, account: r.inSet("subtree"), minor }).select(r.sum("minor"))
1260
1312
  })
1261
1313
  // The engine-native form: the closure stratum converges first, then the
@@ -1264,21 +1316,24 @@ const nativeRollup = program(Accounts, (p) => {
1264
1316
  const sub = p.rec("sub")
1265
1317
  const seeded = sub
1266
1318
  .rule((r) => {
1267
- const { a } = r.vars("a")
1319
+ const a = r.var("a")
1268
1320
  return r
1269
1321
  .match(Account, { id: a })
1270
1322
  .where(eq(a, r.param("root")))
1271
1323
  .select("a")
1272
1324
  })
1273
1325
  .rule((r) => {
1274
- const { a, parent } = r.vars("a", "parent")
1326
+ const a = r.var("a")
1327
+ const parent = r.var("parent")
1275
1328
  return r
1276
1329
  .match(AccountParent, { child: a, parent })
1277
1330
  .idb(sub, parent)
1278
1331
  .select("a")
1279
1332
  })
1280
1333
  return p.output((r) => {
1281
- const { id, a, minor } = r.vars("id", "a", "minor")
1334
+ const id = r.var("id")
1335
+ const a = r.var("a")
1336
+ const minor = r.var("minor")
1282
1337
  return r
1283
1338
  .match(Posting, { id, account: a, minor })
1284
1339
  .idb(seeded, a)
@@ -1348,7 +1403,9 @@ const MaintainedRollup = schema("MaintainedRollup", { Arm, Claim, BusySpan }, [
1348
1403
 
1349
1404
  // Derive the desired rollup on the maintenance snapshot:
1350
1405
  const deriving = query(MaintainedRollup).rule((r) => {
1351
- const { source, person, span } = r.vars("source", "person", "span")
1406
+ const source = r.var("source")
1407
+ const person = r.var("person")
1408
+ const span = r.var("span")
1352
1409
  return r.match(Claim, { source, person, arm: "Busy", span }).select("person", r.pack("span"))
1353
1410
  })
1354
1411
  ```
@@ -1402,7 +1459,10 @@ const Payroll = schema("Payroll", { Employee, Salary }, [
1402
1459
 
1403
1460
  // The post-migration read — salaries in force at an instant:
1404
1461
  const inForceAt = query(Payroll).rule((r) => {
1405
- const { e, name, amount, w } = r.vars("e", "name", "amount", "w")
1462
+ const e = r.var("e")
1463
+ const name = r.var("name")
1464
+ const amount = r.var("amount")
1465
+ const w = r.var("w")
1406
1466
  return r
1407
1467
  .match(Employee, { id: e, name })
1408
1468
  .match(Salary, { employee: e, amount, applies: w })
@@ -1468,3 +1528,77 @@ unit slots `[4,5)`, `[5,6)` satisfies both directions, because nothing forces
1468
1528
  the witness rows to mirror the sidecar's segmentation — only its points. If
1469
1529
  per-row correspondence matters, the host writes zones at slot granularity;
1470
1530
  the schema proves disjointness and coverage either way.
1531
+
1532
+ ## Point reads
1533
+
1534
+ ## 30. The keyed read
1535
+
1536
+ Guarantee: validator/runtime premises — a declared key FD admits at most one
1537
+ fact per determinant tuple (the key phase of the commit judgment), and every
1538
+ keyed point read answers exactly that fact or nothing, on every scope
1539
+ (`ts/test/keyed-get.test.ts`; the engine half is
1540
+ `crates/bumbledb/tests/keyed_get.rs`).
1541
+
1542
+ The key is a **law**, and the read surface is that law made callable. The
1543
+ schema says `key(Program, ["grp"])` — one program per group — so "the
1544
+ program of a group" is a well-posed question with at most one answer, and
1545
+ the store already enforces that on every commit. Hold the statement VALUE:
1546
+ it is the read's selector below (statement identity is the membership rule).
1547
+
1548
+ ```ts
1549
+ const Grp = relation("Grp", { id: u64.fresh, label: str })
1550
+ const Program = relation("Program", { id: u64.fresh, grp: u64, title: str })
1551
+ // The law: one program per group — the callable key.
1552
+ const programGrpKey = key(Program, ["grp"])
1553
+
1554
+ const KeyedRead = schema("KeyedRead", { Grp, Program }, [
1555
+ contained(on(Program, "grp"), on(Grp, "id")),
1556
+ programGrpKey
1557
+ ])
1558
+ ```
1559
+
1560
+ The point read is the statement handed back to `get` — one spelling on
1561
+ every scope (the symmetry rule): `db.get` standalone, `snap.get` inside a
1562
+ read scope, `tx.get` inside a write transaction, where the transaction side
1563
+ answers the FINAL state (base plus pending delta: read-your-writes, a
1564
+ pending delete answers `undefined`). The key object is typed by the
1565
+ statement's own projection — a wrong field name is a compile error, never a
1566
+ runtime shape check. The primary 2-arg form needs no statement: the fresh
1567
+ field IS the primary key.
1568
+
1569
+ ```ts
1570
+ const db = await Db.create("./programs.db", KeyedRead)
1571
+
1572
+ const minted: { grp?: bigint } = {}
1573
+ db.write((tx) => {
1574
+ const g = tx.insert(Grp, { label: "algebra" })
1575
+ tx.insert(Program, { grp: g.id, title: "linear equations" })
1576
+ minted.grp = g.id
1577
+ })
1578
+ const grp = minted.grp ?? 0n
1579
+
1580
+ // db.get — the standalone keyed read through the declared law:
1581
+ const byGroup = db.get(Program, programGrpKey, { grp })
1582
+
1583
+ // snap.get — the same spelling inside a read scope:
1584
+ const viaSnap = db.read((snap) => snap.get(Program, programGrpKey, { grp }))
1585
+
1586
+ // tx.get — key-shaped read-modify-write, final-state (recipe 20's third
1587
+ // idiom): per-fact premises need no earlier snapshot witness.
1588
+ db.write((tx) => {
1589
+ const current = tx.get(Program, programGrpKey, { grp })
1590
+ if (current !== undefined) {
1591
+ tx.delete(Program, current)
1592
+ tx.insert(Program, { id: current.id, grp: current.grp, title: "linear equations II" })
1593
+ }
1594
+ })
1595
+
1596
+ // The primary 2-arg form — the fresh field is the primary key:
1597
+ const byId = byGroup === undefined ? undefined : db.get(Program, { id: byGroup.id })
1598
+ ```
1599
+
1600
+ The anti-pattern this recipe retires: a scan-and-find where a key law
1601
+ exists — `snap.scan(Program).find((row) => row.grp === grp)` — re-derives
1602
+ in the host what the store already enforces. The uniqueness the fold
1603
+ quietly assumes IS the declared key statement; spell the law and the point
1604
+ read comes with it.
package/README.md CHANGED
@@ -79,7 +79,9 @@ if (!result.ok) {
79
79
  // first binding; params are typed by use; rows are typed from the select.
80
80
  // `gt` is one of the free comparison exports.
81
81
  const certifiedAbove = query(Review).rule((r) => {
82
- const { a, k, rank } = r.vars("a", "k", "rank")
82
+ const a = r.var("a")
83
+ const k = r.var("k")
84
+ const rank = r.var("rank")
83
85
  return r
84
86
  .match(Certificate, { attempt: a, kind: k })
85
87
  .match(Kind, { id: k, mastered: true, rank }) // ψ on the read side too
@@ -118,7 +120,7 @@ The drizzle law governs this surface: the SDK's job at the host boundary is tran
118
120
  - The structural type kernel — fields as pure structure (`bool`, `bytes`, `i64`, `u64`, `str`, `interval`, `span`), `relation()`, and `closed()` sealed rosters with typed axiom payloads. A closed reference's value type IS the handle union (`Infer` speaks it); dispatch is native `switch` narrowing with `satisfies never` exhaustiveness. Domains are never declared: `schema()` computes every field's class from the statement list.
119
121
  - The statement algebra — `schema()`, `key`, `contained`, `mirrors`, `window`; faces via `on` (set membership is a plain array in `.where`); counts via `exactly`, `atLeast`, `atMost`, `between`, `none`; ψ-selection via `.where` on relations and closed rosters.
120
122
  - The `Db` runtime — `Db.create`/`Db.open`, path-cached stores, transactions, typed violations, scoped snapshot reads, the witnessed write loop with `abandon`.
121
- - The query surface — Datalog as values, `query(S).rule(r => ...)`: named vars, params typed by use, negation, aggregates, and the free comparison/connective exports (`eq`, `ne`, `lt`, `le`, `gt`, `ge`, `and`, `or`, `not`, `allen`/`ALLEN`, `pointIn`, `covers`); set membership at a closed field is a plain array in the match record (`r.match(Ticket, { priority: ["Normal", "Urgent"] })` — closed-only there: an ordinary field's membership is a bound `r.inSet` param); stratified recursion via `program()`; `db.prepare` as a plain value.
123
+ - The query surface — Datalog as values, `query(S).rule(r => ...)`: named vars, params typed by use, negation, aggregates, and the free comparison/connective exports (`eq`, `ne`, `lt`, `le`, `gt`, `ge`, `and`, `or`, `not`, `allen`/`ALLEN`, `pointIn`); set membership at a closed field is a plain array in the match record (`r.match(Ticket, { priority: ["Normal", "Urgent"] })` — closed-only there: an ordinary field's membership is a bound `r.inSet` param); stratified recursion via `program()`; `db.prepare` as a plain value.
122
124
  - The exhume surface — `Db.exhume`, the schema-independent read path: a store's self-described shapes and raw facts by name, with typed refusals (`ErrExhumeNoDescriptor`, `ErrExhumeFormatMismatch`, `ErrExhumeCorruption`).
123
125
 
124
126
  ## Cookbook
package/dist/closed.d.ts CHANGED
@@ -30,6 +30,7 @@
30
30
  * shape.
31
31
  */
32
32
  import { type AnyField, type ClosedIdField, type Infer } from "#fields.ts";
33
+ import type { AnyRelation, RelationField } from "#relation.ts";
33
34
  import { type SelectionBinding, type SelectionInput } from "#relation.ts";
34
35
  import type { LiteralSpec } from "#spec.ts";
35
36
  /**
@@ -44,7 +45,7 @@ type PayloadField = Exclude<AnyField, {
44
45
  * unspellable — the sealed shape mints the synthetic `id` itself (ordinal
45
46
  * 0 of the matchable fields), so a declared column named `id` would be
46
47
  * shadowed by the synthetic slot everywhere the shape resolves by name
47
- * (`matchFieldsOf`, the projected face, `spec.rs`'s resolver). The wall is
48
+ * (`sealedFieldsOf`, the projected face, `spec.rs`'s resolver). The wall is
48
49
  * typed here and judged again at construction in {@link mintClosed} — the
49
50
  * runtime twin for untyped callers, warmer and earlier than the engine's
50
51
  * `DuplicateFieldName` at `Db.create`.
@@ -173,6 +174,33 @@ interface AnyClosed {
173
174
  readonly axioms: Readonly<Record<string, object>>;
174
175
  readonly columns: Readonly<Record<string, PayloadField>>;
175
176
  }
177
+ /**
178
+ * THE relation-kind discriminant — the ONE spelling of "is this schema
179
+ * member closed?" (the type tier's twin is the `AnyClosed` conditional
180
+ * arms). A closed relation's runtime description carries its handle
181
+ * roster; an ordinary relation's never does. Every runtime closed/ordinary
182
+ * fork in the SDK judges through this predicate — never a re-spelled
183
+ * structural probe.
184
+ */
185
+ declare function isClosedMember(member: AnyRelation | AnyClosed): member is AnyClosed;
186
+ /**
187
+ * The SEALED field list of a schema member — THE one reader of "what
188
+ * fields does this owner expose": an ordinary relation's declared fields;
189
+ * a closed relation's sealed shape — the synthetic `id` (the value's own
190
+ * roster-carrying descriptor, by identity) at ordinal 0, then the declared
191
+ * payload columns at declared index + 1 (the sealed shift, mirroring the
192
+ * engine's `SchemaDescriptor::sealed_fields`). A `ClosedColumn` is
193
+ * structurally a `RelationField`, so both kinds read uniformly.
194
+ */
195
+ declare function sealedFieldsOf(member: AnyRelation | AnyClosed): readonly RelationField[];
196
+ /**
197
+ * One sealed field by name — derived from {@link sealedFieldsOf}, so the
198
+ * closed synthetic `id` resolves everywhere a name is looked up (no reader
199
+ * can silently lack the `id` arm). `undefined` when the name is foreign
200
+ * (the type tiers make that unwritable; the engine re-judges at
201
+ * `Db.create`).
202
+ */
203
+ declare function sealedFieldOf(member: AnyRelation | AnyClosed, fieldName: string): AnyField | undefined;
176
204
  /** Bare tier: `closed("Kind", ["Checking", "Savings"])` — handles only. */
177
205
  declare function closed<const Name extends string, const Handles extends readonly [string, ...string[]]>(name: Name, handles: Handles): Closed<Name, Handles[number], Record<never, never>>;
178
206
  /**
@@ -189,5 +217,5 @@ declare function closed<const Name extends string, const Handles extends readonl
189
217
  */
190
218
  declare function closed<const Name extends string, const Cols extends PayloadColumns, Handles extends string>(name: Name, columns: Cols, axioms: Axioms<Handles, Cols>): Closed<Name, Handles, Cols>;
191
219
  export type { AnyClosed, AnySelectedClosed, AxiomRow, Axioms, Closed, ClosedColumn, ClosedCore, ClosedData, ClosedRow, ClosedSelectable, ClosedSelectionInput, PayloadField, SelectedClosed };
192
- export { closed };
220
+ export { closed, isClosedMember, sealedFieldOf, sealedFieldsOf };
193
221
  //# sourceMappingURL=closed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"closed.d.ts","sourceRoot":"","sources":["../src/closed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EACN,KAAK,QAAQ,EAEb,KAAK,aAAa,EAElB,KAAK,KAAK,EAEV,MAAM,YAAY,CAAA;AACnB,OAAO,EAAoB,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAC3F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C;;;GAGG;AACH,KAAK,YAAY,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CAAE,CAAC,CAAA;AAE/D;;;;;;;;;GASG;AACH,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAE5E,mEAAmE;AACnE,UAAU,YAAY;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;CAC5B;AAED;;;;;GAKG;AACH,UAAU,SAAS;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;CACvC;AAED,+CAA+C;AAC/C,UAAU,UAAU;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;CACnC;AAED,2FAA2F;AAC3F,KAAK,QAAQ,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CAAE,CAAA;AAEzG;;;;;GAKG;AACH,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI;IAChF,QAAQ,EAAE,CAAC,IAAI,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC;CACvC,CAAA;AAED;;;GAGG;AACH,UAAU,UAAU,CAAC,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAC1G,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,KAAK,oBAAoB,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,CAAA;AAE3F;;;;;;;GAOG;AACH,UAAU,cAAc,CAAC,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9G,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAC/C;AAED,4CAA4C;AAC5C,UAAU,iBAAiB;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAC/C;AAED;;;;;;GAMG;AACH,UAAU,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAChH,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;CACjF;AAED;;;;;;;;GAQG;AACH,KAAK,MAAM,CAAC,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI;IACrG,MAAM,IAAI;CACV,SAAS,CAAC,KAAK,CAAC,GACd,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAC/B,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;AAE1E,kEAAkE;AAClE,UAAU,SAAS;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACjD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;CACxD;AAuFD,2EAA2E;AAC3E,iBAAS,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,CAAC,OAAO,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,EAC9F,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,GACd,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;AAEtD;;;;;;;;;;;GAWG;AACH,iBAAS,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,CAAC,IAAI,SAAS,cAAc,EAAE,OAAO,SAAS,MAAM,EACnG,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,IAAI,EACb,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAC3B,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;AA4J9B,YAAY,EACX,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,MAAM,EACN,MAAM,EACN,YAAY,EACZ,UAAU,EACV,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,CAAA;AACD,OAAO,EAAE,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"closed.d.ts","sourceRoot":"","sources":["../src/closed.ts"],"names":[],"mappings":"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"}