@suss/datalog 0.38.0 → 0.38.1

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.d.cts CHANGED
@@ -50,6 +50,108 @@ declare class FactIndex {
50
50
  clear(): void;
51
51
  }
52
52
 
53
+ /**
54
+ * What an evaluation looked up, so a caller that cached its answer can
55
+ * tell later whether a fact that arrived since could change it.
56
+ *
57
+ * While `noteReads` runs, every lookup the join makes is noted as the
58
+ * relation, the column and the value, and a relation read whole is
59
+ * noted whole. At the end each lookup's facts are counted. The cached
60
+ * answer is still right when every count is the same and nothing was
61
+ * retracted from those relations. Without a retraction counts only
62
+ * grow, so the sum of the counts changes exactly when one of them does.
63
+ * DESIGN.md explains why a fact no lookup would find cannot change the
64
+ * answer, and what that asks of the rules.
65
+ */
66
+
67
+ /** @internal One relation's reads: the values looked up per column, unless something read it whole. */
68
+ interface RelationReads {
69
+ whole: boolean;
70
+ columns: (Set<Atom> | undefined)[];
71
+ }
72
+ /** @internal The join notes its reads here while `noteReads` runs. */
73
+ declare class ReadLog {
74
+ private readonly skipped;
75
+ /** Null for a relation the caller said to leave out. */
76
+ private readonly byRelation;
77
+ constructor(skipped: ReadonlySet<string>);
78
+ lookedUp(relation: string, column: number, value: Atom): void;
79
+ readWhole(relation: string): void;
80
+ /** Note everything an inner log noted, for an answer cached inside another. */
81
+ absorb(inner: ReadLog): void;
82
+ /**
83
+ * The reads with what each one finds in `db` now. One question looks
84
+ * up the same values in many columns, so each value is kept once, and
85
+ * each column keeps a bit per value saying whether it was looked up
86
+ * there.
87
+ */
88
+ counted(db: Database): ReadsNoted;
89
+ /** @internal Null for a relation the caller said to leave out. */
90
+ readsOf(relation: string): RelationReads | null;
91
+ }
92
+ /**
93
+ * Group `g` is the column `columns[g]` of the relation at
94
+ * `names[groups[g]]`. Its bits start at word `g` times the words one
95
+ * group takes, and bit `i` says it looked up `values[i]`.
96
+ */
97
+ interface Lookups {
98
+ names: readonly string[];
99
+ groups: readonly number[];
100
+ columns: readonly number[];
101
+ values: readonly Atom[];
102
+ bits: Uint32Array;
103
+ }
104
+ /**
105
+ * The lookups one evaluation made, and how many facts they found when
106
+ * it finished. The values are the atoms the facts already contain, so
107
+ * a value costs a reference, and each column it was looked up in costs
108
+ * a bit.
109
+ */
110
+ declare class ReadsNoted {
111
+ private readonly skipped;
112
+ private readonly found;
113
+ private readonly names;
114
+ private readonly groups;
115
+ private readonly columns;
116
+ private readonly values;
117
+ private readonly bits;
118
+ private readonly words;
119
+ private readonly removed;
120
+ /** The facts in every relation read, when the counts last matched. */
121
+ private facts;
122
+ /** Each relation read, found by name the first time a check needs it. */
123
+ private readonly relations;
124
+ constructor(db: Database, skipped: ReadonlySet<string>, lookups: Lookups, found: number);
125
+ /**
126
+ * Whether every read would find what it found then. A fact left in a
127
+ * skipped relation would join a question asked now without anything
128
+ * here having looked it up, so one there makes the answer no. The
129
+ * counts are taken again only when a relation read has grown.
130
+ */
131
+ unchangedIn(db: Database): boolean;
132
+ private relationAt;
133
+ private sumOver;
134
+ /**
135
+ * The facts the reads find, stopping once past `limit`. With nothing
136
+ * retracted every count only grows, so passing the old total already
137
+ * says that one of them grew.
138
+ */
139
+ private countIn;
140
+ private countGroup;
141
+ }
142
+ /**
143
+ * Run `work` and note what the join and the database's readers look up
144
+ * in every relation outside `skipped`. A caller skips the
145
+ * relations it empties after every question, so that the work derives
146
+ * their facts again and the lookups behind them get noted. The reads
147
+ * are null when a skipped relation was not empty to begin with, since
148
+ * the work could then build on facts nothing here looked up.
149
+ */
150
+ declare function noteReads<T>(db: Database, skipped: ReadonlySet<string>, work: () => T): {
151
+ value: T;
152
+ reads: ReadsNoted | null;
153
+ };
154
+
53
155
  /**
54
156
  * One relation's facts: which tuples are there, and an index per column
55
157
  * so a join that has a value for a column does not scan them all.
@@ -78,6 +180,8 @@ interface Relation {
78
180
  * kept up to date the same way as `columns`.
79
181
  */
80
182
  combined: Map<number, CombinedIndex>;
183
+ /** How many facts have ever been retracted, which `ReadsNoted` checks before trusting a count. */
184
+ removed: number;
81
185
  }
82
186
  /**
83
187
  * A tree of maps with one level per column in `columns`, lowest position
@@ -490,6 +594,8 @@ type AddOutcome = "added" | "improved" | "unchanged";
490
594
  /** A set of facts per relation, with O(1) membership. */
491
595
  declare class Database {
492
596
  private readonly store;
597
+ /** @internal Where reads are noted while `noteReads` runs. */
598
+ readLog: ReadLog | null;
493
599
  private relation;
494
600
  /**
495
601
  * The key this relation gives a tuple, whether or not the fact is
@@ -508,6 +614,8 @@ declare class Database {
508
614
  /** The tag stored for this fact, or undefined when there is none. */
509
615
  tagOf(relationName: string, tuple: Tuple): unknown;
510
616
  has(relationName: string, tuple: Tuple): boolean;
617
+ /** A fact matching the whole tuple agrees on its first column, so that lookup is enough to note. */
618
+ private noteTupleRead;
511
619
  facts(relationName: string): readonly Tuple[];
512
620
  /**
513
621
  * The facts with `value` at `column`. Once one of a literal's terms
@@ -671,4 +779,4 @@ declare function evaluate<Tag = never>(db: Database, rules: Rule[], algebra?: Ta
671
779
  */
672
780
  declare function clearRelations(db: Database, rules: Rule[], relations: readonly string[]): void;
673
781
 
674
- export { type AbandonedQuestion, type AddOutcome, type Atom, type BodyMatch, BudgetExhausted, type ConfidenceLevel, Database, type Demand, type Derivation, type EvaluationProfile, type Literal, type OnDemandRules, type Proof, type ProofDerived, type ProofOptions, type QuestionOutcome, type QuestionTally, type RowBudget, type Rule, type RuleCost, type TagAlgebra, type Term, type Tuple, Witness, type WitnessTag, chargeAbandoned, chargeQuestion, clearRelations, confidence, confidenceWith, constant, deriveOnDemand, evaluate, formatProfile, lit, notLit, profileEvaluation, profileEvaluationAsync, proofOf, rowBudget, rule, ruleLabel, rulesDeriving, stratify, tupleKey, tupleKeyParts, variable, witnesses };
782
+ export { type AbandonedQuestion, type AddOutcome, type Atom, type BodyMatch, BudgetExhausted, type ConfidenceLevel, Database, type Demand, type Derivation, type EvaluationProfile, type Literal, type OnDemandRules, type Proof, type ProofDerived, type ProofOptions, type QuestionOutcome, type QuestionTally, ReadsNoted, type RowBudget, type Rule, type RuleCost, type TagAlgebra, type Term, type Tuple, Witness, type WitnessTag, chargeAbandoned, chargeQuestion, clearRelations, confidence, confidenceWith, constant, deriveOnDemand, evaluate, formatProfile, lit, notLit, noteReads, profileEvaluation, profileEvaluationAsync, proofOf, rowBudget, rule, ruleLabel, rulesDeriving, stratify, tupleKey, tupleKeyParts, variable, witnesses };
package/dist/index.d.ts CHANGED
@@ -50,6 +50,108 @@ declare class FactIndex {
50
50
  clear(): void;
51
51
  }
52
52
 
53
+ /**
54
+ * What an evaluation looked up, so a caller that cached its answer can
55
+ * tell later whether a fact that arrived since could change it.
56
+ *
57
+ * While `noteReads` runs, every lookup the join makes is noted as the
58
+ * relation, the column and the value, and a relation read whole is
59
+ * noted whole. At the end each lookup's facts are counted. The cached
60
+ * answer is still right when every count is the same and nothing was
61
+ * retracted from those relations. Without a retraction counts only
62
+ * grow, so the sum of the counts changes exactly when one of them does.
63
+ * DESIGN.md explains why a fact no lookup would find cannot change the
64
+ * answer, and what that asks of the rules.
65
+ */
66
+
67
+ /** @internal One relation's reads: the values looked up per column, unless something read it whole. */
68
+ interface RelationReads {
69
+ whole: boolean;
70
+ columns: (Set<Atom> | undefined)[];
71
+ }
72
+ /** @internal The join notes its reads here while `noteReads` runs. */
73
+ declare class ReadLog {
74
+ private readonly skipped;
75
+ /** Null for a relation the caller said to leave out. */
76
+ private readonly byRelation;
77
+ constructor(skipped: ReadonlySet<string>);
78
+ lookedUp(relation: string, column: number, value: Atom): void;
79
+ readWhole(relation: string): void;
80
+ /** Note everything an inner log noted, for an answer cached inside another. */
81
+ absorb(inner: ReadLog): void;
82
+ /**
83
+ * The reads with what each one finds in `db` now. One question looks
84
+ * up the same values in many columns, so each value is kept once, and
85
+ * each column keeps a bit per value saying whether it was looked up
86
+ * there.
87
+ */
88
+ counted(db: Database): ReadsNoted;
89
+ /** @internal Null for a relation the caller said to leave out. */
90
+ readsOf(relation: string): RelationReads | null;
91
+ }
92
+ /**
93
+ * Group `g` is the column `columns[g]` of the relation at
94
+ * `names[groups[g]]`. Its bits start at word `g` times the words one
95
+ * group takes, and bit `i` says it looked up `values[i]`.
96
+ */
97
+ interface Lookups {
98
+ names: readonly string[];
99
+ groups: readonly number[];
100
+ columns: readonly number[];
101
+ values: readonly Atom[];
102
+ bits: Uint32Array;
103
+ }
104
+ /**
105
+ * The lookups one evaluation made, and how many facts they found when
106
+ * it finished. The values are the atoms the facts already contain, so
107
+ * a value costs a reference, and each column it was looked up in costs
108
+ * a bit.
109
+ */
110
+ declare class ReadsNoted {
111
+ private readonly skipped;
112
+ private readonly found;
113
+ private readonly names;
114
+ private readonly groups;
115
+ private readonly columns;
116
+ private readonly values;
117
+ private readonly bits;
118
+ private readonly words;
119
+ private readonly removed;
120
+ /** The facts in every relation read, when the counts last matched. */
121
+ private facts;
122
+ /** Each relation read, found by name the first time a check needs it. */
123
+ private readonly relations;
124
+ constructor(db: Database, skipped: ReadonlySet<string>, lookups: Lookups, found: number);
125
+ /**
126
+ * Whether every read would find what it found then. A fact left in a
127
+ * skipped relation would join a question asked now without anything
128
+ * here having looked it up, so one there makes the answer no. The
129
+ * counts are taken again only when a relation read has grown.
130
+ */
131
+ unchangedIn(db: Database): boolean;
132
+ private relationAt;
133
+ private sumOver;
134
+ /**
135
+ * The facts the reads find, stopping once past `limit`. With nothing
136
+ * retracted every count only grows, so passing the old total already
137
+ * says that one of them grew.
138
+ */
139
+ private countIn;
140
+ private countGroup;
141
+ }
142
+ /**
143
+ * Run `work` and note what the join and the database's readers look up
144
+ * in every relation outside `skipped`. A caller skips the
145
+ * relations it empties after every question, so that the work derives
146
+ * their facts again and the lookups behind them get noted. The reads
147
+ * are null when a skipped relation was not empty to begin with, since
148
+ * the work could then build on facts nothing here looked up.
149
+ */
150
+ declare function noteReads<T>(db: Database, skipped: ReadonlySet<string>, work: () => T): {
151
+ value: T;
152
+ reads: ReadsNoted | null;
153
+ };
154
+
53
155
  /**
54
156
  * One relation's facts: which tuples are there, and an index per column
55
157
  * so a join that has a value for a column does not scan them all.
@@ -78,6 +180,8 @@ interface Relation {
78
180
  * kept up to date the same way as `columns`.
79
181
  */
80
182
  combined: Map<number, CombinedIndex>;
183
+ /** How many facts have ever been retracted, which `ReadsNoted` checks before trusting a count. */
184
+ removed: number;
81
185
  }
82
186
  /**
83
187
  * A tree of maps with one level per column in `columns`, lowest position
@@ -490,6 +594,8 @@ type AddOutcome = "added" | "improved" | "unchanged";
490
594
  /** A set of facts per relation, with O(1) membership. */
491
595
  declare class Database {
492
596
  private readonly store;
597
+ /** @internal Where reads are noted while `noteReads` runs. */
598
+ readLog: ReadLog | null;
493
599
  private relation;
494
600
  /**
495
601
  * The key this relation gives a tuple, whether or not the fact is
@@ -508,6 +614,8 @@ declare class Database {
508
614
  /** The tag stored for this fact, or undefined when there is none. */
509
615
  tagOf(relationName: string, tuple: Tuple): unknown;
510
616
  has(relationName: string, tuple: Tuple): boolean;
617
+ /** A fact matching the whole tuple agrees on its first column, so that lookup is enough to note. */
618
+ private noteTupleRead;
511
619
  facts(relationName: string): readonly Tuple[];
512
620
  /**
513
621
  * The facts with `value` at `column`. Once one of a literal's terms
@@ -671,4 +779,4 @@ declare function evaluate<Tag = never>(db: Database, rules: Rule[], algebra?: Ta
671
779
  */
672
780
  declare function clearRelations(db: Database, rules: Rule[], relations: readonly string[]): void;
673
781
 
674
- export { type AbandonedQuestion, type AddOutcome, type Atom, type BodyMatch, BudgetExhausted, type ConfidenceLevel, Database, type Demand, type Derivation, type EvaluationProfile, type Literal, type OnDemandRules, type Proof, type ProofDerived, type ProofOptions, type QuestionOutcome, type QuestionTally, type RowBudget, type Rule, type RuleCost, type TagAlgebra, type Term, type Tuple, Witness, type WitnessTag, chargeAbandoned, chargeQuestion, clearRelations, confidence, confidenceWith, constant, deriveOnDemand, evaluate, formatProfile, lit, notLit, profileEvaluation, profileEvaluationAsync, proofOf, rowBudget, rule, ruleLabel, rulesDeriving, stratify, tupleKey, tupleKeyParts, variable, witnesses };
782
+ export { type AbandonedQuestion, type AddOutcome, type Atom, type BodyMatch, BudgetExhausted, type ConfidenceLevel, Database, type Demand, type Derivation, type EvaluationProfile, type Literal, type OnDemandRules, type Proof, type ProofDerived, type ProofOptions, type QuestionOutcome, type QuestionTally, ReadsNoted, type RowBudget, type Rule, type RuleCost, type TagAlgebra, type Term, type Tuple, Witness, type WitnessTag, chargeAbandoned, chargeQuestion, clearRelations, confidence, confidenceWith, constant, deriveOnDemand, evaluate, formatProfile, lit, notLit, noteReads, profileEvaluation, profileEvaluationAsync, proofOf, rowBudget, rule, ruleLabel, rulesDeriving, stratify, tupleKey, tupleKeyParts, variable, witnesses };