@suss/datalog 0.10.0 → 0.11.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.js CHANGED
@@ -1,3 +1,147 @@
1
+ // src/onDemand.ts
2
+ var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
3
+ var anyBound = (adornment) => adornment.some(Boolean);
4
+ var keyOf = ({ relation, adornment }) => `${relation}/${adornmentKey(adornment)}`;
5
+ var boundTerms = (terms, adornment) => terms.filter((_, column) => adornment[column]);
6
+ var positiveLiteral = (relation, terms) => ({
7
+ relation,
8
+ terms,
9
+ negated: false
10
+ });
11
+ var demandRewrites = /* @__PURE__ */ new WeakSet();
12
+ var isDemandRewritten = (rules) => demandRewrites.has(rules);
13
+ function deriveOnDemand(rules, complete) {
14
+ const negated = rules.find((r) => r.body.some((l) => l.negated));
15
+ if (negated !== void 0) {
16
+ throw new Error(
17
+ `cannot derive "${negated.head.relation}" on demand: its rule uses negation, and a relation derived only where somebody asked can make a negated literal match where it did not`
18
+ );
19
+ }
20
+ const rulesByHead = /* @__PURE__ */ new Map();
21
+ for (const r of rules) {
22
+ const existing = rulesByHead.get(r.head.relation);
23
+ if (existing === void 0) {
24
+ rulesByHead.set(r.head.relation, [r]);
25
+ } else {
26
+ existing.push(r);
27
+ }
28
+ }
29
+ const derives = (relation) => rulesByHead.has(relation);
30
+ const asked = /* @__PURE__ */ new Map();
31
+ const pending = [];
32
+ const want = (adorned) => {
33
+ const key = keyOf(adorned);
34
+ if (!asked.has(key)) {
35
+ asked.set(key, adorned);
36
+ pending.push(adorned);
37
+ }
38
+ };
39
+ for (const relation of complete) {
40
+ const derivedBy = rulesByHead.get(relation);
41
+ if (derivedBy === void 0) {
42
+ throw new Error(
43
+ `no rule derives "${relation}", so it cannot be asked for in full`
44
+ );
45
+ }
46
+ want({ relation, adornment: derivedBy[0].head.terms.map(() => false) });
47
+ }
48
+ while (pending.length > 0) {
49
+ const adorned = pending.pop();
50
+ for (const r of rulesByHead.get(adorned.relation) ?? []) {
51
+ const adornments = bodyAdornments(r, adorned.adornment, derives);
52
+ r.body.forEach((literal, at) => {
53
+ const adornment = adornments[at];
54
+ if (adornment !== null) {
55
+ want({ relation: literal.relation, adornment });
56
+ }
57
+ });
58
+ }
59
+ }
60
+ const variantsOf = /* @__PURE__ */ new Map();
61
+ for (const { relation } of asked.values()) {
62
+ variantsOf.set(relation, (variantsOf.get(relation) ?? 0) + 1);
63
+ }
64
+ const nameOf = (relation, adornment) => {
65
+ if (!anyBound(adornment) || variantsOf.get(relation) === 1) {
66
+ return relation;
67
+ }
68
+ return `${relation}:${adornmentKey(adornment)}`;
69
+ };
70
+ const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
71
+ const rewritten = [];
72
+ const demandDriven = /* @__PURE__ */ new Set();
73
+ const already = /* @__PURE__ */ new Set();
74
+ const emit = (r) => {
75
+ const key = JSON.stringify(r);
76
+ if (!already.has(key)) {
77
+ already.add(key);
78
+ rewritten.push(r);
79
+ }
80
+ };
81
+ for (const adorned of asked.values()) {
82
+ if (anyBound(adorned.adornment)) {
83
+ demandDriven.add(nameOf(adorned.relation, adorned.adornment));
84
+ demandDriven.add(demandOn(adorned.relation, adorned.adornment));
85
+ }
86
+ for (const r of rulesByHead.get(adorned.relation) ?? []) {
87
+ const guard = anyBound(adorned.adornment) ? [
88
+ positiveLiteral(
89
+ demandOn(adorned.relation, adorned.adornment),
90
+ boundTerms(r.head.terms, adorned.adornment)
91
+ )
92
+ ] : [];
93
+ const adornments = bodyAdornments(r, adorned.adornment, derives);
94
+ const body = [];
95
+ r.body.forEach((literal, at) => {
96
+ const adornment = adornments[at];
97
+ if (adornment !== null && anyBound(adornment)) {
98
+ emit({
99
+ head: {
100
+ relation: demandOn(literal.relation, adornment),
101
+ terms: boundTerms(literal.terms, adornment)
102
+ },
103
+ body: [...guard, ...body]
104
+ });
105
+ }
106
+ body.push(
107
+ adornment === null ? literal : positiveLiteral(
108
+ nameOf(literal.relation, adornment),
109
+ literal.terms
110
+ )
111
+ );
112
+ });
113
+ emit({
114
+ head: {
115
+ relation: nameOf(adorned.relation, adorned.adornment),
116
+ terms: r.head.terms
117
+ },
118
+ body: [...guard, ...body]
119
+ });
120
+ }
121
+ }
122
+ demandRewrites.add(rewritten);
123
+ return { rules: rewritten, demandDriven: [...demandDriven] };
124
+ }
125
+ function bodyAdornments(r, headAdornment, derives) {
126
+ const bound = /* @__PURE__ */ new Set();
127
+ r.head.terms.forEach((term, column) => {
128
+ if (headAdornment[column] && term.type === "variable") {
129
+ bound.add(term.name);
130
+ }
131
+ });
132
+ return r.body.map((literal) => {
133
+ const adornment = derives(literal.relation) ? literal.terms.map(
134
+ (term) => term.type === "constant" || bound.has(term.name)
135
+ ) : null;
136
+ for (const term of literal.terms) {
137
+ if (term.type === "variable") {
138
+ bound.add(term.name);
139
+ }
140
+ }
141
+ return adornment;
142
+ });
143
+ }
144
+
1
145
  // src/profile.ts
2
146
  var open = [];
3
147
  var isProfiling = () => open.length > 0;
@@ -189,145 +333,92 @@ function tupleKeyParts(key) {
189
333
  return parts;
190
334
  }
191
335
 
192
- // src/onDemand.ts
193
- var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
194
- var anyBound = (adornment) => adornment.some(Boolean);
195
- var keyOf = ({ relation, adornment }) => `${relation}/${adornmentKey(adornment)}`;
196
- var boundTerms = (terms, adornment) => terms.filter((_, column) => adornment[column]);
197
- var positiveLiteral = (relation, terms) => ({
198
- relation,
199
- terms,
200
- negated: false
201
- });
202
- function deriveOnDemand(rules, complete) {
203
- const negated = rules.find((r) => r.body.some((l) => l.negated));
204
- if (negated !== void 0) {
205
- throw new Error(
206
- `cannot derive "${negated.head.relation}" on demand: its rule uses negation, and a relation derived only where somebody asked can make a negated literal match where it did not`
207
- );
208
- }
209
- const rulesByHead = /* @__PURE__ */ new Map();
210
- for (const r of rules) {
211
- const existing = rulesByHead.get(r.head.relation);
212
- if (existing === void 0) {
213
- rulesByHead.set(r.head.relation, [r]);
214
- } else {
215
- existing.push(r);
216
- }
217
- }
218
- const derives = (relation) => rulesByHead.has(relation);
219
- const asked = /* @__PURE__ */ new Map();
220
- const pending = [];
221
- const want = (adorned) => {
222
- const key = keyOf(adorned);
223
- if (!asked.has(key)) {
224
- asked.set(key, adorned);
225
- pending.push(adorned);
336
+ // src/confidence.ts
337
+ var RANK = {
338
+ low: 0,
339
+ medium: 1,
340
+ high: 2
341
+ };
342
+ function confidenceWith(levelOf) {
343
+ return {
344
+ asserted: "high",
345
+ absent: "high",
346
+ combine(bodyTags, derivation) {
347
+ let lowest = levelOf(derivation.rule) ?? "high";
348
+ for (const tag of bodyTags) {
349
+ if (RANK[tag] < RANK[lowest]) {
350
+ lowest = tag;
351
+ }
352
+ }
353
+ return lowest;
354
+ },
355
+ merge(stored, incoming) {
356
+ return RANK[incoming] > RANK[stored] ? incoming : stored;
226
357
  }
227
358
  };
228
- for (const relation of complete) {
229
- const derivedBy = rulesByHead.get(relation);
230
- if (derivedBy === void 0) {
231
- throw new Error(
232
- `no rule derives "${relation}", so it cannot be asked for in full`
233
- );
234
- }
235
- want({ relation, adornment: derivedBy[0].head.terms.map(() => false) });
359
+ }
360
+ var confidence = confidenceWith(
361
+ () => void 0
362
+ );
363
+
364
+ // src/witness.ts
365
+ var Witness = class {
366
+ constructor(rule2, body) {
367
+ this.rule = rule2;
368
+ this.body = body;
236
369
  }
237
- while (pending.length > 0) {
238
- const adorned = pending.pop();
239
- for (const r of rulesByHead.get(adorned.relation) ?? []) {
240
- const adornments = bodyAdornments(r, adorned.adornment, derives);
241
- r.body.forEach((literal, at) => {
242
- const adornment = adornments[at];
243
- if (adornment !== null) {
244
- want({ relation: literal.relation, adornment });
245
- }
246
- });
247
- }
370
+ rule;
371
+ body;
372
+ };
373
+ var witnesses = {
374
+ asserted: "asserted",
375
+ absent: "absent",
376
+ // A witness is built from the derivation alone, and saying so lets
377
+ // the evaluator skip the body-tag lookups, which were most of the
378
+ // tagged path's cost on a large sweep.
379
+ ignoresBodyTags: true,
380
+ combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
381
+ merge: (stored) => stored
382
+ };
383
+ var DEFAULT_MAX_DEPTH = 128;
384
+ function proofOf(db, relation, tuple, options = {}) {
385
+ return walk(
386
+ db,
387
+ relation,
388
+ tuple,
389
+ options.maxDepth ?? DEFAULT_MAX_DEPTH,
390
+ /* @__PURE__ */ new Set()
391
+ );
392
+ }
393
+ var premiseOf = {
394
+ fact: (db, match, depth, path) => walk(db, match.relation, match.tuple, depth, path),
395
+ absence: (_db, match) => ({
396
+ kind: "absence",
397
+ relation: match.relation,
398
+ tuple: match.tuple
399
+ })
400
+ };
401
+ function walk(db, relation, tuple, depth, path) {
402
+ if (!db.has(relation, tuple)) {
403
+ return { kind: "absence", relation, tuple };
248
404
  }
249
- const variantsOf = /* @__PURE__ */ new Map();
250
- for (const { relation } of asked.values()) {
251
- variantsOf.set(relation, (variantsOf.get(relation) ?? 0) + 1);
405
+ const tag = db.tagOf(relation, tuple);
406
+ if (!(tag instanceof Witness)) {
407
+ return { kind: "fact", relation, tuple };
252
408
  }
253
- const nameOf = (relation, adornment) => {
254
- if (!anyBound(adornment) || variantsOf.get(relation) === 1) {
255
- return relation;
256
- }
257
- return `${relation}:${adornmentKey(adornment)}`;
258
- };
259
- const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
260
- const rewritten = [];
261
- const demandDriven = /* @__PURE__ */ new Set();
262
- const already = /* @__PURE__ */ new Set();
263
- const emit = (r) => {
264
- const key = JSON.stringify(r);
265
- if (!already.has(key)) {
266
- already.add(key);
267
- rewritten.push(r);
268
- }
269
- };
270
- for (const adorned of asked.values()) {
271
- if (anyBound(adorned.adornment)) {
272
- demandDriven.add(nameOf(adorned.relation, adorned.adornment));
273
- demandDriven.add(demandOn(adorned.relation, adorned.adornment));
274
- }
275
- for (const r of rulesByHead.get(adorned.relation) ?? []) {
276
- const guard = anyBound(adorned.adornment) ? [
277
- positiveLiteral(
278
- demandOn(adorned.relation, adorned.adornment),
279
- boundTerms(r.head.terms, adorned.adornment)
280
- )
281
- ] : [];
282
- const adornments = bodyAdornments(r, adorned.adornment, derives);
283
- const body = [];
284
- r.body.forEach((literal, at) => {
285
- const adornment = adornments[at];
286
- if (adornment !== null && anyBound(adornment)) {
287
- emit({
288
- head: {
289
- relation: demandOn(literal.relation, adornment),
290
- terms: boundTerms(literal.terms, adornment)
291
- },
292
- body: [...guard, ...body]
293
- });
294
- }
295
- body.push(
296
- adornment === null ? literal : positiveLiteral(
297
- nameOf(literal.relation, adornment),
298
- literal.terms
299
- )
300
- );
301
- });
302
- emit({
303
- head: {
304
- relation: nameOf(adorned.relation, adorned.adornment),
305
- terms: r.head.terms
306
- },
307
- body: [...guard, ...body]
308
- });
309
- }
409
+ if (depth <= 0) {
410
+ return { kind: "truncated", relation, tuple, reason: "depth" };
310
411
  }
311
- return { rules: rewritten, demandDriven: [...demandDriven] };
312
- }
313
- function bodyAdornments(r, headAdornment, derives) {
314
- const bound = /* @__PURE__ */ new Set();
315
- r.head.terms.forEach((term, column) => {
316
- if (headAdornment[column] && term.type === "variable") {
317
- bound.add(term.name);
318
- }
319
- });
320
- return r.body.map((literal) => {
321
- const adornment = derives(literal.relation) ? literal.terms.map(
322
- (term) => term.type === "constant" || bound.has(term.name)
323
- ) : null;
324
- for (const term of literal.terms) {
325
- if (term.type === "variable") {
326
- bound.add(term.name);
327
- }
328
- }
329
- return adornment;
330
- });
412
+ const key = tupleKey([relation, ...tuple]);
413
+ if (path.has(key)) {
414
+ return { kind: "truncated", relation, tuple, reason: "cycle" };
415
+ }
416
+ path.add(key);
417
+ const premises = tag.body.map(
418
+ (match) => premiseOf[match.kind](db, match, depth - 1, path)
419
+ );
420
+ path.delete(key);
421
+ return { kind: "derived", relation, tuple, rule: tag.rule, premises };
331
422
  }
332
423
 
333
424
  // src/index.ts
@@ -343,8 +434,16 @@ var notLit = (relation, ...terms) => ({
343
434
  terms,
344
435
  negated: true
345
436
  });
346
- var rule = (relation, terms, body) => ({ head: { relation, terms }, body });
437
+ var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
438
+ var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
439
+ var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
347
440
  var keyOf2 = (tuple) => tupleKey(tuple);
441
+ function tagSlots(relation) {
442
+ if (relation.tags === void 0) {
443
+ relation.tags = /* @__PURE__ */ new Map();
444
+ }
445
+ return relation.tags;
446
+ }
348
447
  function addToBucket(index, key, tuple) {
349
448
  const bucket = index.get(key);
350
449
  if (bucket === void 0) {
@@ -363,30 +462,53 @@ var Database = class {
363
462
  const created = {
364
463
  keys: /* @__PURE__ */ new Set(),
365
464
  tuples: [],
465
+ tags: void 0,
366
466
  indexes: /* @__PURE__ */ new Map()
367
467
  };
368
468
  this.store.set(name, created);
369
469
  return created;
370
470
  }
371
- /** Add a fact; returns true when the tuple is new. */
372
- add(relationName, tuple) {
471
+ /**
472
+ * Add a fact, or merge a tag into one already present. A new fact
473
+ * stores `tag` when one is given. For a fact already present,
474
+ * `merge` gets the stored tag (undefined when there is none) and the
475
+ * incoming one; returning anything but the stored value stores the
476
+ * result and reports "improved".
477
+ */
478
+ add(relationName, tuple, tag, merge) {
373
479
  const relation = this.relation(relationName);
374
480
  const key = keyOf2(tuple);
375
481
  if (relation.keys.has(key)) {
482
+ let outcome = "unchanged";
483
+ if (merge !== void 0) {
484
+ const stored = relation.tags?.get(key);
485
+ const merged = merge(stored, tag);
486
+ if (merged !== stored) {
487
+ tagSlots(relation).set(key, merged);
488
+ outcome = "improved";
489
+ }
490
+ }
376
491
  if (!isDeriving(this)) {
377
492
  claimFact(this, relationName, key);
378
493
  }
379
- return false;
494
+ return outcome;
380
495
  }
381
496
  relation.keys.add(key);
382
497
  relation.tuples.push(tuple);
498
+ if (tag !== void 0) {
499
+ tagSlots(relation).set(key, tag);
500
+ }
383
501
  for (const [column, index] of relation.indexes) {
384
502
  const value = tuple[column];
385
503
  if (value !== void 0) {
386
504
  addToBucket(index, value, tuple);
387
505
  }
388
506
  }
389
- return true;
507
+ return "added";
508
+ }
509
+ /** The tag stored for this fact, or undefined when there is none. */
510
+ tagOf(relationName, tuple) {
511
+ return this.store.get(relationName)?.tags?.get(keyOf2(tuple));
390
512
  }
391
513
  has(relationName, tuple) {
392
514
  return this.store.get(relationName)?.keys.has(keyOf2(tuple)) ?? false;
@@ -445,6 +567,7 @@ var Database = class {
445
567
  }
446
568
  for (const key of going) {
447
569
  relation.keys.delete(key);
570
+ relation.tags?.delete(key);
448
571
  }
449
572
  relation.tuples = relation.tuples.filter(
450
573
  (tuple) => !going.has(keyOf2(tuple))
@@ -585,6 +708,69 @@ function boundSource(db, literal, bindings) {
585
708
  }
586
709
  return db.facts(literal.relation);
587
710
  }
711
+ var EMPTY_TAGS = [];
712
+ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
713
+ const results = [];
714
+ const bodyTags = [];
715
+ const bodyMatches = [];
716
+ const readsTags = algebra.ignoresBodyTags !== true;
717
+ const step = (literalIndex, positiveIndex, bindings) => {
718
+ if (literalIndex === r.body.length) {
719
+ results.push({
720
+ tuple: headTuple(r.head, bindings),
721
+ tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
722
+ rule: r,
723
+ body: bodyMatches.slice()
724
+ })
725
+ });
726
+ return;
727
+ }
728
+ const literal = r.body[literalIndex];
729
+ if (literal.negated) {
730
+ const grounded = groundNegated(literal, bindings);
731
+ if (!db.has(literal.relation, grounded)) {
732
+ if (readsTags) {
733
+ bodyTags.push(algebra.absent);
734
+ }
735
+ bodyMatches.push({
736
+ kind: "absence",
737
+ relation: literal.relation,
738
+ tuple: grounded
739
+ });
740
+ step(literalIndex + 1, positiveIndex, bindings);
741
+ if (readsTags) {
742
+ bodyTags.pop();
743
+ }
744
+ bodyMatches.pop();
745
+ }
746
+ return;
747
+ }
748
+ const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
749
+ for (const tuple of source) {
750
+ const next = unify(literal, tuple, bindings);
751
+ if (next !== null) {
752
+ if (readsTags) {
753
+ const stored = db.tagOf(literal.relation, tuple);
754
+ bodyTags.push(
755
+ stored === void 0 ? algebra.asserted : stored
756
+ );
757
+ }
758
+ bodyMatches.push({
759
+ kind: "fact",
760
+ relation: literal.relation,
761
+ tuple
762
+ });
763
+ step(literalIndex + 1, positiveIndex + 1, next);
764
+ if (readsTags) {
765
+ bodyTags.pop();
766
+ }
767
+ bodyMatches.pop();
768
+ }
769
+ }
770
+ };
771
+ step(0, 0, /* @__PURE__ */ new Map());
772
+ return results;
773
+ }
588
774
  var evaluated = /* @__PURE__ */ new WeakMap();
589
775
  var deriving = /* @__PURE__ */ new Map();
590
776
  function isDeriving(db) {
@@ -644,10 +830,15 @@ function currentMarks(db) {
644
830
  }
645
831
  return marks;
646
832
  }
647
- function evaluate(db, rules) {
833
+ function evaluate(db, rules, algebra) {
834
+ if (algebra !== void 0 && isDemandRewritten(rules)) {
835
+ throw new Error(
836
+ "cannot evaluate demand-rewritten rules with a tag algebra: tags would follow the demand-transformed rules rather than the ones the caller wrote"
837
+ );
838
+ }
648
839
  deriving.set(db, (deriving.get(db) ?? 0) + 1);
649
840
  try {
650
- return runRules(db, rules);
841
+ return runRules(db, rules, algebra);
651
842
  } finally {
652
843
  const depth = (deriving.get(db) ?? 1) - 1;
653
844
  if (depth === 0) {
@@ -688,7 +879,16 @@ function couldProduce(db, rule2) {
688
879
  (literal) => literal.negated || db.size(literal.relation) > 0
689
880
  );
690
881
  }
691
- function runRules(db, rules) {
882
+ function storedMergeFor(algebra) {
883
+ return (stored, incoming) => {
884
+ const merged = algebra.merge(
885
+ stored === void 0 ? algebra.asserted : stored,
886
+ incoming
887
+ );
888
+ return stored === void 0 && merged === algebra.asserted ? void 0 : merged;
889
+ };
890
+ }
891
+ function runRules(db, rules, algebra) {
692
892
  const {
693
893
  signature,
694
894
  name: ruleSetName,
@@ -709,37 +909,64 @@ function runRules(db, rules) {
709
909
  state.derived.clear();
710
910
  }
711
911
  const derived = state.derived;
912
+ const noteDerived = (relation, tuple) => {
913
+ let ledger = derived.get(relation);
914
+ if (ledger === void 0) {
915
+ ledger = /* @__PURE__ */ new Map();
916
+ derived.set(relation, ledger);
917
+ }
918
+ ledger.set(keyOf2(tuple), tuple);
919
+ };
920
+ const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
712
921
  const marks = canResume(rules, state) ? state.marks : void 0;
713
922
  for (const stratum of strata) {
714
923
  let delta = /* @__PURE__ */ new Map();
715
924
  const derivedHere = new Set(stratum.map((r) => r.head.relation));
716
925
  const record = (relation, tuple) => {
717
- if (db.add(relation, tuple)) {
926
+ if (db.add(relation, tuple) === "added") {
718
927
  addTo(delta, relation, tuple);
719
- let ledger = derived.get(relation);
720
- if (ledger === void 0) {
721
- ledger = /* @__PURE__ */ new Map();
722
- derived.set(relation, ledger);
928
+ noteDerived(relation, tuple);
929
+ }
930
+ };
931
+ const recordTagged = mergeStored === void 0 ? void 0 : (relation, tuple, tag) => {
932
+ const outcome = db.add(relation, tuple, tag, mergeStored);
933
+ if (outcome === "unchanged") {
934
+ return;
935
+ }
936
+ addTo(delta, relation, tuple);
937
+ if (outcome === "added") {
938
+ noteDerived(relation, tuple);
939
+ }
940
+ };
941
+ const deriveInto = (r, seed, deltaIndex) => {
942
+ if (algebra !== void 0 && recordTagged !== void 0) {
943
+ for (const found of evaluateRuleTagged(
944
+ db,
945
+ seed,
946
+ r,
947
+ deltaIndex,
948
+ algebra
949
+ )) {
950
+ recordTagged(r.head.relation, found.tuple, found.tag);
723
951
  }
724
- ledger.set(keyOf2(tuple), tuple);
952
+ return;
953
+ }
954
+ for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
955
+ record(r.head.relation, tuple);
725
956
  }
726
957
  };
727
958
  const runOneRule = (r, seed, deltaIndex) => {
728
959
  if (!isProfiling()) {
729
- for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
730
- record(r.head.relation, tuple);
731
- }
960
+ deriveInto(r, seed, deltaIndex);
732
961
  return;
733
962
  }
734
963
  const startedAt = performance.now();
735
964
  const before = db.size(r.head.relation);
736
- for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
737
- record(r.head.relation, tuple);
738
- }
965
+ deriveInto(r, seed, deltaIndex);
739
966
  chargeRule(
740
967
  ruleSetName,
741
968
  r.head.relation,
742
- r.body.map((l) => l.negated ? `!${l.relation}` : l.relation),
969
+ r.body.map(literalName),
743
970
  performance.now() - startedAt,
744
971
  db.size(r.head.relation) - before
745
972
  );
@@ -806,7 +1033,10 @@ function addTo(buckets, relation, tuple) {
806
1033
  }
807
1034
  export {
808
1035
  Database,
1036
+ Witness,
809
1037
  clearRelations,
1038
+ confidence,
1039
+ confidenceWith,
810
1040
  constant,
811
1041
  deriveOnDemand,
812
1042
  evaluate,
@@ -815,9 +1045,12 @@ export {
815
1045
  notLit,
816
1046
  profileEvaluation,
817
1047
  profileEvaluationAsync,
1048
+ proofOf,
818
1049
  rule,
1050
+ ruleLabel,
819
1051
  stratify,
820
1052
  tupleKey,
821
1053
  tupleKeyParts,
822
- variable
1054
+ variable,
1055
+ witnesses
823
1056
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/datalog",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "A small semi-naïve Datalog evaluator with stratified negation — the rules engine behind suss's derived program facts.",
6
6
  "keywords": [