@bjornpagen/bumbledb 0.5.0 → 0.6.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 +96 -131
- package/README.md +9 -9
- package/dist/db.js +2 -2
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/query/atom.d.ts +132 -201
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +30 -42
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +116 -0
- package/dist/query/find.d.ts.map +1 -0
- package/dist/query/{select.js → find.js} +22 -22
- package/dist/query/find.js.map +1 -0
- package/dist/query/lower.d.ts +123 -158
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +437 -493
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts +22 -14
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +35 -42
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +3 -3
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +9 -9
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +127 -72
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +80 -33
- package/dist/query/scope.js.map +1 -1
- package/package.json +2 -2
- package/src/db.ts +4 -4
- package/src/index.ts +11 -7
- package/src/query/atom.ts +181 -263
- package/src/query/find.ts +212 -0
- package/src/query/lower.ts +588 -722
- package/src/query/predicate.ts +37 -45
- package/src/query/run.ts +10 -10
- package/src/query/scope.ts +172 -87
- package/dist/query/select.d.ts +0 -128
- package/dist/query/select.d.ts.map +0 -1
- package/dist/query/select.js.map +0 -1
- package/src/query/select.ts +0 -215
package/COOKBOOK.md
CHANGED
|
@@ -56,7 +56,8 @@ import {
|
|
|
56
56
|
relation,
|
|
57
57
|
schema,
|
|
58
58
|
str,
|
|
59
|
-
u64
|
|
59
|
+
u64,
|
|
60
|
+
v
|
|
60
61
|
} from "@bjornpagen/bumbledb"
|
|
61
62
|
```
|
|
62
63
|
|
|
@@ -118,30 +119,27 @@ const Uptime = schema("Uptime", { Service, Outage }, [
|
|
|
118
119
|
key(Outage, ["service", "window"])
|
|
119
120
|
])
|
|
120
121
|
|
|
121
|
-
// down at instant t —
|
|
122
|
-
//
|
|
122
|
+
// down at instant t — v(Outage) mints a fresh variable per column (typed by
|
|
123
|
+
// that column's law-class); destructure what you bind, reuse to join:
|
|
123
124
|
const downAt = query(Uptime).rule((r) => {
|
|
124
|
-
const service =
|
|
125
|
-
const window = r.var("window")
|
|
125
|
+
const { service, window } = v(Outage)
|
|
126
126
|
return r
|
|
127
127
|
.match(Outage, { service, window })
|
|
128
128
|
.where(pointIn(r.param("t"), window))
|
|
129
|
-
.
|
|
129
|
+
.find({ service })
|
|
130
130
|
})
|
|
131
131
|
// overlapping an incident window (one Allen mask, no operator zoo):
|
|
132
132
|
const overlapping = query(Uptime).rule((r) => {
|
|
133
|
-
const service =
|
|
134
|
-
const window = r.var("window")
|
|
133
|
+
const { service, window } = v(Outage)
|
|
135
134
|
return r
|
|
136
135
|
.match(Outage, { service, window })
|
|
137
136
|
.where(allen(window, ALLEN.intersects, r.param("incident")))
|
|
138
|
-
.
|
|
137
|
+
.find({ service, window })
|
|
139
138
|
})
|
|
140
139
|
// total downtime per service (the denotation's one arithmetic):
|
|
141
140
|
const downtime = query(Uptime).rule((r) => {
|
|
142
|
-
const service =
|
|
143
|
-
|
|
144
|
-
return r.match(Outage, { service, window }).select("service", r.sum(r.duration("window")))
|
|
141
|
+
const { service, window } = v(Outage)
|
|
142
|
+
return r.match(Outage, { service, window }).find({ service, downtime: r.sum(r.duration(window)) })
|
|
145
143
|
})
|
|
146
144
|
```
|
|
147
145
|
|
|
@@ -220,11 +218,11 @@ const Optionality = schema("Optionality", { Business, MailingAddress }, [
|
|
|
220
218
|
|
|
221
219
|
// Negation is plain anti-join (no null branch exists in any operator):
|
|
222
220
|
const unaddressed = query(Optionality).rule((r) => {
|
|
223
|
-
const b =
|
|
221
|
+
const { id: b } = v(Business)
|
|
224
222
|
return r
|
|
225
223
|
.match(Business, { id: b })
|
|
226
224
|
.where(not(MailingAddress, { business: b }))
|
|
227
|
-
.
|
|
225
|
+
.find({ b })
|
|
228
226
|
})
|
|
229
227
|
```
|
|
230
228
|
|
|
@@ -260,11 +258,8 @@ const Money = schema("Money", { Currency, Account, Posting }, [
|
|
|
260
258
|
// silently. Bind the fresh id: set semantics would collapse two equal
|
|
261
259
|
// (account, currency, minor) postings without it.
|
|
262
260
|
const totals = query(Money).rule((r) => {
|
|
263
|
-
const id =
|
|
264
|
-
|
|
265
|
-
const currency = r.var("currency")
|
|
266
|
-
const minor = r.var("minor")
|
|
267
|
-
return r.match(Posting, { id, account, currency, minor }).select("account", "currency", r.sum("minor"))
|
|
261
|
+
const { id, account, currency, minor } = v(Posting)
|
|
262
|
+
return r.match(Posting, { id, account, currency, minor }).find({ account, currency, total: r.sum(minor) })
|
|
268
263
|
})
|
|
269
264
|
```
|
|
270
265
|
|
|
@@ -300,8 +295,8 @@ const Content = schema("Content", { Region, Document, Replica }, [
|
|
|
300
295
|
|
|
301
296
|
// a bytes param self-encodes (Uint8Array by inference):
|
|
302
297
|
const byDigest = query(Content).rule((r) => {
|
|
303
|
-
const id =
|
|
304
|
-
return r.match(Document, { id, payload: r.param("digest") }).
|
|
298
|
+
const { id } = v(Document)
|
|
299
|
+
return r.match(Document, { id, payload: r.param("digest") }).find({ id })
|
|
305
300
|
})
|
|
306
301
|
```
|
|
307
302
|
|
|
@@ -342,8 +337,8 @@ const Tickets = schema("Tickets", { Priority, Ticket }, [
|
|
|
342
337
|
// that drifts without a rebuild is an ordinary relation — a vocabulary is
|
|
343
338
|
// never written, only declared.
|
|
344
339
|
const urgent = query(Tickets).rule((r) => {
|
|
345
|
-
const t =
|
|
346
|
-
return r.match(Ticket, { id: t, priority: "Urgent" }).
|
|
340
|
+
const { id: t } = v(Ticket)
|
|
341
|
+
return r.match(Ticket, { id: t, priority: "Urgent" }).find({ t })
|
|
347
342
|
})
|
|
348
343
|
|
|
349
344
|
// Set membership is a plain array — the drizzle law's spelling, closed-only
|
|
@@ -351,8 +346,8 @@ const urgent = query(Tickets).rule((r) => {
|
|
|
351
346
|
// ∈-set param, `r.inSet`); the array folds to the same wire set the param
|
|
352
347
|
// spelling crosses. In `.where()` selections arrays work at EVERY field kind.
|
|
353
348
|
const actionable = query(Tickets).rule((r) => {
|
|
354
|
-
const t =
|
|
355
|
-
return r.match(Ticket, { id: t, priority: ["Normal", "Urgent"] }).
|
|
349
|
+
const { id: t } = v(Ticket)
|
|
350
|
+
return r.match(Ticket, { id: t, priority: ["Normal", "Urgent"] }).find({ t })
|
|
356
351
|
})
|
|
357
352
|
```
|
|
358
353
|
|
|
@@ -396,12 +391,11 @@ const Review = schema("Review", { Kind, Attempt, Certificate }, [
|
|
|
396
391
|
// exactly like an ordinary one, and the atom folds at prepare into a
|
|
397
392
|
// plan-constant handle set on its sibling.
|
|
398
393
|
const masteredAttempts = query(Review).rule((r) => {
|
|
399
|
-
const a =
|
|
400
|
-
const k = r.var("k")
|
|
394
|
+
const { id: a, kind: k } = v(Attempt)
|
|
401
395
|
return r
|
|
402
396
|
.match(Attempt, { id: a, kind: k })
|
|
403
397
|
.match(Kind, { id: k, mastered: true })
|
|
404
|
-
.
|
|
398
|
+
.find({ a })
|
|
405
399
|
})
|
|
406
400
|
|
|
407
401
|
// Host dispatch on the payload tier is the record-table idiom — a `Record`
|
|
@@ -460,12 +454,11 @@ const Oncall = schema("Oncall", { Severity, Incident, Escalation }, [
|
|
|
460
454
|
|
|
461
455
|
// who is being paged — the same ψ, on the read side:
|
|
462
456
|
const paged = query(Oncall).rule((r) => {
|
|
463
|
-
const i =
|
|
464
|
-
const s = r.var("s")
|
|
457
|
+
const { incident: i, severity: s } = v(Escalation)
|
|
465
458
|
return r
|
|
466
459
|
.match(Escalation, { incident: i, severity: s })
|
|
467
460
|
.match(Severity, { id: s, pages: true })
|
|
468
|
-
.
|
|
461
|
+
.find({ i })
|
|
469
462
|
})
|
|
470
463
|
```
|
|
471
464
|
|
|
@@ -507,12 +500,11 @@ const Playlists = schema("Playlists", { Playlist, Extent, Slot }, [
|
|
|
507
500
|
|
|
508
501
|
// Positional access is membership — "what plays at position ?pos":
|
|
509
502
|
const playingAt = query(Playlists).rule((r) => {
|
|
510
|
-
const slot =
|
|
511
|
-
const track = r.var("track")
|
|
503
|
+
const { slot, track } = v(Slot)
|
|
512
504
|
return r
|
|
513
505
|
.match(Slot, { playlist: r.param("list"), slot, track })
|
|
514
506
|
.where(pointIn(r.param("pos"), slot))
|
|
515
|
-
.
|
|
507
|
+
.find({ track })
|
|
516
508
|
})
|
|
517
509
|
|
|
518
510
|
// Answers are SETS — the host sorts them, and the SDK ships the comparator:
|
|
@@ -568,12 +560,12 @@ const Ast = schema("Ast", { Kind, Node, Lit, Add, Parent }, [
|
|
|
568
560
|
])
|
|
569
561
|
|
|
570
562
|
const lhsLiteral = query(Ast).rule((r) => {
|
|
571
|
-
const l =
|
|
572
|
-
const
|
|
563
|
+
const { lhs: l } = v(Add)
|
|
564
|
+
const { value } = v(Lit)
|
|
573
565
|
return r
|
|
574
566
|
.match(Add, { node: r.param("n"), lhs: l })
|
|
575
|
-
.match(Lit, { node: l, value
|
|
576
|
-
.
|
|
567
|
+
.match(Lit, { node: l, value })
|
|
568
|
+
.find({ value })
|
|
577
569
|
})
|
|
578
570
|
```
|
|
579
571
|
|
|
@@ -604,13 +596,12 @@ const Graph = schema("Graph", { Person, Repo, Follows, Maintains }, [
|
|
|
604
596
|
// live in the "Person.id" class, so the reuse is lawful); `lt` keeps each
|
|
605
597
|
// pair once:
|
|
606
598
|
const mutual = query(Graph).rule((r) => {
|
|
607
|
-
const a =
|
|
608
|
-
const b = r.var("b")
|
|
599
|
+
const { follower: a, followee: b } = v(Follows)
|
|
609
600
|
return r
|
|
610
601
|
.match(Follows, { follower: a, followee: b })
|
|
611
602
|
.match(Follows, { follower: b, followee: a })
|
|
612
603
|
.where(lt(a, b))
|
|
613
|
-
.
|
|
604
|
+
.find({ a, b })
|
|
614
605
|
})
|
|
615
606
|
```
|
|
616
607
|
|
|
@@ -643,15 +634,12 @@ const Ecs = schema("Ecs", { Entity, Transform, Velocity, Renderable }, [
|
|
|
643
634
|
|
|
644
635
|
// The physics join is the component intersection:
|
|
645
636
|
const physics = query(Ecs).rule((r) => {
|
|
646
|
-
const entity =
|
|
647
|
-
const
|
|
648
|
-
const y = r.var("y")
|
|
649
|
-
const dx = r.var("dx")
|
|
650
|
-
const dy = r.var("dy")
|
|
637
|
+
const { entity, x, y } = v(Transform)
|
|
638
|
+
const { dx, dy } = v(Velocity)
|
|
651
639
|
return r
|
|
652
640
|
.match(Transform, { entity, x, y })
|
|
653
641
|
.match(Velocity, { entity, dx, dy })
|
|
654
|
-
.
|
|
642
|
+
.find({ entity, x, y, dx, dy })
|
|
655
643
|
})
|
|
656
644
|
```
|
|
657
645
|
|
|
@@ -687,12 +675,12 @@ const Orders = schema("Orders", { State, Order, Placement, Shipment }, [
|
|
|
687
675
|
])
|
|
688
676
|
|
|
689
677
|
const shipped = query(Orders).rule((r) => {
|
|
690
|
-
const id =
|
|
691
|
-
const carrier =
|
|
678
|
+
const { id } = v(Order)
|
|
679
|
+
const { carrier } = v(Shipment)
|
|
692
680
|
return r
|
|
693
681
|
.match(Order, { id, state: "Shipped" })
|
|
694
682
|
.match(Shipment, { order: id, carrier })
|
|
695
|
-
.
|
|
683
|
+
.find({ id, carrier })
|
|
696
684
|
})
|
|
697
685
|
```
|
|
698
686
|
|
|
@@ -758,20 +746,18 @@ const Calendar = schema("Calendar", { Rsvp, Arm, Person, Room, Event, Attendance
|
|
|
758
746
|
])
|
|
759
747
|
|
|
760
748
|
const roomConflicts = query(Calendar).rule((r) => {
|
|
761
|
-
const room =
|
|
762
|
-
const span = r.var("span")
|
|
749
|
+
const { room, span } = v(Booking)
|
|
763
750
|
return r
|
|
764
751
|
.match(Booking, { room, span })
|
|
765
752
|
.where(allen(span, ALLEN.intersects, r.param("want")))
|
|
766
|
-
.
|
|
753
|
+
.find({ room, span })
|
|
767
754
|
})
|
|
768
755
|
const personLoad = query(Calendar).rule((r) => {
|
|
769
|
-
const person =
|
|
770
|
-
const span = r.var("span")
|
|
756
|
+
const { person, span } = v(Claim)
|
|
771
757
|
return r
|
|
772
758
|
.match(Claim, { person, span })
|
|
773
759
|
.where(allen(span, ALLEN.intersects, r.param("window")))
|
|
774
|
-
.
|
|
760
|
+
.find({ person, span })
|
|
775
761
|
})
|
|
776
762
|
```
|
|
777
763
|
|
|
@@ -803,23 +789,21 @@ const Pricing = schema("Pricing", { Policy, Version }, [
|
|
|
803
789
|
|
|
804
790
|
// in force on date t — one membership probe:
|
|
805
791
|
const inForce = query(Pricing).rule((r) => {
|
|
806
|
-
const rate_bps =
|
|
807
|
-
const valid = r.var("valid")
|
|
792
|
+
const { rate_bps, valid } = v(Version)
|
|
808
793
|
return r
|
|
809
794
|
.match(Version, { policy: r.param("p"), rate_bps, valid })
|
|
810
795
|
.where(pointIn(r.param("t"), valid))
|
|
811
|
-
.
|
|
796
|
+
.find({ rate_bps })
|
|
812
797
|
})
|
|
813
798
|
// clean successions (half-open makes MEETS exact, no ±1 fudge):
|
|
814
799
|
const successions = query(Pricing).rule((r) => {
|
|
815
|
-
const p =
|
|
816
|
-
const
|
|
817
|
-
const b = r.var("b")
|
|
800
|
+
const { policy: p, valid: a } = v(Version)
|
|
801
|
+
const { valid: b } = v(Version)
|
|
818
802
|
return r
|
|
819
803
|
.match(Version, { policy: p, valid: a })
|
|
820
804
|
.match(Version, { policy: p, valid: b })
|
|
821
805
|
.where(allen(a, ALLEN.meets, b))
|
|
822
|
-
.
|
|
806
|
+
.find({ a, b })
|
|
823
807
|
})
|
|
824
808
|
```
|
|
825
809
|
|
|
@@ -848,12 +832,11 @@ const Payroll = schema("Payroll", { FiscalYear, PayPeriod }, [
|
|
|
848
832
|
|
|
849
833
|
// the period holding date t:
|
|
850
834
|
const holding = query(Payroll).rule((r) => {
|
|
851
|
-
const seq =
|
|
852
|
-
const span = r.var("span")
|
|
835
|
+
const { seq, span } = v(PayPeriod)
|
|
853
836
|
return r
|
|
854
837
|
.match(PayPeriod, { year: r.param("y"), seq, span })
|
|
855
838
|
.where(pointIn(r.param("t"), span))
|
|
856
|
-
.
|
|
839
|
+
.find({ seq })
|
|
857
840
|
})
|
|
858
841
|
```
|
|
859
842
|
|
|
@@ -897,14 +880,13 @@ const Tax = schema("Tax", { Status, Regime, Bracket, Residency, Earned }, [
|
|
|
897
880
|
// owed is host arithmetic over the bracket walk — arithmetic beyond the
|
|
898
881
|
// measure is refused (the ledger).
|
|
899
882
|
const marginal = query(Tax).rule((r) => {
|
|
900
|
-
const reg =
|
|
901
|
-
const b =
|
|
902
|
-
const rate_bps = r.var("rate_bps")
|
|
883
|
+
const { id: reg } = v(Regime)
|
|
884
|
+
const { income: b, rate_bps } = v(Bracket)
|
|
903
885
|
return r
|
|
904
886
|
.match(Regime, { id: reg, year: r.param("y"), status: r.param("s") })
|
|
905
887
|
.match(Bracket, { regime: reg, income: b, rate_bps })
|
|
906
888
|
.where(pointIn(r.param("taxable"), b))
|
|
907
|
-
.
|
|
889
|
+
.find({ rate_bps })
|
|
908
890
|
})
|
|
909
891
|
```
|
|
910
892
|
|
|
@@ -931,15 +913,13 @@ const FreeTime = schema("FreeTime", { Person, Claim }, [
|
|
|
931
913
|
|
|
932
914
|
// busy time, coalesced (adjacent segments merge — the half-open law):
|
|
933
915
|
const busy = query(FreeTime).rule((r) => {
|
|
934
|
-
const person =
|
|
935
|
-
|
|
936
|
-
return r.match(Claim, { person, span }).select("person", r.pack("span"))
|
|
916
|
+
const { person, span } = v(Claim)
|
|
917
|
+
return r.match(Claim, { person, span }).find({ person, packed: r.pack(span) })
|
|
937
918
|
})
|
|
938
919
|
// raw claimed time (overlaps double-count — often the wrong question):
|
|
939
920
|
const claimed = query(FreeTime).rule((r) => {
|
|
940
|
-
const person =
|
|
941
|
-
|
|
942
|
-
return r.match(Claim, { person, span }).select("person", r.sum(r.duration("span")))
|
|
921
|
+
const { person, span } = v(Claim)
|
|
922
|
+
return r.match(Claim, { person, span }).find({ person, claimed: r.sum(r.duration(span)) })
|
|
943
923
|
})
|
|
944
924
|
// Coalesced totals = the two-query composition (pack, then a host fold) —
|
|
945
925
|
// aggregates never nest; free time (gaps) is the two-line host walk over
|
|
@@ -979,17 +959,13 @@ const Ledger = schema("Ledger", { Account, JournalEntry, Posting }, [
|
|
|
979
959
|
|
|
980
960
|
// balances (bind the fresh id — set semantics collapses duplicates):
|
|
981
961
|
const balances = query(Ledger).rule((r) => {
|
|
982
|
-
const id =
|
|
983
|
-
|
|
984
|
-
const minor = r.var("minor")
|
|
985
|
-
return r.match(Posting, { id, account, minor }).select("account", r.sum("minor"))
|
|
962
|
+
const { id, account, minor } = v(Posting)
|
|
963
|
+
return r.match(Posting, { id, account, minor }).find({ account, balance: r.sum(minor) })
|
|
986
964
|
})
|
|
987
965
|
// double-entry audit (host asserts every total is 0 — discipline, not schema):
|
|
988
966
|
const doubleEntry = query(Ledger).rule((r) => {
|
|
989
|
-
const id =
|
|
990
|
-
|
|
991
|
-
const minor = r.var("minor")
|
|
992
|
-
return r.match(Posting, { id, entry, minor }).select("entry", r.sum("minor"))
|
|
967
|
+
const { id, entry, minor } = v(Posting)
|
|
968
|
+
return r.match(Posting, { id, entry, minor }).find({ entry, balance: r.sum(minor) })
|
|
993
969
|
})
|
|
994
970
|
```
|
|
995
971
|
|
|
@@ -1024,9 +1000,8 @@ const Jobs = schema("Jobs", { State, Job, Lease }, [
|
|
|
1024
1000
|
|
|
1025
1001
|
// update-where's premise — "still Queued" is the witness:
|
|
1026
1002
|
const stillQueued = query(Jobs).rule((r) => {
|
|
1027
|
-
const id =
|
|
1028
|
-
|
|
1029
|
-
return r.match(Job, { id, state: "Queued", payload }).select("id", "payload")
|
|
1003
|
+
const { id, payload } = v(Job)
|
|
1004
|
+
return r.match(Job, { id, state: "Queued", payload }).find({ id, payload })
|
|
1030
1005
|
})
|
|
1031
1006
|
|
|
1032
1007
|
const db = await Db.create("./jobs.db", Jobs)
|
|
@@ -1082,9 +1057,8 @@ const Rollup = schema("Rollup", { Arm, Claim, BusySpan }, [
|
|
|
1082
1057
|
// against sources it didn't actually read. The deriving query (pack IS the
|
|
1083
1058
|
// coalesce):
|
|
1084
1059
|
const deriving = query(Rollup).rule((r) => {
|
|
1085
|
-
const person =
|
|
1086
|
-
|
|
1087
|
-
return r.match(Claim, { person, span, arm: "Busy" }).select("person", r.pack("span"))
|
|
1060
|
+
const { person, span } = v(Claim)
|
|
1061
|
+
return r.match(Claim, { person, span, arm: "Busy" }).find({ person, packed: r.pack(span) })
|
|
1088
1062
|
})
|
|
1089
1063
|
```
|
|
1090
1064
|
|
|
@@ -1117,20 +1091,20 @@ const Payments = schema("Payments", { Kind, Payment, Card, Ach }, [
|
|
|
1117
1091
|
// provably disjoint, so the executor elides cross-rule dedup — the free lunch.
|
|
1118
1092
|
const wholeDu = query(Payments)
|
|
1119
1093
|
.rule((r) => {
|
|
1120
|
-
const id =
|
|
1121
|
-
const n =
|
|
1094
|
+
const { id } = v(Payment)
|
|
1095
|
+
const { last4: n } = v(Card)
|
|
1122
1096
|
return r
|
|
1123
1097
|
.match(Payment, { id, kind: "Card" })
|
|
1124
1098
|
.match(Card, { payment: id, last4: n })
|
|
1125
|
-
.
|
|
1099
|
+
.find({ id, n })
|
|
1126
1100
|
})
|
|
1127
1101
|
.rule((r) => {
|
|
1128
|
-
const id =
|
|
1129
|
-
const n =
|
|
1102
|
+
const { id } = v(Payment)
|
|
1103
|
+
const { routing: n } = v(Ach)
|
|
1130
1104
|
return r
|
|
1131
1105
|
.match(Payment, { id, kind: "Ach" })
|
|
1132
1106
|
.match(Ach, { payment: id, routing: n })
|
|
1133
|
-
.
|
|
1107
|
+
.find({ id, n })
|
|
1134
1108
|
})
|
|
1135
1109
|
```
|
|
1136
1110
|
|
|
@@ -1198,8 +1172,8 @@ const Closure = schema("Closure", { Node, Parent }, [
|
|
|
1198
1172
|
|
|
1199
1173
|
// The loop's one query — the frontier's children, one ∈-set probe:
|
|
1200
1174
|
const step = query(Closure).rule((r) => {
|
|
1201
|
-
const c =
|
|
1202
|
-
return r.match(Parent, { child: c, parent: r.inSet("frontier") }).
|
|
1175
|
+
const { child: c } = v(Parent)
|
|
1176
|
+
return r.match(Parent, { child: c, parent: r.inSet("frontier") }).find({ c })
|
|
1203
1177
|
})
|
|
1204
1178
|
```
|
|
1205
1179
|
|
|
@@ -1242,23 +1216,22 @@ const reach = program(Closure, (p) => {
|
|
|
1242
1216
|
const rec = p.rec("reach")
|
|
1243
1217
|
const seeded = rec
|
|
1244
1218
|
.rule((r) => {
|
|
1245
|
-
const c =
|
|
1219
|
+
const { id: c } = v(Node)
|
|
1246
1220
|
return r
|
|
1247
1221
|
.match(Node, { id: c })
|
|
1248
1222
|
.where(eq(c, r.param("root")))
|
|
1249
|
-
.
|
|
1223
|
+
.find({ c })
|
|
1250
1224
|
})
|
|
1251
1225
|
.rule((r) => {
|
|
1252
|
-
const c =
|
|
1253
|
-
const parent = r.var("parent")
|
|
1226
|
+
const { child: c, parent } = v(Parent)
|
|
1254
1227
|
return r
|
|
1255
1228
|
.match(Parent, { child: c, parent })
|
|
1256
|
-
.idb(rec, parent)
|
|
1257
|
-
.
|
|
1229
|
+
.idb(rec, { c: parent })
|
|
1230
|
+
.find({ c })
|
|
1258
1231
|
})
|
|
1259
1232
|
return p.output((r) => {
|
|
1260
|
-
const c =
|
|
1261
|
-
return r.match(Node, { id: c }).idb(seeded, c).
|
|
1233
|
+
const { id: c } = v(Node)
|
|
1234
|
+
return r.match(Node, { id: c }).idb(seeded, { c }).find({ c })
|
|
1262
1235
|
})
|
|
1263
1236
|
})
|
|
1264
1237
|
const reachPrepared = db.prepare(reach)
|
|
@@ -1300,15 +1273,14 @@ const Accounts = schema("Accounts", { Account, AccountParent, Posting }, [
|
|
|
1300
1273
|
// The two queries the host rollup composes:
|
|
1301
1274
|
// the frontier step (recipe 24's loop, verbatim):
|
|
1302
1275
|
const frontierStep = query(Accounts).rule((r) => {
|
|
1303
|
-
const c =
|
|
1304
|
-
return r.match(AccountParent, { child: c, parent: r.inSet("frontier") }).
|
|
1276
|
+
const { child: c } = v(AccountParent)
|
|
1277
|
+
return r.match(AccountParent, { child: c, parent: r.inSet("frontier") }).find({ c })
|
|
1305
1278
|
})
|
|
1306
1279
|
// the rollup over the accumulated subtree (bind the fresh id — recipe
|
|
1307
1280
|
// 19's discipline, spent again; equal postings to one account both count):
|
|
1308
1281
|
const subtreeRollup = query(Accounts).rule((r) => {
|
|
1309
|
-
const id =
|
|
1310
|
-
|
|
1311
|
-
return r.match(Posting, { id, account: r.inSet("subtree"), minor }).select(r.sum("minor"))
|
|
1282
|
+
const { id, minor } = v(Posting)
|
|
1283
|
+
return r.match(Posting, { id, account: r.inSet("subtree"), minor }).find({ total: r.sum(minor) })
|
|
1312
1284
|
})
|
|
1313
1285
|
// The engine-native form: the closure stratum converges first, then the
|
|
1314
1286
|
// output's fold runs once over the finished subtree.
|
|
@@ -1316,28 +1288,25 @@ const nativeRollup = program(Accounts, (p) => {
|
|
|
1316
1288
|
const sub = p.rec("sub")
|
|
1317
1289
|
const seeded = sub
|
|
1318
1290
|
.rule((r) => {
|
|
1319
|
-
const a =
|
|
1291
|
+
const { id: a } = v(Account)
|
|
1320
1292
|
return r
|
|
1321
1293
|
.match(Account, { id: a })
|
|
1322
1294
|
.where(eq(a, r.param("root")))
|
|
1323
|
-
.
|
|
1295
|
+
.find({ a })
|
|
1324
1296
|
})
|
|
1325
1297
|
.rule((r) => {
|
|
1326
|
-
const a =
|
|
1327
|
-
const parent = r.var("parent")
|
|
1298
|
+
const { child: a, parent } = v(AccountParent)
|
|
1328
1299
|
return r
|
|
1329
1300
|
.match(AccountParent, { child: a, parent })
|
|
1330
|
-
.idb(sub, parent)
|
|
1331
|
-
.
|
|
1301
|
+
.idb(sub, { a: parent })
|
|
1302
|
+
.find({ a })
|
|
1332
1303
|
})
|
|
1333
1304
|
return p.output((r) => {
|
|
1334
|
-
const id =
|
|
1335
|
-
const a = r.var("a")
|
|
1336
|
-
const minor = r.var("minor")
|
|
1305
|
+
const { id, account: a, minor } = v(Posting)
|
|
1337
1306
|
return r
|
|
1338
1307
|
.match(Posting, { id, account: a, minor })
|
|
1339
|
-
.idb(seeded, a)
|
|
1340
|
-
.
|
|
1308
|
+
.idb(seeded, { a })
|
|
1309
|
+
.find({ total: r.sum(minor) })
|
|
1341
1310
|
})
|
|
1342
1311
|
})
|
|
1343
1312
|
```
|
|
@@ -1403,10 +1372,8 @@ const MaintainedRollup = schema("MaintainedRollup", { Arm, Claim, BusySpan }, [
|
|
|
1403
1372
|
|
|
1404
1373
|
// Derive the desired rollup on the maintenance snapshot:
|
|
1405
1374
|
const deriving = query(MaintainedRollup).rule((r) => {
|
|
1406
|
-
const source =
|
|
1407
|
-
|
|
1408
|
-
const span = r.var("span")
|
|
1409
|
-
return r.match(Claim, { source, person, arm: "Busy", span }).select("person", r.pack("span"))
|
|
1375
|
+
const { source, person, span } = v(Claim)
|
|
1376
|
+
return r.match(Claim, { source, person, arm: "Busy", span }).find({ person, packed: r.pack(span) })
|
|
1410
1377
|
})
|
|
1411
1378
|
```
|
|
1412
1379
|
|
|
@@ -1459,15 +1426,13 @@ const Payroll = schema("Payroll", { Employee, Salary }, [
|
|
|
1459
1426
|
|
|
1460
1427
|
// The post-migration read — salaries in force at an instant:
|
|
1461
1428
|
const inForceAt = query(Payroll).rule((r) => {
|
|
1462
|
-
const e =
|
|
1463
|
-
const
|
|
1464
|
-
const amount = r.var("amount")
|
|
1465
|
-
const w = r.var("w")
|
|
1429
|
+
const { id: e, name } = v(Employee)
|
|
1430
|
+
const { amount, applies: w } = v(Salary)
|
|
1466
1431
|
return r
|
|
1467
1432
|
.match(Employee, { id: e, name })
|
|
1468
1433
|
.match(Salary, { employee: e, amount, applies: w })
|
|
1469
1434
|
.where(pointIn(r.param("at"), w))
|
|
1470
|
-
.
|
|
1435
|
+
.find({ name, amount })
|
|
1471
1436
|
})
|
|
1472
1437
|
```
|
|
1473
1438
|
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ classes, inferred query rows, and rejections that arrive as data rather than
|
|
|
27
27
|
exceptions.
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { bool, closed, contained, Db, gt, type Infer, key, on, query, relation, schema, u64 } from "@bjornpagen/bumbledb"
|
|
30
|
+
import { bool, closed, contained, Db, gt, type Infer, key, on, query, relation, schema, u64, v } from "@bjornpagen/bumbledb"
|
|
31
31
|
|
|
32
32
|
// A closed relation: a sealed roster of axioms with typed payload columns.
|
|
33
33
|
// At the host surface a handle is its NAME — the string literal "DirectPass"
|
|
@@ -75,18 +75,18 @@ if (!result.ok) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// Query: Datalog as values.
|
|
79
|
-
//
|
|
78
|
+
// Query: Datalog as values. v(R) mints a fresh variable per column, typed by
|
|
79
|
+
// the column's law-class; reusing one by object reference IS the join, and
|
|
80
|
+
// rows are typed from the find keys. Params are typed by use.
|
|
80
81
|
// `gt` is one of the free comparison exports.
|
|
81
82
|
const certifiedAbove = query(Review).rule((r) => {
|
|
82
|
-
const a =
|
|
83
|
-
const
|
|
84
|
-
const rank = r.var("rank")
|
|
83
|
+
const { attempt: a, kind: k } = v(Certificate)
|
|
84
|
+
const { rank } = v(Kind)
|
|
85
85
|
return r
|
|
86
86
|
.match(Certificate, { attempt: a, kind: k })
|
|
87
|
-
.match(Kind, { id: k, mastered: true, rank }) // ψ on the read side too
|
|
87
|
+
.match(Kind, { id: k, mastered: true, rank }) // k reused at Kind.id — that reuse is the join; ψ on the read side too
|
|
88
88
|
.where(gt(rank, r.param("floor")))
|
|
89
|
-
.
|
|
89
|
+
.find({ a, rank })
|
|
90
90
|
})
|
|
91
91
|
|
|
92
92
|
const prepared = db.prepare(certifiedAbove)
|
|
@@ -120,7 +120,7 @@ The drizzle law governs this surface: the SDK's job at the host boundary is tran
|
|
|
120
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.
|
|
121
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.
|
|
122
122
|
- The `Db` runtime — `Db.create`/`Db.open`, path-cached stores, transactions, typed violations, scoped snapshot reads, the witnessed write loop with `abandon`.
|
|
123
|
-
- The query surface — Datalog as values, `query(S).rule(r => ...)`:
|
|
123
|
+
- The query surface — Datalog as values, `query(S).rule(r => ...)`: `v(R)`-minted vars (identity is the object reference — reusing one across binding positions IS the join), `find({...})` named result heads (renames are real), params typed by use unchanged, 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.
|
|
124
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`).
|
|
125
125
|
|
|
126
126
|
## Cookbook
|
package/dist/db.js
CHANGED
|
@@ -492,7 +492,7 @@ function openDb(handle, theory, manifest) {
|
|
|
492
492
|
const rows = bridged("execute bumbledb prepared query", function callExecute() {
|
|
493
493
|
return native.preparedExecute(plan.handle, state.handle, wire);
|
|
494
494
|
});
|
|
495
|
-
return decodeAnswers(plan.
|
|
495
|
+
return decodeAnswers(plan.finds, rows);
|
|
496
496
|
}
|
|
497
497
|
const scope = Object.freeze({
|
|
498
498
|
generation,
|
|
@@ -927,7 +927,7 @@ function openDb(handle, theory, manifest) {
|
|
|
927
927
|
handle: preparedHandle,
|
|
928
928
|
owner,
|
|
929
929
|
params: q.data.params,
|
|
930
|
-
|
|
930
|
+
finds: q.data.finds
|
|
931
931
|
}));
|
|
932
932
|
planReclaimer.register(prepared, preparedHandle);
|
|
933
933
|
return prepared;
|