@suss/datalog 0.37.0 → 0.38.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/dist/index.cjs CHANGED
@@ -50,45 +50,93 @@ __export(index_exports, {
50
50
  module.exports = __toCommonJS(index_exports);
51
51
 
52
52
  // src/factIndex.ts
53
- var emptyNode = () => ({
54
- present: false,
55
- tag: void 0,
56
- next: void 0
57
- });
53
+ var FactKey = class {
54
+ constructor(tuple) {
55
+ this.tuple = tuple;
56
+ }
57
+ tuple;
58
+ present = false;
59
+ tag = void 0;
60
+ };
61
+ var Branch = class {
62
+ /** The key of a tuple that ends at this column, as a shorter one in a relation of mixed arity does. */
63
+ here = void 0;
64
+ next = /* @__PURE__ */ new Map();
65
+ };
66
+ function sameFrom(a, b, from) {
67
+ if (a.length !== b.length) {
68
+ return false;
69
+ }
70
+ for (let at = from; at < a.length; at++) {
71
+ if (a[at] !== b[at]) {
72
+ return false;
73
+ }
74
+ }
75
+ return true;
76
+ }
77
+ function place(branch, key, depth) {
78
+ if (depth === key.tuple.length) {
79
+ branch.here = key;
80
+ } else {
81
+ branch.next.set(key.tuple[depth], key);
82
+ }
83
+ }
84
+ function soleKey(branch) {
85
+ if (branch.here !== void 0) {
86
+ return branch.next.size === 0 ? branch.here : void 0;
87
+ }
88
+ if (branch.next.size !== 1) {
89
+ return void 0;
90
+ }
91
+ const only = branch.next.values().next().value;
92
+ return only instanceof FactKey ? only : void 0;
93
+ }
58
94
  var FactIndex = class {
59
95
  /** How many facts are present. */
60
96
  size = 0;
61
- root = emptyNode();
97
+ root = new Branch();
62
98
  /** This tuple's key here, whether or not the fact is present. */
63
99
  key(tuple) {
64
- let node = this.root;
65
- for (let i = 0; i < tuple.length; i++) {
66
- let next = node.next;
67
- if (next === void 0) {
68
- next = /* @__PURE__ */ new Map();
69
- node.next = next;
100
+ let branch = this.root;
101
+ for (let depth = 0; ; depth++) {
102
+ if (depth === tuple.length) {
103
+ branch.here ??= new FactKey(tuple);
104
+ return branch.here;
70
105
  }
71
- const atom = tuple[i];
72
- let child = next.get(atom);
106
+ const atom = tuple[depth];
107
+ const child = branch.next.get(atom);
73
108
  if (child === void 0) {
74
- child = emptyNode();
75
- next.set(atom, child);
109
+ const key = new FactKey(tuple);
110
+ branch.next.set(atom, key);
111
+ return key;
112
+ }
113
+ if (child instanceof Branch) {
114
+ branch = child;
115
+ continue;
116
+ }
117
+ if (sameFrom(child.tuple, tuple, depth + 1)) {
118
+ return child;
76
119
  }
77
- node = child;
120
+ const split = new Branch();
121
+ place(split, child, depth + 1);
122
+ branch.next.set(atom, split);
123
+ branch = split;
78
124
  }
79
- return node;
80
125
  }
81
126
  /** The key of a fact this relation has, or undefined when it has none. */
82
127
  find(tuple) {
83
- let node = this.root;
84
- for (let i = 0; i < tuple.length; i++) {
85
- const child = node.next?.get(tuple[i]);
128
+ let branch = this.root;
129
+ for (let depth = 0; ; depth++) {
130
+ const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
86
131
  if (child === void 0) {
87
132
  return void 0;
88
133
  }
89
- node = child;
134
+ if (child instanceof Branch) {
135
+ branch = child;
136
+ continue;
137
+ }
138
+ return child.present && sameFrom(child.tuple, tuple, depth + 1) ? child : void 0;
90
139
  }
91
- return node.present ? node : void 0;
92
140
  }
93
141
  has(key) {
94
142
  return key.present;
@@ -112,32 +160,45 @@ var FactIndex = class {
112
160
  this.size -= 1;
113
161
  }
114
162
  /**
115
- * Drop what is left of a removed tuple's path, up to the first node
116
- * another fact still needs. Without this a relation that is retracted
117
- * from over and over keeps a node per tuple it ever had.
163
+ * Drop a removed tuple's key, and any branch left with one key to
164
+ * tell apart. Without this a relation that is retracted from over and
165
+ * over keeps a key per tuple it ever had.
118
166
  */
119
167
  prune(tuple) {
120
- const path = [this.root];
121
- let node = this.root;
122
- for (let i = 0; i < tuple.length; i++) {
123
- const child = node.next?.get(tuple[i]);
168
+ const path = [];
169
+ let branch = this.root;
170
+ let depth = 0;
171
+ for (; ; depth++) {
172
+ path.push(branch);
173
+ const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
124
174
  if (child === void 0) {
125
175
  return;
126
176
  }
127
- node = child;
128
- path.push(child);
177
+ if (child instanceof Branch) {
178
+ branch = child;
179
+ continue;
180
+ }
181
+ if (child.present || !sameFrom(child.tuple, tuple, depth + 1)) {
182
+ return;
183
+ }
184
+ break;
185
+ }
186
+ if (depth === tuple.length) {
187
+ branch.here = void 0;
188
+ } else {
189
+ branch.next.delete(tuple[depth]);
129
190
  }
130
- for (let i = path.length - 1; i > 0; i--) {
131
- const child = path[i];
132
- if (child.present || (child.next?.size ?? 0) > 0) {
191
+ for (let at = path.length - 1; at > 0; at--) {
192
+ const sole = soleKey(path[at]);
193
+ if (sole === void 0) {
133
194
  return;
134
195
  }
135
- path[i - 1].next?.delete(tuple[i - 1]);
196
+ path[at - 1].next.set(tuple[at - 1], sole);
136
197
  }
137
198
  }
138
199
  /** Drop every fact, and whatever was kept in order to find them. */
139
200
  clear() {
140
- this.root = emptyNode();
201
+ this.root = new Branch();
141
202
  this.size = 0;
142
203
  }
143
204
  };
@@ -574,6 +635,24 @@ function formatProfile(profile) {
574
635
  }
575
636
 
576
637
  // src/relation.ts
638
+ var isLone = (bucket) => typeof bucket[0] !== "object";
639
+ var NO_TUPLES = [];
640
+ function tuplesIn(bucket) {
641
+ if (bucket === void 0) {
642
+ return NO_TUPLES;
643
+ }
644
+ return isLone(bucket) ? [bucket] : bucket;
645
+ }
646
+ function fileUnder(buckets, key, tuple) {
647
+ const bucket = buckets.get(key);
648
+ if (bucket === void 0) {
649
+ buckets.set(key, tuple);
650
+ } else if (isLone(bucket)) {
651
+ buckets.set(key, [bucket, tuple]);
652
+ } else {
653
+ bucket.push(tuple);
654
+ }
655
+ }
577
656
  var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
578
657
  function storedTuplesOf(relation, facts) {
579
658
  const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
@@ -582,19 +661,24 @@ function storedTuplesOf(relation, facts) {
582
661
  return null;
583
662
  }
584
663
  const stored = facts.map(
585
- (fact) => buckets.get(fact[column])?.find((tuple) => sameTuple(tuple, fact))
664
+ (fact) => tuplesIn(buckets.get(fact[column])).find(
665
+ (tuple) => sameTuple(tuple, fact)
666
+ )
586
667
  );
587
668
  return stored.every((tuple) => tuple !== void 0) ? stored : null;
588
669
  }
589
670
  function removeFrom(buckets, key, tuple) {
590
671
  const bucket = buckets.get(key);
591
- const at = bucket?.indexOf(tuple) ?? -1;
592
- if (bucket === void 0 || at === -1) {
672
+ if (bucket === void 0) {
593
673
  return;
594
674
  }
595
- bucket.splice(at, 1);
596
- if (bucket.length === 0) {
675
+ if (isLone(bucket)) {
597
676
  buckets.delete(key);
677
+ return;
678
+ }
679
+ bucket.splice(bucket.indexOf(tuple), 1);
680
+ if (bucket.length === 1) {
681
+ buckets.set(key, bucket[0]);
598
682
  }
599
683
  }
600
684
  function removeTuples(relation, stored) {
@@ -646,7 +730,7 @@ function addToCombined(index, tuple) {
646
730
  }
647
731
  level = next;
648
732
  }
649
- addToBucket(
733
+ fileUnder(
650
734
  level,
651
735
  tuple[index.columns[last]],
652
736
  tuple
@@ -665,10 +749,10 @@ function combinedBucketIn(relation, mask, values) {
665
749
  for (const value of values) {
666
750
  level = level.get(value);
667
751
  if (level === void 0) {
668
- return [];
752
+ return NO_TUPLES;
669
753
  }
670
754
  }
671
- return level;
755
+ return tuplesIn(level);
672
756
  }
673
757
  function addToBucket(buckets, key, tuple) {
674
758
  const bucket = buckets.get(key);
@@ -685,12 +769,12 @@ function bucketIn(relation, column, value) {
685
769
  for (const tuple of relation.tuples) {
686
770
  const at = tuple[column];
687
771
  if (at !== void 0) {
688
- addToBucket(buckets, at, tuple);
772
+ fileUnder(buckets, at, tuple);
689
773
  }
690
774
  }
691
775
  relation.columns[column] = buckets;
692
776
  }
693
- return buckets.get(value) ?? [];
777
+ return tuplesIn(buckets.get(value));
694
778
  }
695
779
 
696
780
  // src/stratum.ts
@@ -956,7 +1040,7 @@ var Database = class {
956
1040
  const buckets = relation.columns[column];
957
1041
  const value = tuple[column];
958
1042
  if (buckets !== void 0 && value !== void 0) {
959
- addToBucket(buckets, value, tuple);
1043
+ fileUnder(buckets, value, tuple);
960
1044
  }
961
1045
  }
962
1046
  for (const combined of relation.combined.values()) {
package/dist/index.d.cts CHANGED
@@ -7,10 +7,10 @@
7
7
  * The join asks for that key again on every candidate the rules
8
8
  * consider, so what the key costs to work out sets the cost of
9
9
  * evaluation. A relation finds a fact by walking a trie of its tuple's
10
- * atoms, one map hop per column, and the node the walk lands on is the
11
- * fact itself. Nothing is built to identify a tuple, and a membership
12
- * test that fails stops at the first column that misses. DESIGN.md says
13
- * why the walk is over the atoms rather than over interned integers.
10
+ * atoms, one map hop per column, to the first column where no other
11
+ * tuple shares the atoms so far. The key is stored there, and the walk
12
+ * compares the rest of the tuple instead of taking a map per column.
13
+ * DESIGN.md says why the walk is over the atoms, not interned integers.
14
14
  */
15
15
 
16
16
  /**
@@ -18,10 +18,11 @@
18
18
  * nothing to the relation that did not hand it out, and nothing outside
19
19
  * this module reads its fields.
20
20
  */
21
- interface FactKey {
21
+ declare class FactKey {
22
+ readonly tuple: Tuple;
22
23
  present: boolean;
23
24
  tag: unknown;
24
- next: Map<Atom, FactKey> | undefined;
25
+ constructor(tuple: Tuple);
25
26
  }
26
27
  /** The facts one relation has, and the tag stored against each. */
27
28
  declare class FactIndex {
@@ -40,9 +41,9 @@ declare class FactIndex {
40
41
  /** Take the fact away, and the tag with it. */
41
42
  remove(key: FactKey): void;
42
43
  /**
43
- * Drop what is left of a removed tuple's path, up to the first node
44
- * another fact still needs. Without this a relation that is retracted
45
- * from over and over keeps a node per tuple it ever had.
44
+ * Drop a removed tuple's key, and any branch left with one key to
45
+ * tell apart. Without this a relation that is retracted from over and
46
+ * over keeps a key per tuple it ever had.
46
47
  */
47
48
  prune(tuple: Tuple): void;
48
49
  /** Drop every fact, and whatever was kept in order to find them. */
@@ -70,7 +71,7 @@ interface Relation {
70
71
  * encoding it first would build a string out of every node id on every
71
72
  * lookup and buy nothing.
72
73
  */
73
- columns: (Map<Atom, Tuple[]> | undefined)[];
74
+ columns: (Map<Atom, Bucket> | undefined)[];
74
75
  /**
75
76
  * Per set of columns, the tuples under each combination of values at
76
77
  * those columns, keyed by a bitmask of the column positions. Built and
@@ -86,6 +87,13 @@ interface CombinedIndex {
86
87
  columns: readonly number[];
87
88
  root: Map<Atom, unknown>;
88
89
  }
90
+ /**
91
+ * The tuples an index has under one value, in the order they arrived.
92
+ * Most values in a column of node ids belong to a single tuple, and an
93
+ * array for each of those cost more than the tuple itself, so a lone
94
+ * tuple is kept on its own.
95
+ */
96
+ type Bucket = Tuple | Tuple[];
89
97
 
90
98
  /**
91
99
  * Confidence levels as a tag algebra: how sure the run is of each
package/dist/index.d.ts CHANGED
@@ -7,10 +7,10 @@
7
7
  * The join asks for that key again on every candidate the rules
8
8
  * consider, so what the key costs to work out sets the cost of
9
9
  * evaluation. A relation finds a fact by walking a trie of its tuple's
10
- * atoms, one map hop per column, and the node the walk lands on is the
11
- * fact itself. Nothing is built to identify a tuple, and a membership
12
- * test that fails stops at the first column that misses. DESIGN.md says
13
- * why the walk is over the atoms rather than over interned integers.
10
+ * atoms, one map hop per column, to the first column where no other
11
+ * tuple shares the atoms so far. The key is stored there, and the walk
12
+ * compares the rest of the tuple instead of taking a map per column.
13
+ * DESIGN.md says why the walk is over the atoms, not interned integers.
14
14
  */
15
15
 
16
16
  /**
@@ -18,10 +18,11 @@
18
18
  * nothing to the relation that did not hand it out, and nothing outside
19
19
  * this module reads its fields.
20
20
  */
21
- interface FactKey {
21
+ declare class FactKey {
22
+ readonly tuple: Tuple;
22
23
  present: boolean;
23
24
  tag: unknown;
24
- next: Map<Atom, FactKey> | undefined;
25
+ constructor(tuple: Tuple);
25
26
  }
26
27
  /** The facts one relation has, and the tag stored against each. */
27
28
  declare class FactIndex {
@@ -40,9 +41,9 @@ declare class FactIndex {
40
41
  /** Take the fact away, and the tag with it. */
41
42
  remove(key: FactKey): void;
42
43
  /**
43
- * Drop what is left of a removed tuple's path, up to the first node
44
- * another fact still needs. Without this a relation that is retracted
45
- * from over and over keeps a node per tuple it ever had.
44
+ * Drop a removed tuple's key, and any branch left with one key to
45
+ * tell apart. Without this a relation that is retracted from over and
46
+ * over keeps a key per tuple it ever had.
46
47
  */
47
48
  prune(tuple: Tuple): void;
48
49
  /** Drop every fact, and whatever was kept in order to find them. */
@@ -70,7 +71,7 @@ interface Relation {
70
71
  * encoding it first would build a string out of every node id on every
71
72
  * lookup and buy nothing.
72
73
  */
73
- columns: (Map<Atom, Tuple[]> | undefined)[];
74
+ columns: (Map<Atom, Bucket> | undefined)[];
74
75
  /**
75
76
  * Per set of columns, the tuples under each combination of values at
76
77
  * those columns, keyed by a bitmask of the column positions. Built and
@@ -86,6 +87,13 @@ interface CombinedIndex {
86
87
  columns: readonly number[];
87
88
  root: Map<Atom, unknown>;
88
89
  }
90
+ /**
91
+ * The tuples an index has under one value, in the order they arrived.
92
+ * Most values in a column of node ids belong to a single tuple, and an
93
+ * array for each of those cost more than the tuple itself, so a lone
94
+ * tuple is kept on its own.
95
+ */
96
+ type Bucket = Tuple | Tuple[];
89
97
 
90
98
  /**
91
99
  * Confidence levels as a tag algebra: how sure the run is of each
package/dist/index.js CHANGED
@@ -1,43 +1,91 @@
1
1
  // src/factIndex.ts
2
- var emptyNode = () => ({
3
- present: false,
4
- tag: void 0,
5
- next: void 0
6
- });
2
+ var FactKey = class {
3
+ constructor(tuple) {
4
+ this.tuple = tuple;
5
+ }
6
+ tuple;
7
+ present = false;
8
+ tag = void 0;
9
+ };
10
+ var Branch = class {
11
+ /** The key of a tuple that ends at this column, as a shorter one in a relation of mixed arity does. */
12
+ here = void 0;
13
+ next = /* @__PURE__ */ new Map();
14
+ };
15
+ function sameFrom(a, b, from) {
16
+ if (a.length !== b.length) {
17
+ return false;
18
+ }
19
+ for (let at = from; at < a.length; at++) {
20
+ if (a[at] !== b[at]) {
21
+ return false;
22
+ }
23
+ }
24
+ return true;
25
+ }
26
+ function place(branch, key, depth) {
27
+ if (depth === key.tuple.length) {
28
+ branch.here = key;
29
+ } else {
30
+ branch.next.set(key.tuple[depth], key);
31
+ }
32
+ }
33
+ function soleKey(branch) {
34
+ if (branch.here !== void 0) {
35
+ return branch.next.size === 0 ? branch.here : void 0;
36
+ }
37
+ if (branch.next.size !== 1) {
38
+ return void 0;
39
+ }
40
+ const only = branch.next.values().next().value;
41
+ return only instanceof FactKey ? only : void 0;
42
+ }
7
43
  var FactIndex = class {
8
44
  /** How many facts are present. */
9
45
  size = 0;
10
- root = emptyNode();
46
+ root = new Branch();
11
47
  /** This tuple's key here, whether or not the fact is present. */
12
48
  key(tuple) {
13
- let node = this.root;
14
- for (let i = 0; i < tuple.length; i++) {
15
- let next = node.next;
16
- if (next === void 0) {
17
- next = /* @__PURE__ */ new Map();
18
- node.next = next;
49
+ let branch = this.root;
50
+ for (let depth = 0; ; depth++) {
51
+ if (depth === tuple.length) {
52
+ branch.here ??= new FactKey(tuple);
53
+ return branch.here;
19
54
  }
20
- const atom = tuple[i];
21
- let child = next.get(atom);
55
+ const atom = tuple[depth];
56
+ const child = branch.next.get(atom);
22
57
  if (child === void 0) {
23
- child = emptyNode();
24
- next.set(atom, child);
58
+ const key = new FactKey(tuple);
59
+ branch.next.set(atom, key);
60
+ return key;
61
+ }
62
+ if (child instanceof Branch) {
63
+ branch = child;
64
+ continue;
65
+ }
66
+ if (sameFrom(child.tuple, tuple, depth + 1)) {
67
+ return child;
25
68
  }
26
- node = child;
69
+ const split = new Branch();
70
+ place(split, child, depth + 1);
71
+ branch.next.set(atom, split);
72
+ branch = split;
27
73
  }
28
- return node;
29
74
  }
30
75
  /** The key of a fact this relation has, or undefined when it has none. */
31
76
  find(tuple) {
32
- let node = this.root;
33
- for (let i = 0; i < tuple.length; i++) {
34
- const child = node.next?.get(tuple[i]);
77
+ let branch = this.root;
78
+ for (let depth = 0; ; depth++) {
79
+ const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
35
80
  if (child === void 0) {
36
81
  return void 0;
37
82
  }
38
- node = child;
83
+ if (child instanceof Branch) {
84
+ branch = child;
85
+ continue;
86
+ }
87
+ return child.present && sameFrom(child.tuple, tuple, depth + 1) ? child : void 0;
39
88
  }
40
- return node.present ? node : void 0;
41
89
  }
42
90
  has(key) {
43
91
  return key.present;
@@ -61,32 +109,45 @@ var FactIndex = class {
61
109
  this.size -= 1;
62
110
  }
63
111
  /**
64
- * Drop what is left of a removed tuple's path, up to the first node
65
- * another fact still needs. Without this a relation that is retracted
66
- * from over and over keeps a node per tuple it ever had.
112
+ * Drop a removed tuple's key, and any branch left with one key to
113
+ * tell apart. Without this a relation that is retracted from over and
114
+ * over keeps a key per tuple it ever had.
67
115
  */
68
116
  prune(tuple) {
69
- const path = [this.root];
70
- let node = this.root;
71
- for (let i = 0; i < tuple.length; i++) {
72
- const child = node.next?.get(tuple[i]);
117
+ const path = [];
118
+ let branch = this.root;
119
+ let depth = 0;
120
+ for (; ; depth++) {
121
+ path.push(branch);
122
+ const child = depth === tuple.length ? branch.here : branch.next.get(tuple[depth]);
73
123
  if (child === void 0) {
74
124
  return;
75
125
  }
76
- node = child;
77
- path.push(child);
126
+ if (child instanceof Branch) {
127
+ branch = child;
128
+ continue;
129
+ }
130
+ if (child.present || !sameFrom(child.tuple, tuple, depth + 1)) {
131
+ return;
132
+ }
133
+ break;
134
+ }
135
+ if (depth === tuple.length) {
136
+ branch.here = void 0;
137
+ } else {
138
+ branch.next.delete(tuple[depth]);
78
139
  }
79
- for (let i = path.length - 1; i > 0; i--) {
80
- const child = path[i];
81
- if (child.present || (child.next?.size ?? 0) > 0) {
140
+ for (let at = path.length - 1; at > 0; at--) {
141
+ const sole = soleKey(path[at]);
142
+ if (sole === void 0) {
82
143
  return;
83
144
  }
84
- path[i - 1].next?.delete(tuple[i - 1]);
145
+ path[at - 1].next.set(tuple[at - 1], sole);
85
146
  }
86
147
  }
87
148
  /** Drop every fact, and whatever was kept in order to find them. */
88
149
  clear() {
89
- this.root = emptyNode();
150
+ this.root = new Branch();
90
151
  this.size = 0;
91
152
  }
92
153
  };
@@ -523,6 +584,24 @@ function formatProfile(profile) {
523
584
  }
524
585
 
525
586
  // src/relation.ts
587
+ var isLone = (bucket) => typeof bucket[0] !== "object";
588
+ var NO_TUPLES = [];
589
+ function tuplesIn(bucket) {
590
+ if (bucket === void 0) {
591
+ return NO_TUPLES;
592
+ }
593
+ return isLone(bucket) ? [bucket] : bucket;
594
+ }
595
+ function fileUnder(buckets, key, tuple) {
596
+ const bucket = buckets.get(key);
597
+ if (bucket === void 0) {
598
+ buckets.set(key, tuple);
599
+ } else if (isLone(bucket)) {
600
+ buckets.set(key, [bucket, tuple]);
601
+ } else {
602
+ bucket.push(tuple);
603
+ }
604
+ }
526
605
  var sameTuple = (a, b) => a.length === b.length && a.every((atom, at) => atom === b[at]);
527
606
  function storedTuplesOf(relation, facts) {
528
607
  const column = relation.columns.findIndex((buckets2) => buckets2 !== void 0);
@@ -531,19 +610,24 @@ function storedTuplesOf(relation, facts) {
531
610
  return null;
532
611
  }
533
612
  const stored = facts.map(
534
- (fact) => buckets.get(fact[column])?.find((tuple) => sameTuple(tuple, fact))
613
+ (fact) => tuplesIn(buckets.get(fact[column])).find(
614
+ (tuple) => sameTuple(tuple, fact)
615
+ )
535
616
  );
536
617
  return stored.every((tuple) => tuple !== void 0) ? stored : null;
537
618
  }
538
619
  function removeFrom(buckets, key, tuple) {
539
620
  const bucket = buckets.get(key);
540
- const at = bucket?.indexOf(tuple) ?? -1;
541
- if (bucket === void 0 || at === -1) {
621
+ if (bucket === void 0) {
542
622
  return;
543
623
  }
544
- bucket.splice(at, 1);
545
- if (bucket.length === 0) {
624
+ if (isLone(bucket)) {
546
625
  buckets.delete(key);
626
+ return;
627
+ }
628
+ bucket.splice(bucket.indexOf(tuple), 1);
629
+ if (bucket.length === 1) {
630
+ buckets.set(key, bucket[0]);
547
631
  }
548
632
  }
549
633
  function removeTuples(relation, stored) {
@@ -595,7 +679,7 @@ function addToCombined(index, tuple) {
595
679
  }
596
680
  level = next;
597
681
  }
598
- addToBucket(
682
+ fileUnder(
599
683
  level,
600
684
  tuple[index.columns[last]],
601
685
  tuple
@@ -614,10 +698,10 @@ function combinedBucketIn(relation, mask, values) {
614
698
  for (const value of values) {
615
699
  level = level.get(value);
616
700
  if (level === void 0) {
617
- return [];
701
+ return NO_TUPLES;
618
702
  }
619
703
  }
620
- return level;
704
+ return tuplesIn(level);
621
705
  }
622
706
  function addToBucket(buckets, key, tuple) {
623
707
  const bucket = buckets.get(key);
@@ -634,12 +718,12 @@ function bucketIn(relation, column, value) {
634
718
  for (const tuple of relation.tuples) {
635
719
  const at = tuple[column];
636
720
  if (at !== void 0) {
637
- addToBucket(buckets, at, tuple);
721
+ fileUnder(buckets, at, tuple);
638
722
  }
639
723
  }
640
724
  relation.columns[column] = buckets;
641
725
  }
642
- return buckets.get(value) ?? [];
726
+ return tuplesIn(buckets.get(value));
643
727
  }
644
728
 
645
729
  // src/stratum.ts
@@ -905,7 +989,7 @@ var Database = class {
905
989
  const buckets = relation.columns[column];
906
990
  const value = tuple[column];
907
991
  if (buckets !== void 0 && value !== void 0) {
908
- addToBucket(buckets, value, tuple);
992
+ fileUnder(buckets, value, tuple);
909
993
  }
910
994
  }
911
995
  for (const combined of relation.combined.values()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/datalog",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "type": "module",
5
5
  "description": "A small semi-naive Datalog evaluator with stratified negation, the rules engine suss runs its whole-program analyses on.",
6
6
  "keywords": [