@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.cjs CHANGED
@@ -21,7 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Database: () => Database,
24
+ Witness: () => Witness,
24
25
  clearRelations: () => clearRelations,
26
+ confidence: () => confidence,
27
+ confidenceWith: () => confidenceWith,
25
28
  constant: () => constant,
26
29
  deriveOnDemand: () => deriveOnDemand,
27
30
  evaluate: () => evaluate,
@@ -30,14 +33,161 @@ __export(index_exports, {
30
33
  notLit: () => notLit,
31
34
  profileEvaluation: () => profileEvaluation,
32
35
  profileEvaluationAsync: () => profileEvaluationAsync,
36
+ proofOf: () => proofOf,
33
37
  rule: () => rule,
38
+ ruleLabel: () => ruleLabel,
34
39
  stratify: () => stratify,
35
40
  tupleKey: () => tupleKey,
36
41
  tupleKeyParts: () => tupleKeyParts,
37
- variable: () => variable
42
+ variable: () => variable,
43
+ witnesses: () => witnesses
38
44
  });
39
45
  module.exports = __toCommonJS(index_exports);
40
46
 
47
+ // src/onDemand.ts
48
+ var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
49
+ var anyBound = (adornment) => adornment.some(Boolean);
50
+ var keyOf = ({ relation, adornment }) => `${relation}/${adornmentKey(adornment)}`;
51
+ var boundTerms = (terms, adornment) => terms.filter((_, column) => adornment[column]);
52
+ var positiveLiteral = (relation, terms) => ({
53
+ relation,
54
+ terms,
55
+ negated: false
56
+ });
57
+ var demandRewrites = /* @__PURE__ */ new WeakSet();
58
+ var isDemandRewritten = (rules) => demandRewrites.has(rules);
59
+ function deriveOnDemand(rules, complete) {
60
+ const negated = rules.find((r) => r.body.some((l) => l.negated));
61
+ if (negated !== void 0) {
62
+ throw new Error(
63
+ `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`
64
+ );
65
+ }
66
+ const rulesByHead = /* @__PURE__ */ new Map();
67
+ for (const r of rules) {
68
+ const existing = rulesByHead.get(r.head.relation);
69
+ if (existing === void 0) {
70
+ rulesByHead.set(r.head.relation, [r]);
71
+ } else {
72
+ existing.push(r);
73
+ }
74
+ }
75
+ const derives = (relation) => rulesByHead.has(relation);
76
+ const asked = /* @__PURE__ */ new Map();
77
+ const pending = [];
78
+ const want = (adorned) => {
79
+ const key = keyOf(adorned);
80
+ if (!asked.has(key)) {
81
+ asked.set(key, adorned);
82
+ pending.push(adorned);
83
+ }
84
+ };
85
+ for (const relation of complete) {
86
+ const derivedBy = rulesByHead.get(relation);
87
+ if (derivedBy === void 0) {
88
+ throw new Error(
89
+ `no rule derives "${relation}", so it cannot be asked for in full`
90
+ );
91
+ }
92
+ want({ relation, adornment: derivedBy[0].head.terms.map(() => false) });
93
+ }
94
+ while (pending.length > 0) {
95
+ const adorned = pending.pop();
96
+ for (const r of rulesByHead.get(adorned.relation) ?? []) {
97
+ const adornments = bodyAdornments(r, adorned.adornment, derives);
98
+ r.body.forEach((literal, at) => {
99
+ const adornment = adornments[at];
100
+ if (adornment !== null) {
101
+ want({ relation: literal.relation, adornment });
102
+ }
103
+ });
104
+ }
105
+ }
106
+ const variantsOf = /* @__PURE__ */ new Map();
107
+ for (const { relation } of asked.values()) {
108
+ variantsOf.set(relation, (variantsOf.get(relation) ?? 0) + 1);
109
+ }
110
+ const nameOf = (relation, adornment) => {
111
+ if (!anyBound(adornment) || variantsOf.get(relation) === 1) {
112
+ return relation;
113
+ }
114
+ return `${relation}:${adornmentKey(adornment)}`;
115
+ };
116
+ const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
117
+ const rewritten = [];
118
+ const demandDriven = /* @__PURE__ */ new Set();
119
+ const already = /* @__PURE__ */ new Set();
120
+ const emit = (r) => {
121
+ const key = JSON.stringify(r);
122
+ if (!already.has(key)) {
123
+ already.add(key);
124
+ rewritten.push(r);
125
+ }
126
+ };
127
+ for (const adorned of asked.values()) {
128
+ if (anyBound(adorned.adornment)) {
129
+ demandDriven.add(nameOf(adorned.relation, adorned.adornment));
130
+ demandDriven.add(demandOn(adorned.relation, adorned.adornment));
131
+ }
132
+ for (const r of rulesByHead.get(adorned.relation) ?? []) {
133
+ const guard = anyBound(adorned.adornment) ? [
134
+ positiveLiteral(
135
+ demandOn(adorned.relation, adorned.adornment),
136
+ boundTerms(r.head.terms, adorned.adornment)
137
+ )
138
+ ] : [];
139
+ const adornments = bodyAdornments(r, adorned.adornment, derives);
140
+ const body = [];
141
+ r.body.forEach((literal, at) => {
142
+ const adornment = adornments[at];
143
+ if (adornment !== null && anyBound(adornment)) {
144
+ emit({
145
+ head: {
146
+ relation: demandOn(literal.relation, adornment),
147
+ terms: boundTerms(literal.terms, adornment)
148
+ },
149
+ body: [...guard, ...body]
150
+ });
151
+ }
152
+ body.push(
153
+ adornment === null ? literal : positiveLiteral(
154
+ nameOf(literal.relation, adornment),
155
+ literal.terms
156
+ )
157
+ );
158
+ });
159
+ emit({
160
+ head: {
161
+ relation: nameOf(adorned.relation, adorned.adornment),
162
+ terms: r.head.terms
163
+ },
164
+ body: [...guard, ...body]
165
+ });
166
+ }
167
+ }
168
+ demandRewrites.add(rewritten);
169
+ return { rules: rewritten, demandDriven: [...demandDriven] };
170
+ }
171
+ function bodyAdornments(r, headAdornment, derives) {
172
+ const bound = /* @__PURE__ */ new Set();
173
+ r.head.terms.forEach((term, column) => {
174
+ if (headAdornment[column] && term.type === "variable") {
175
+ bound.add(term.name);
176
+ }
177
+ });
178
+ return r.body.map((literal) => {
179
+ const adornment = derives(literal.relation) ? literal.terms.map(
180
+ (term) => term.type === "constant" || bound.has(term.name)
181
+ ) : null;
182
+ for (const term of literal.terms) {
183
+ if (term.type === "variable") {
184
+ bound.add(term.name);
185
+ }
186
+ }
187
+ return adornment;
188
+ });
189
+ }
190
+
41
191
  // src/profile.ts
42
192
  var open = [];
43
193
  var isProfiling = () => open.length > 0;
@@ -229,145 +379,92 @@ function tupleKeyParts(key) {
229
379
  return parts;
230
380
  }
231
381
 
232
- // src/onDemand.ts
233
- var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
234
- var anyBound = (adornment) => adornment.some(Boolean);
235
- var keyOf = ({ relation, adornment }) => `${relation}/${adornmentKey(adornment)}`;
236
- var boundTerms = (terms, adornment) => terms.filter((_, column) => adornment[column]);
237
- var positiveLiteral = (relation, terms) => ({
238
- relation,
239
- terms,
240
- negated: false
241
- });
242
- function deriveOnDemand(rules, complete) {
243
- const negated = rules.find((r) => r.body.some((l) => l.negated));
244
- if (negated !== void 0) {
245
- throw new Error(
246
- `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`
247
- );
248
- }
249
- const rulesByHead = /* @__PURE__ */ new Map();
250
- for (const r of rules) {
251
- const existing = rulesByHead.get(r.head.relation);
252
- if (existing === void 0) {
253
- rulesByHead.set(r.head.relation, [r]);
254
- } else {
255
- existing.push(r);
256
- }
257
- }
258
- const derives = (relation) => rulesByHead.has(relation);
259
- const asked = /* @__PURE__ */ new Map();
260
- const pending = [];
261
- const want = (adorned) => {
262
- const key = keyOf(adorned);
263
- if (!asked.has(key)) {
264
- asked.set(key, adorned);
265
- pending.push(adorned);
382
+ // src/confidence.ts
383
+ var RANK = {
384
+ low: 0,
385
+ medium: 1,
386
+ high: 2
387
+ };
388
+ function confidenceWith(levelOf) {
389
+ return {
390
+ asserted: "high",
391
+ absent: "high",
392
+ combine(bodyTags, derivation) {
393
+ let lowest = levelOf(derivation.rule) ?? "high";
394
+ for (const tag of bodyTags) {
395
+ if (RANK[tag] < RANK[lowest]) {
396
+ lowest = tag;
397
+ }
398
+ }
399
+ return lowest;
400
+ },
401
+ merge(stored, incoming) {
402
+ return RANK[incoming] > RANK[stored] ? incoming : stored;
266
403
  }
267
404
  };
268
- for (const relation of complete) {
269
- const derivedBy = rulesByHead.get(relation);
270
- if (derivedBy === void 0) {
271
- throw new Error(
272
- `no rule derives "${relation}", so it cannot be asked for in full`
273
- );
274
- }
275
- want({ relation, adornment: derivedBy[0].head.terms.map(() => false) });
405
+ }
406
+ var confidence = confidenceWith(
407
+ () => void 0
408
+ );
409
+
410
+ // src/witness.ts
411
+ var Witness = class {
412
+ constructor(rule2, body) {
413
+ this.rule = rule2;
414
+ this.body = body;
276
415
  }
277
- while (pending.length > 0) {
278
- const adorned = pending.pop();
279
- for (const r of rulesByHead.get(adorned.relation) ?? []) {
280
- const adornments = bodyAdornments(r, adorned.adornment, derives);
281
- r.body.forEach((literal, at) => {
282
- const adornment = adornments[at];
283
- if (adornment !== null) {
284
- want({ relation: literal.relation, adornment });
285
- }
286
- });
287
- }
416
+ rule;
417
+ body;
418
+ };
419
+ var witnesses = {
420
+ asserted: "asserted",
421
+ absent: "absent",
422
+ // A witness is built from the derivation alone, and saying so lets
423
+ // the evaluator skip the body-tag lookups, which were most of the
424
+ // tagged path's cost on a large sweep.
425
+ ignoresBodyTags: true,
426
+ combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
427
+ merge: (stored) => stored
428
+ };
429
+ var DEFAULT_MAX_DEPTH = 128;
430
+ function proofOf(db, relation, tuple, options = {}) {
431
+ return walk(
432
+ db,
433
+ relation,
434
+ tuple,
435
+ options.maxDepth ?? DEFAULT_MAX_DEPTH,
436
+ /* @__PURE__ */ new Set()
437
+ );
438
+ }
439
+ var premiseOf = {
440
+ fact: (db, match, depth, path) => walk(db, match.relation, match.tuple, depth, path),
441
+ absence: (_db, match) => ({
442
+ kind: "absence",
443
+ relation: match.relation,
444
+ tuple: match.tuple
445
+ })
446
+ };
447
+ function walk(db, relation, tuple, depth, path) {
448
+ if (!db.has(relation, tuple)) {
449
+ return { kind: "absence", relation, tuple };
288
450
  }
289
- const variantsOf = /* @__PURE__ */ new Map();
290
- for (const { relation } of asked.values()) {
291
- variantsOf.set(relation, (variantsOf.get(relation) ?? 0) + 1);
451
+ const tag = db.tagOf(relation, tuple);
452
+ if (!(tag instanceof Witness)) {
453
+ return { kind: "fact", relation, tuple };
292
454
  }
293
- const nameOf = (relation, adornment) => {
294
- if (!anyBound(adornment) || variantsOf.get(relation) === 1) {
295
- return relation;
296
- }
297
- return `${relation}:${adornmentKey(adornment)}`;
298
- };
299
- const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
300
- const rewritten = [];
301
- const demandDriven = /* @__PURE__ */ new Set();
302
- const already = /* @__PURE__ */ new Set();
303
- const emit = (r) => {
304
- const key = JSON.stringify(r);
305
- if (!already.has(key)) {
306
- already.add(key);
307
- rewritten.push(r);
308
- }
309
- };
310
- for (const adorned of asked.values()) {
311
- if (anyBound(adorned.adornment)) {
312
- demandDriven.add(nameOf(adorned.relation, adorned.adornment));
313
- demandDriven.add(demandOn(adorned.relation, adorned.adornment));
314
- }
315
- for (const r of rulesByHead.get(adorned.relation) ?? []) {
316
- const guard = anyBound(adorned.adornment) ? [
317
- positiveLiteral(
318
- demandOn(adorned.relation, adorned.adornment),
319
- boundTerms(r.head.terms, adorned.adornment)
320
- )
321
- ] : [];
322
- const adornments = bodyAdornments(r, adorned.adornment, derives);
323
- const body = [];
324
- r.body.forEach((literal, at) => {
325
- const adornment = adornments[at];
326
- if (adornment !== null && anyBound(adornment)) {
327
- emit({
328
- head: {
329
- relation: demandOn(literal.relation, adornment),
330
- terms: boundTerms(literal.terms, adornment)
331
- },
332
- body: [...guard, ...body]
333
- });
334
- }
335
- body.push(
336
- adornment === null ? literal : positiveLiteral(
337
- nameOf(literal.relation, adornment),
338
- literal.terms
339
- )
340
- );
341
- });
342
- emit({
343
- head: {
344
- relation: nameOf(adorned.relation, adorned.adornment),
345
- terms: r.head.terms
346
- },
347
- body: [...guard, ...body]
348
- });
349
- }
455
+ if (depth <= 0) {
456
+ return { kind: "truncated", relation, tuple, reason: "depth" };
350
457
  }
351
- return { rules: rewritten, demandDriven: [...demandDriven] };
352
- }
353
- function bodyAdornments(r, headAdornment, derives) {
354
- const bound = /* @__PURE__ */ new Set();
355
- r.head.terms.forEach((term, column) => {
356
- if (headAdornment[column] && term.type === "variable") {
357
- bound.add(term.name);
358
- }
359
- });
360
- return r.body.map((literal) => {
361
- const adornment = derives(literal.relation) ? literal.terms.map(
362
- (term) => term.type === "constant" || bound.has(term.name)
363
- ) : null;
364
- for (const term of literal.terms) {
365
- if (term.type === "variable") {
366
- bound.add(term.name);
367
- }
368
- }
369
- return adornment;
370
- });
458
+ const key = tupleKey([relation, ...tuple]);
459
+ if (path.has(key)) {
460
+ return { kind: "truncated", relation, tuple, reason: "cycle" };
461
+ }
462
+ path.add(key);
463
+ const premises = tag.body.map(
464
+ (match) => premiseOf[match.kind](db, match, depth - 1, path)
465
+ );
466
+ path.delete(key);
467
+ return { kind: "derived", relation, tuple, rule: tag.rule, premises };
371
468
  }
372
469
 
373
470
  // src/index.ts
@@ -383,8 +480,16 @@ var notLit = (relation, ...terms) => ({
383
480
  terms,
384
481
  negated: true
385
482
  });
386
- var rule = (relation, terms, body) => ({ head: { relation, terms }, body });
483
+ var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
484
+ var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
485
+ var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
387
486
  var keyOf2 = (tuple) => tupleKey(tuple);
487
+ function tagSlots(relation) {
488
+ if (relation.tags === void 0) {
489
+ relation.tags = /* @__PURE__ */ new Map();
490
+ }
491
+ return relation.tags;
492
+ }
388
493
  function addToBucket(index, key, tuple) {
389
494
  const bucket = index.get(key);
390
495
  if (bucket === void 0) {
@@ -403,30 +508,53 @@ var Database = class {
403
508
  const created = {
404
509
  keys: /* @__PURE__ */ new Set(),
405
510
  tuples: [],
511
+ tags: void 0,
406
512
  indexes: /* @__PURE__ */ new Map()
407
513
  };
408
514
  this.store.set(name, created);
409
515
  return created;
410
516
  }
411
- /** Add a fact; returns true when the tuple is new. */
412
- add(relationName, tuple) {
517
+ /**
518
+ * Add a fact, or merge a tag into one already present. A new fact
519
+ * stores `tag` when one is given. For a fact already present,
520
+ * `merge` gets the stored tag (undefined when there is none) and the
521
+ * incoming one; returning anything but the stored value stores the
522
+ * result and reports "improved".
523
+ */
524
+ add(relationName, tuple, tag, merge) {
413
525
  const relation = this.relation(relationName);
414
526
  const key = keyOf2(tuple);
415
527
  if (relation.keys.has(key)) {
528
+ let outcome = "unchanged";
529
+ if (merge !== void 0) {
530
+ const stored = relation.tags?.get(key);
531
+ const merged = merge(stored, tag);
532
+ if (merged !== stored) {
533
+ tagSlots(relation).set(key, merged);
534
+ outcome = "improved";
535
+ }
536
+ }
416
537
  if (!isDeriving(this)) {
417
538
  claimFact(this, relationName, key);
418
539
  }
419
- return false;
540
+ return outcome;
420
541
  }
421
542
  relation.keys.add(key);
422
543
  relation.tuples.push(tuple);
544
+ if (tag !== void 0) {
545
+ tagSlots(relation).set(key, tag);
546
+ }
423
547
  for (const [column, index] of relation.indexes) {
424
548
  const value = tuple[column];
425
549
  if (value !== void 0) {
426
550
  addToBucket(index, value, tuple);
427
551
  }
428
552
  }
429
- return true;
553
+ return "added";
554
+ }
555
+ /** The tag stored for this fact, or undefined when there is none. */
556
+ tagOf(relationName, tuple) {
557
+ return this.store.get(relationName)?.tags?.get(keyOf2(tuple));
430
558
  }
431
559
  has(relationName, tuple) {
432
560
  return this.store.get(relationName)?.keys.has(keyOf2(tuple)) ?? false;
@@ -485,6 +613,7 @@ var Database = class {
485
613
  }
486
614
  for (const key of going) {
487
615
  relation.keys.delete(key);
616
+ relation.tags?.delete(key);
488
617
  }
489
618
  relation.tuples = relation.tuples.filter(
490
619
  (tuple) => !going.has(keyOf2(tuple))
@@ -625,6 +754,69 @@ function boundSource(db, literal, bindings) {
625
754
  }
626
755
  return db.facts(literal.relation);
627
756
  }
757
+ var EMPTY_TAGS = [];
758
+ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
759
+ const results = [];
760
+ const bodyTags = [];
761
+ const bodyMatches = [];
762
+ const readsTags = algebra.ignoresBodyTags !== true;
763
+ const step = (literalIndex, positiveIndex, bindings) => {
764
+ if (literalIndex === r.body.length) {
765
+ results.push({
766
+ tuple: headTuple(r.head, bindings),
767
+ tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
768
+ rule: r,
769
+ body: bodyMatches.slice()
770
+ })
771
+ });
772
+ return;
773
+ }
774
+ const literal = r.body[literalIndex];
775
+ if (literal.negated) {
776
+ const grounded = groundNegated(literal, bindings);
777
+ if (!db.has(literal.relation, grounded)) {
778
+ if (readsTags) {
779
+ bodyTags.push(algebra.absent);
780
+ }
781
+ bodyMatches.push({
782
+ kind: "absence",
783
+ relation: literal.relation,
784
+ tuple: grounded
785
+ });
786
+ step(literalIndex + 1, positiveIndex, bindings);
787
+ if (readsTags) {
788
+ bodyTags.pop();
789
+ }
790
+ bodyMatches.pop();
791
+ }
792
+ return;
793
+ }
794
+ const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
795
+ for (const tuple of source) {
796
+ const next = unify(literal, tuple, bindings);
797
+ if (next !== null) {
798
+ if (readsTags) {
799
+ const stored = db.tagOf(literal.relation, tuple);
800
+ bodyTags.push(
801
+ stored === void 0 ? algebra.asserted : stored
802
+ );
803
+ }
804
+ bodyMatches.push({
805
+ kind: "fact",
806
+ relation: literal.relation,
807
+ tuple
808
+ });
809
+ step(literalIndex + 1, positiveIndex + 1, next);
810
+ if (readsTags) {
811
+ bodyTags.pop();
812
+ }
813
+ bodyMatches.pop();
814
+ }
815
+ }
816
+ };
817
+ step(0, 0, /* @__PURE__ */ new Map());
818
+ return results;
819
+ }
628
820
  var evaluated = /* @__PURE__ */ new WeakMap();
629
821
  var deriving = /* @__PURE__ */ new Map();
630
822
  function isDeriving(db) {
@@ -684,10 +876,15 @@ function currentMarks(db) {
684
876
  }
685
877
  return marks;
686
878
  }
687
- function evaluate(db, rules) {
879
+ function evaluate(db, rules, algebra) {
880
+ if (algebra !== void 0 && isDemandRewritten(rules)) {
881
+ throw new Error(
882
+ "cannot evaluate demand-rewritten rules with a tag algebra: tags would follow the demand-transformed rules rather than the ones the caller wrote"
883
+ );
884
+ }
688
885
  deriving.set(db, (deriving.get(db) ?? 0) + 1);
689
886
  try {
690
- return runRules(db, rules);
887
+ return runRules(db, rules, algebra);
691
888
  } finally {
692
889
  const depth = (deriving.get(db) ?? 1) - 1;
693
890
  if (depth === 0) {
@@ -728,7 +925,16 @@ function couldProduce(db, rule2) {
728
925
  (literal) => literal.negated || db.size(literal.relation) > 0
729
926
  );
730
927
  }
731
- function runRules(db, rules) {
928
+ function storedMergeFor(algebra) {
929
+ return (stored, incoming) => {
930
+ const merged = algebra.merge(
931
+ stored === void 0 ? algebra.asserted : stored,
932
+ incoming
933
+ );
934
+ return stored === void 0 && merged === algebra.asserted ? void 0 : merged;
935
+ };
936
+ }
937
+ function runRules(db, rules, algebra) {
732
938
  const {
733
939
  signature,
734
940
  name: ruleSetName,
@@ -749,37 +955,64 @@ function runRules(db, rules) {
749
955
  state.derived.clear();
750
956
  }
751
957
  const derived = state.derived;
958
+ const noteDerived = (relation, tuple) => {
959
+ let ledger = derived.get(relation);
960
+ if (ledger === void 0) {
961
+ ledger = /* @__PURE__ */ new Map();
962
+ derived.set(relation, ledger);
963
+ }
964
+ ledger.set(keyOf2(tuple), tuple);
965
+ };
966
+ const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
752
967
  const marks = canResume(rules, state) ? state.marks : void 0;
753
968
  for (const stratum of strata) {
754
969
  let delta = /* @__PURE__ */ new Map();
755
970
  const derivedHere = new Set(stratum.map((r) => r.head.relation));
756
971
  const record = (relation, tuple) => {
757
- if (db.add(relation, tuple)) {
972
+ if (db.add(relation, tuple) === "added") {
758
973
  addTo(delta, relation, tuple);
759
- let ledger = derived.get(relation);
760
- if (ledger === void 0) {
761
- ledger = /* @__PURE__ */ new Map();
762
- derived.set(relation, ledger);
974
+ noteDerived(relation, tuple);
975
+ }
976
+ };
977
+ const recordTagged = mergeStored === void 0 ? void 0 : (relation, tuple, tag) => {
978
+ const outcome = db.add(relation, tuple, tag, mergeStored);
979
+ if (outcome === "unchanged") {
980
+ return;
981
+ }
982
+ addTo(delta, relation, tuple);
983
+ if (outcome === "added") {
984
+ noteDerived(relation, tuple);
985
+ }
986
+ };
987
+ const deriveInto = (r, seed, deltaIndex) => {
988
+ if (algebra !== void 0 && recordTagged !== void 0) {
989
+ for (const found of evaluateRuleTagged(
990
+ db,
991
+ seed,
992
+ r,
993
+ deltaIndex,
994
+ algebra
995
+ )) {
996
+ recordTagged(r.head.relation, found.tuple, found.tag);
763
997
  }
764
- ledger.set(keyOf2(tuple), tuple);
998
+ return;
999
+ }
1000
+ for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
1001
+ record(r.head.relation, tuple);
765
1002
  }
766
1003
  };
767
1004
  const runOneRule = (r, seed, deltaIndex) => {
768
1005
  if (!isProfiling()) {
769
- for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
770
- record(r.head.relation, tuple);
771
- }
1006
+ deriveInto(r, seed, deltaIndex);
772
1007
  return;
773
1008
  }
774
1009
  const startedAt = performance.now();
775
1010
  const before = db.size(r.head.relation);
776
- for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
777
- record(r.head.relation, tuple);
778
- }
1011
+ deriveInto(r, seed, deltaIndex);
779
1012
  chargeRule(
780
1013
  ruleSetName,
781
1014
  r.head.relation,
782
- r.body.map((l) => l.negated ? `!${l.relation}` : l.relation),
1015
+ r.body.map(literalName),
783
1016
  performance.now() - startedAt,
784
1017
  db.size(r.head.relation) - before
785
1018
  );
@@ -847,7 +1080,10 @@ function addTo(buckets, relation, tuple) {
847
1080
  // Annotate the CommonJS export names for ESM import in node:
848
1081
  0 && (module.exports = {
849
1082
  Database,
1083
+ Witness,
850
1084
  clearRelations,
1085
+ confidence,
1086
+ confidenceWith,
851
1087
  constant,
852
1088
  deriveOnDemand,
853
1089
  evaluate,
@@ -856,9 +1092,12 @@ function addTo(buckets, relation, tuple) {
856
1092
  notLit,
857
1093
  profileEvaluation,
858
1094
  profileEvaluationAsync,
1095
+ proofOf,
859
1096
  rule,
1097
+ ruleLabel,
860
1098
  stratify,
861
1099
  tupleKey,
862
1100
  tupleKeyParts,
863
- variable
1101
+ variable,
1102
+ witnesses
864
1103
  });