@suss/resolution 0.31.0 → 0.31.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.cjs CHANGED
@@ -34,6 +34,8 @@ __export(index_exports, {
34
34
  RESOLUTION_RULES: () => RESOLUTION_RULES,
35
35
  RESULT_STEP: () => RESULT_STEP,
36
36
  UNDER_ANSWER_RELATIONS: () => UNDER_ANSWER_RELATIONS,
37
+ UNDER_QUESTION_ROW_BUDGET: () => UNDER_QUESTION_ROW_BUDGET,
38
+ UNDER_RUN_ROW_BUDGET: () => UNDER_RUN_ROW_BUDGET,
37
39
  VALUE_STEP: () => VALUE_STEP,
38
40
  addPackWords: () => addPackWords,
39
41
  agreedMountPrefix: () => agreedMountPrefix,
@@ -61,6 +63,7 @@ __export(index_exports, {
61
63
  resolutionUnderProgram: () => resolutionUnderProgram,
62
64
  singleAnswers: () => singleAnswers,
63
65
  startsAtName: () => startsAtName,
66
+ underQuestionSpend: () => underQuestionSpend,
64
67
  valueLeftByWrites: () => valueLeftByWrites,
65
68
  writesRunInOrder: () => writesRunInOrder,
66
69
  writtenSourcesOf: () => writtenSourcesOf,
@@ -147,7 +150,21 @@ function askResolution(db, keys, asking = "wanted", program = resolutionProgram(
147
150
  }
148
151
  }
149
152
  var askedUnderByDb = /* @__PURE__ */ new WeakMap();
150
- function askResolutionUnder(db, pairs, program = resolutionUnderProgram()) {
153
+ var UNDER_QUESTION_ROW_BUDGET = 2e7;
154
+ var UNDER_RUN_ROW_BUDGET = 4e7;
155
+ var spentUnderByDb = /* @__PURE__ */ new WeakMap();
156
+ function spendOf(db) {
157
+ let spend = spentUnderByDb.get(db);
158
+ if (spend === void 0) {
159
+ spend = { rows: 0, asked: 0, abandoned: 0, skipped: 0 };
160
+ spentUnderByDb.set(db, spend);
161
+ }
162
+ return spend;
163
+ }
164
+ function underQuestionSpend(db) {
165
+ return { ...spendOf(db) };
166
+ }
167
+ function askResolutionUnder(db, pairs, program = resolutionUnderProgram(), questionBudget = UNDER_QUESTION_ROW_BUDGET, runBudget = UNDER_RUN_ROW_BUDGET) {
151
168
  let asked = askedUnderByDb.get(db);
152
169
  if (asked === void 0) {
153
170
  asked = /* @__PURE__ */ new Set();
@@ -157,17 +174,49 @@ function askResolutionUnder(db, pairs, program = resolutionUnderProgram()) {
157
174
  ([key, site]) => !asked.has((0, import_datalog.tupleKey)([key, site]))
158
175
  );
159
176
  if (fresh.length === 0) {
160
- return;
177
+ return "answered";
161
178
  }
179
+ const spend = spendOf(db);
162
180
  for (const [key, site] of fresh) {
163
181
  asked.add((0, import_datalog.tupleKey)([key, site]));
182
+ }
183
+ spend.asked += 1;
184
+ if (spend.rows >= runBudget) {
185
+ spend.skipped += 1;
186
+ (0, import_datalog.chargeQuestion)("skipped");
187
+ return "abandoned";
188
+ }
189
+ for (const [key, site] of fresh) {
164
190
  db.add("wantedUnder", [key, site]);
165
191
  }
166
- (0, import_datalog.evaluate)(db, program.rules);
167
192
  const forget = queryFacts(program);
168
- if (forget.length > 0) {
169
- (0, import_datalog.clearRelations)(db, program.rules, forget);
193
+ const clearDerived = () => {
194
+ if (forget.length > 0) {
195
+ (0, import_datalog.clearRelations)(db, program.rules, forget);
196
+ }
197
+ };
198
+ const budget = (0, import_datalog.rowBudget)(questionBudget);
199
+ try {
200
+ (0, import_datalog.evaluate)(db, program.rules, void 0, budget);
201
+ } catch (error) {
202
+ if (!(error instanceof import_datalog.BudgetExhausted)) {
203
+ throw error;
204
+ }
205
+ spend.rows += error.examined;
206
+ spend.abandoned += 1;
207
+ (0, import_datalog.chargeQuestion)("abandoned");
208
+ db.retract("wantedUnder", fresh);
209
+ clearDerived();
210
+ (0, import_datalog.chargeAbandoned)(
211
+ fresh.map(([key, site]) => `${key} under ${site}`).join(", "),
212
+ error.examined
213
+ );
214
+ return "abandoned";
170
215
  }
216
+ spend.rows += budget.examined;
217
+ (0, import_datalog.chargeQuestion)();
218
+ clearDerived();
219
+ return "answered";
171
220
  }
172
221
 
173
222
  // src/callee.ts
@@ -2076,18 +2125,44 @@ var STATED_RULES = [
2076
2125
  [(0, import_datalog4.lit)("binds", (0, import_datalog4.variable)("g"), (0, import_datalog4.variable)("f")), (0, import_datalog4.lit)("callsFunction", (0, import_datalog4.variable)("r"), (0, import_datalog4.variable)("g"))]
2077
2126
  ),
2078
2127
  // A function written on an object, called off it: `client.get(o)`.
2079
- // Every rule above joins on a name the callee binds to, and a read of
2080
- // a property binds to nothing.
2128
+ // The receiver comes from the object through `refersToObject`, not from
2129
+ // `objectOf`: a walk from every reader of the name was most of a run.
2081
2130
  (0, import_datalog4.rule)(
2082
2131
  "callsFunction",
2083
2132
  [(0, import_datalog4.variable)("r"), (0, import_datalog4.variable)("f")],
2084
2133
  [
2085
2134
  (0, import_datalog4.lit)("holdsProperty", (0, import_datalog4.variable)("obj"), (0, import_datalog4.variable)("n"), (0, import_datalog4.variable)("f")),
2135
+ (0, import_datalog4.lit)("refersToObject", (0, import_datalog4.variable)("o"), (0, import_datalog4.variable)("obj")),
2086
2136
  (0, import_datalog4.lit)("readsProperty", (0, import_datalog4.variable)("c"), (0, import_datalog4.variable)("o"), (0, import_datalog4.variable)("n")),
2087
- (0, import_datalog4.lit)("objectOf", (0, import_datalog4.variable)("o"), (0, import_datalog4.variable)("obj")),
2088
2137
  (0, import_datalog4.lit)("call", (0, import_datalog4.variable)("r"), (0, import_datalog4.variable)("c"))
2089
2138
  ]
2090
2139
  ),
2140
+ // The expressions that refer to an object literal by binding alone: the
2141
+ // declaration written as it, a reference or import of that, and a
2142
+ // fallback over any of those. Asked from the object, so it visits only them.
2143
+ (0, import_datalog4.rule)("refersToObject", [(0, import_datalog4.variable)("obj"), (0, import_datalog4.variable)("obj")], [(0, import_datalog4.lit)("objectValue", (0, import_datalog4.variable)("obj"))]),
2144
+ (0, import_datalog4.rule)(
2145
+ "refersToObject",
2146
+ [(0, import_datalog4.variable)("x"), (0, import_datalog4.variable)("obj")],
2147
+ [(0, import_datalog4.lit)("refersToObject", (0, import_datalog4.variable)("y"), (0, import_datalog4.variable)("obj")), (0, import_datalog4.lit)("binds", (0, import_datalog4.variable)("x"), (0, import_datalog4.variable)("y"))]
2148
+ ),
2149
+ (0, import_datalog4.rule)(
2150
+ "refersToObject",
2151
+ [(0, import_datalog4.variable)("x"), (0, import_datalog4.variable)("obj")],
2152
+ [
2153
+ (0, import_datalog4.lit)("refersToObject", (0, import_datalog4.variable)("y"), (0, import_datalog4.variable)("obj")),
2154
+ (0, import_datalog4.lit)("moduleExport", (0, import_datalog4.variable)("m"), (0, import_datalog4.variable)("n"), (0, import_datalog4.variable)("y")),
2155
+ (0, import_datalog4.lit)("imports", (0, import_datalog4.variable)("x"), (0, import_datalog4.variable)("m"), (0, import_datalog4.variable)("n"))
2156
+ ]
2157
+ ),
2158
+ (0, import_datalog4.rule)(
2159
+ "refersToObject",
2160
+ [(0, import_datalog4.variable)("x"), (0, import_datalog4.variable)("obj")],
2161
+ [
2162
+ (0, import_datalog4.lit)("refersToObject", (0, import_datalog4.variable)("b"), (0, import_datalog4.variable)("obj")),
2163
+ (0, import_datalog4.lit)("fallbackBranch", (0, import_datalog4.variable)("x"), (0, import_datalog4.variable)("b"))
2164
+ ]
2165
+ ),
2091
2166
  // What an object contains, its base class included, so a method the base
2092
2167
  // declares is found on a subclass that never overrode it. A method both
2093
2168
  // declare gives two, and the caller decides. This is its own relation
@@ -2739,6 +2814,8 @@ var UNDER_ANSWER_RELATIONS = [
2739
2814
  RESOLUTION_RULES,
2740
2815
  RESULT_STEP,
2741
2816
  UNDER_ANSWER_RELATIONS,
2817
+ UNDER_QUESTION_ROW_BUDGET,
2818
+ UNDER_RUN_ROW_BUDGET,
2742
2819
  VALUE_STEP,
2743
2820
  addPackWords,
2744
2821
  agreedMountPrefix,
@@ -2766,6 +2843,7 @@ var UNDER_ANSWER_RELATIONS = [
2766
2843
  resolutionUnderProgram,
2767
2844
  singleAnswers,
2768
2845
  startsAtName,
2846
+ underQuestionSpend,
2769
2847
  valueLeftByWrites,
2770
2848
  writesRunInOrder,
2771
2849
  writtenSourcesOf,
package/dist/index.d.cts CHANGED
@@ -426,12 +426,59 @@ declare function queryFacts(program: OnDemandRules): readonly string[];
426
426
  * question, which is how each adapter reads a project.
427
427
  */
428
428
  declare function askResolution(db: Database, keys: Iterable<string>, asking?: string, program?: OnDemandRules): void;
429
+ /**
430
+ * How many rows the joins may read for one question under a site.
431
+ *
432
+ * The biggest question a project could answer read 200k rows on the
433
+ * largest public corpus target and 600k on the regression fixtures. A
434
+ * question that cannot be answered walks every node under every context
435
+ * and reads that much per rule, round after round, without settling.
436
+ * The gap is wide enough for thirty times the biggest healthy question,
437
+ * which is a few seconds before the walk is given up on.
438
+ */
439
+ declare const UNDER_QUESTION_ROW_BUDGET = 20000000;
440
+ /**
441
+ * How many rows the joins may read for every under-question a run asks,
442
+ * together.
443
+ *
444
+ * The per-question budget stops one question walking forever. It says
445
+ * nothing about how many questions a project asks, and a value read
446
+ * under every site of a class built in seventy places is seventy
447
+ * questions, each a fixpoint of its own. Twenty of those at the
448
+ * per-question limit is seven minutes, which is a hang however well
449
+ * each behaved on its own.
450
+ *
451
+ * This is two runaway questions' worth, or sixty-six of the biggest
452
+ * one measured. Past it a run keeps the context-free answer.
453
+ */
454
+ declare const UNDER_RUN_ROW_BUDGET = 40000000;
455
+ /** Whether a question was worked through, or given up on a budget. */
456
+ type UnderOutcome = "answered" | "abandoned";
457
+ /** What the under-questions of one run have cost it so far. */
458
+ interface UnderSpend {
459
+ rows: number;
460
+ asked: number;
461
+ abandoned: number;
462
+ skipped: number;
463
+ }
464
+ /**
465
+ * What the under-questions of this database have cost, for a caller
466
+ * reporting on a run.
467
+ */
468
+ declare function underQuestionSpend(db: Database): Readonly<UnderSpend>;
429
469
  /**
430
470
  * `askResolution` for a value read under one allocation site. Each pair
431
471
  * is a question of its own, so asking about a value under two sites is
432
472
  * two rows, and asking the same pair twice costs nothing.
433
- */
434
- declare function askResolutionUnder(db: Database, pairs: Iterable<readonly [string, string]>, program?: OnDemandRules): void;
473
+ *
474
+ * A question that runs past `questionBudget`, or one asked after the
475
+ * run has spent `runBudget` on questions before it, is abandoned: the
476
+ * rows it added come back out, the answer relations stay as empty as
477
+ * they were, and the caller is told so it can keep the context-free
478
+ * answer instead. The pair stays marked as asked either way, so nobody
479
+ * pays for it twice.
480
+ */
481
+ declare function askResolutionUnder(db: Database, pairs: Iterable<readonly [string, string]>, program?: OnDemandRules, questionBudget?: number, runBudget?: number): UnderOutcome;
435
482
 
436
483
  /**
437
484
  * The result shape a why session hands back, shared by every language
@@ -687,4 +734,4 @@ declare const ANSWER_RELATIONS: string[];
687
734
  */
688
735
  declare const UNDER_ANSWER_RELATIONS: string[];
689
736
 
690
- export { ANSWER_RELATIONS, ASKING_RELATIONS, type AssociationConstructor, BASE_CLASS_RULE, type CalleeOutcome, type CaseFiles, type ChainReads, type ContractCase, type ContractOptions, type DescribeAtom, type EntersAsSelf, type ExplainOptions, type ExplainResolvedKeyOptions, type ExplainStats, FACT_CONTRACT_CASES, type FactsOf, type GivesBackOne, type GivesBackOneOfArgument, type GivesBackOneOfImport, INSTANCE_STEP, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, NAMESPACE_MEMBER_RULE, NO_CONTEXT, NO_CONTEXT_NAME, type NameReads, type NameWrite, type OrderedWrite, type PackWords, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ReadNode, type ResolutionExplanation, type ResolutionStep, type SpannedNode, type StepContext, type StepPhrase, UNDER_ANSWER_RELATIONS, type UnwrapsByName, VALUE_STEP, type ValueLocation, type WhyExplained, addPackWords, agreedMountPrefix, allocationSitesOf, alsoSteps, answersByKey, askResolution, askResolutionUnder, calleeOutcomeOf, calleeOutcomes, checkFactContract, comesToUnder, couldBeSettled, explainResolutionProof, explainResolvedKey, isWrittenAsUnder, joinMountedPath, mountPathsOf, nodeOfKey, objectOfUnder, placeholderValues, queryFacts, renderExplanation, resolutionProgram, resolutionUnderProgram, singleAnswers, startsAtName, valueLeftByWrites, writesRunInOrder, writtenSourcesOf, writtenValueOf, writtenValueUnder, writtenValuesByKey, writtenValuesOf };
737
+ export { ANSWER_RELATIONS, ASKING_RELATIONS, type AssociationConstructor, BASE_CLASS_RULE, type CalleeOutcome, type CaseFiles, type ChainReads, type ContractCase, type ContractOptions, type DescribeAtom, type EntersAsSelf, type ExplainOptions, type ExplainResolvedKeyOptions, type ExplainStats, FACT_CONTRACT_CASES, type FactsOf, type GivesBackOne, type GivesBackOneOfArgument, type GivesBackOneOfImport, INSTANCE_STEP, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, NAMESPACE_MEMBER_RULE, NO_CONTEXT, NO_CONTEXT_NAME, type NameReads, type NameWrite, type OrderedWrite, type PackWords, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ReadNode, type ResolutionExplanation, type ResolutionStep, type SpannedNode, type StepContext, type StepPhrase, UNDER_ANSWER_RELATIONS, UNDER_QUESTION_ROW_BUDGET, UNDER_RUN_ROW_BUDGET, type UnderOutcome, type UnwrapsByName, VALUE_STEP, type ValueLocation, type WhyExplained, addPackWords, agreedMountPrefix, allocationSitesOf, alsoSteps, answersByKey, askResolution, askResolutionUnder, calleeOutcomeOf, calleeOutcomes, checkFactContract, comesToUnder, couldBeSettled, explainResolutionProof, explainResolvedKey, isWrittenAsUnder, joinMountedPath, mountPathsOf, nodeOfKey, objectOfUnder, placeholderValues, queryFacts, renderExplanation, resolutionProgram, resolutionUnderProgram, singleAnswers, startsAtName, underQuestionSpend, valueLeftByWrites, writesRunInOrder, writtenSourcesOf, writtenValueOf, writtenValueUnder, writtenValuesByKey, writtenValuesOf };
package/dist/index.d.ts CHANGED
@@ -426,12 +426,59 @@ declare function queryFacts(program: OnDemandRules): readonly string[];
426
426
  * question, which is how each adapter reads a project.
427
427
  */
428
428
  declare function askResolution(db: Database, keys: Iterable<string>, asking?: string, program?: OnDemandRules): void;
429
+ /**
430
+ * How many rows the joins may read for one question under a site.
431
+ *
432
+ * The biggest question a project could answer read 200k rows on the
433
+ * largest public corpus target and 600k on the regression fixtures. A
434
+ * question that cannot be answered walks every node under every context
435
+ * and reads that much per rule, round after round, without settling.
436
+ * The gap is wide enough for thirty times the biggest healthy question,
437
+ * which is a few seconds before the walk is given up on.
438
+ */
439
+ declare const UNDER_QUESTION_ROW_BUDGET = 20000000;
440
+ /**
441
+ * How many rows the joins may read for every under-question a run asks,
442
+ * together.
443
+ *
444
+ * The per-question budget stops one question walking forever. It says
445
+ * nothing about how many questions a project asks, and a value read
446
+ * under every site of a class built in seventy places is seventy
447
+ * questions, each a fixpoint of its own. Twenty of those at the
448
+ * per-question limit is seven minutes, which is a hang however well
449
+ * each behaved on its own.
450
+ *
451
+ * This is two runaway questions' worth, or sixty-six of the biggest
452
+ * one measured. Past it a run keeps the context-free answer.
453
+ */
454
+ declare const UNDER_RUN_ROW_BUDGET = 40000000;
455
+ /** Whether a question was worked through, or given up on a budget. */
456
+ type UnderOutcome = "answered" | "abandoned";
457
+ /** What the under-questions of one run have cost it so far. */
458
+ interface UnderSpend {
459
+ rows: number;
460
+ asked: number;
461
+ abandoned: number;
462
+ skipped: number;
463
+ }
464
+ /**
465
+ * What the under-questions of this database have cost, for a caller
466
+ * reporting on a run.
467
+ */
468
+ declare function underQuestionSpend(db: Database): Readonly<UnderSpend>;
429
469
  /**
430
470
  * `askResolution` for a value read under one allocation site. Each pair
431
471
  * is a question of its own, so asking about a value under two sites is
432
472
  * two rows, and asking the same pair twice costs nothing.
433
- */
434
- declare function askResolutionUnder(db: Database, pairs: Iterable<readonly [string, string]>, program?: OnDemandRules): void;
473
+ *
474
+ * A question that runs past `questionBudget`, or one asked after the
475
+ * run has spent `runBudget` on questions before it, is abandoned: the
476
+ * rows it added come back out, the answer relations stay as empty as
477
+ * they were, and the caller is told so it can keep the context-free
478
+ * answer instead. The pair stays marked as asked either way, so nobody
479
+ * pays for it twice.
480
+ */
481
+ declare function askResolutionUnder(db: Database, pairs: Iterable<readonly [string, string]>, program?: OnDemandRules, questionBudget?: number, runBudget?: number): UnderOutcome;
435
482
 
436
483
  /**
437
484
  * The result shape a why session hands back, shared by every language
@@ -687,4 +734,4 @@ declare const ANSWER_RELATIONS: string[];
687
734
  */
688
735
  declare const UNDER_ANSWER_RELATIONS: string[];
689
736
 
690
- export { ANSWER_RELATIONS, ASKING_RELATIONS, type AssociationConstructor, BASE_CLASS_RULE, type CalleeOutcome, type CaseFiles, type ChainReads, type ContractCase, type ContractOptions, type DescribeAtom, type EntersAsSelf, type ExplainOptions, type ExplainResolvedKeyOptions, type ExplainStats, FACT_CONTRACT_CASES, type FactsOf, type GivesBackOne, type GivesBackOneOfArgument, type GivesBackOneOfImport, INSTANCE_STEP, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, NAMESPACE_MEMBER_RULE, NO_CONTEXT, NO_CONTEXT_NAME, type NameReads, type NameWrite, type OrderedWrite, type PackWords, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ReadNode, type ResolutionExplanation, type ResolutionStep, type SpannedNode, type StepContext, type StepPhrase, UNDER_ANSWER_RELATIONS, type UnwrapsByName, VALUE_STEP, type ValueLocation, type WhyExplained, addPackWords, agreedMountPrefix, allocationSitesOf, alsoSteps, answersByKey, askResolution, askResolutionUnder, calleeOutcomeOf, calleeOutcomes, checkFactContract, comesToUnder, couldBeSettled, explainResolutionProof, explainResolvedKey, isWrittenAsUnder, joinMountedPath, mountPathsOf, nodeOfKey, objectOfUnder, placeholderValues, queryFacts, renderExplanation, resolutionProgram, resolutionUnderProgram, singleAnswers, startsAtName, valueLeftByWrites, writesRunInOrder, writtenSourcesOf, writtenValueOf, writtenValueUnder, writtenValuesByKey, writtenValuesOf };
737
+ export { ANSWER_RELATIONS, ASKING_RELATIONS, type AssociationConstructor, BASE_CLASS_RULE, type CalleeOutcome, type CaseFiles, type ChainReads, type ContractCase, type ContractOptions, type DescribeAtom, type EntersAsSelf, type ExplainOptions, type ExplainResolvedKeyOptions, type ExplainStats, FACT_CONTRACT_CASES, type FactsOf, type GivesBackOne, type GivesBackOneOfArgument, type GivesBackOneOfImport, INSTANCE_STEP, type MountEdge, type MountEdges, NAMESPACE_IMPORT, NAMESPACE_IMPORT_NAME, NAMESPACE_MEMBER_RULE, NO_CONTEXT, NO_CONTEXT_NAME, type NameReads, type NameWrite, type OrderedWrite, type PackWords, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ReadNode, type ResolutionExplanation, type ResolutionStep, type SpannedNode, type StepContext, type StepPhrase, UNDER_ANSWER_RELATIONS, UNDER_QUESTION_ROW_BUDGET, UNDER_RUN_ROW_BUDGET, type UnderOutcome, type UnwrapsByName, VALUE_STEP, type ValueLocation, type WhyExplained, addPackWords, agreedMountPrefix, allocationSitesOf, alsoSteps, answersByKey, askResolution, askResolutionUnder, calleeOutcomeOf, calleeOutcomes, checkFactContract, comesToUnder, couldBeSettled, explainResolutionProof, explainResolvedKey, isWrittenAsUnder, joinMountedPath, mountPathsOf, nodeOfKey, objectOfUnder, placeholderValues, queryFacts, renderExplanation, resolutionProgram, resolutionUnderProgram, singleAnswers, startsAtName, underQuestionSpend, valueLeftByWrites, writesRunInOrder, writtenSourcesOf, writtenValueOf, writtenValueUnder, writtenValuesByKey, writtenValuesOf };
package/dist/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  // src/program.ts
2
2
  import {
3
+ BudgetExhausted,
4
+ chargeAbandoned,
5
+ chargeQuestion,
3
6
  clearRelations,
4
7
  deriveOnDemand,
5
8
  evaluate,
9
+ rowBudget,
6
10
  tupleKey
7
11
  } from "@suss/datalog";
8
12
  var ASKING_RELATIONS = [
@@ -79,7 +83,21 @@ function askResolution(db, keys, asking = "wanted", program = resolutionProgram(
79
83
  }
80
84
  }
81
85
  var askedUnderByDb = /* @__PURE__ */ new WeakMap();
82
- function askResolutionUnder(db, pairs, program = resolutionUnderProgram()) {
86
+ var UNDER_QUESTION_ROW_BUDGET = 2e7;
87
+ var UNDER_RUN_ROW_BUDGET = 4e7;
88
+ var spentUnderByDb = /* @__PURE__ */ new WeakMap();
89
+ function spendOf(db) {
90
+ let spend = spentUnderByDb.get(db);
91
+ if (spend === void 0) {
92
+ spend = { rows: 0, asked: 0, abandoned: 0, skipped: 0 };
93
+ spentUnderByDb.set(db, spend);
94
+ }
95
+ return spend;
96
+ }
97
+ function underQuestionSpend(db) {
98
+ return { ...spendOf(db) };
99
+ }
100
+ function askResolutionUnder(db, pairs, program = resolutionUnderProgram(), questionBudget = UNDER_QUESTION_ROW_BUDGET, runBudget = UNDER_RUN_ROW_BUDGET) {
83
101
  let asked = askedUnderByDb.get(db);
84
102
  if (asked === void 0) {
85
103
  asked = /* @__PURE__ */ new Set();
@@ -89,17 +107,49 @@ function askResolutionUnder(db, pairs, program = resolutionUnderProgram()) {
89
107
  ([key, site]) => !asked.has(tupleKey([key, site]))
90
108
  );
91
109
  if (fresh.length === 0) {
92
- return;
110
+ return "answered";
93
111
  }
112
+ const spend = spendOf(db);
94
113
  for (const [key, site] of fresh) {
95
114
  asked.add(tupleKey([key, site]));
115
+ }
116
+ spend.asked += 1;
117
+ if (spend.rows >= runBudget) {
118
+ spend.skipped += 1;
119
+ chargeQuestion("skipped");
120
+ return "abandoned";
121
+ }
122
+ for (const [key, site] of fresh) {
96
123
  db.add("wantedUnder", [key, site]);
97
124
  }
98
- evaluate(db, program.rules);
99
125
  const forget = queryFacts(program);
100
- if (forget.length > 0) {
101
- clearRelations(db, program.rules, forget);
126
+ const clearDerived = () => {
127
+ if (forget.length > 0) {
128
+ clearRelations(db, program.rules, forget);
129
+ }
130
+ };
131
+ const budget = rowBudget(questionBudget);
132
+ try {
133
+ evaluate(db, program.rules, void 0, budget);
134
+ } catch (error) {
135
+ if (!(error instanceof BudgetExhausted)) {
136
+ throw error;
137
+ }
138
+ spend.rows += error.examined;
139
+ spend.abandoned += 1;
140
+ chargeQuestion("abandoned");
141
+ db.retract("wantedUnder", fresh);
142
+ clearDerived();
143
+ chargeAbandoned(
144
+ fresh.map(([key, site]) => `${key} under ${site}`).join(", "),
145
+ error.examined
146
+ );
147
+ return "abandoned";
102
148
  }
149
+ spend.rows += budget.examined;
150
+ chargeQuestion();
151
+ clearDerived();
152
+ return "answered";
103
153
  }
104
154
 
105
155
  // src/callee.ts
@@ -2008,18 +2058,44 @@ var STATED_RULES = [
2008
2058
  [lit("binds", v("g"), v("f")), lit("callsFunction", v("r"), v("g"))]
2009
2059
  ),
2010
2060
  // A function written on an object, called off it: `client.get(o)`.
2011
- // Every rule above joins on a name the callee binds to, and a read of
2012
- // a property binds to nothing.
2061
+ // The receiver comes from the object through `refersToObject`, not from
2062
+ // `objectOf`: a walk from every reader of the name was most of a run.
2013
2063
  rule(
2014
2064
  "callsFunction",
2015
2065
  [v("r"), v("f")],
2016
2066
  [
2017
2067
  lit("holdsProperty", v("obj"), v("n"), v("f")),
2068
+ lit("refersToObject", v("o"), v("obj")),
2018
2069
  lit("readsProperty", v("c"), v("o"), v("n")),
2019
- lit("objectOf", v("o"), v("obj")),
2020
2070
  lit("call", v("r"), v("c"))
2021
2071
  ]
2022
2072
  ),
2073
+ // The expressions that refer to an object literal by binding alone: the
2074
+ // declaration written as it, a reference or import of that, and a
2075
+ // fallback over any of those. Asked from the object, so it visits only them.
2076
+ rule("refersToObject", [v("obj"), v("obj")], [lit("objectValue", v("obj"))]),
2077
+ rule(
2078
+ "refersToObject",
2079
+ [v("x"), v("obj")],
2080
+ [lit("refersToObject", v("y"), v("obj")), lit("binds", v("x"), v("y"))]
2081
+ ),
2082
+ rule(
2083
+ "refersToObject",
2084
+ [v("x"), v("obj")],
2085
+ [
2086
+ lit("refersToObject", v("y"), v("obj")),
2087
+ lit("moduleExport", v("m"), v("n"), v("y")),
2088
+ lit("imports", v("x"), v("m"), v("n"))
2089
+ ]
2090
+ ),
2091
+ rule(
2092
+ "refersToObject",
2093
+ [v("x"), v("obj")],
2094
+ [
2095
+ lit("refersToObject", v("b"), v("obj")),
2096
+ lit("fallbackBranch", v("x"), v("b"))
2097
+ ]
2098
+ ),
2023
2099
  // What an object contains, its base class included, so a method the base
2024
2100
  // declares is found on a subclass that never overrode it. A method both
2025
2101
  // declare gives two, and the caller decides. This is its own relation
@@ -2670,6 +2746,8 @@ export {
2670
2746
  RESOLUTION_RULES,
2671
2747
  RESULT_STEP,
2672
2748
  UNDER_ANSWER_RELATIONS,
2749
+ UNDER_QUESTION_ROW_BUDGET,
2750
+ UNDER_RUN_ROW_BUDGET,
2673
2751
  VALUE_STEP,
2674
2752
  addPackWords,
2675
2753
  agreedMountPrefix,
@@ -2697,6 +2775,7 @@ export {
2697
2775
  resolutionUnderProgram,
2698
2776
  singleAnswers,
2699
2777
  startsAtName,
2778
+ underQuestionSpend,
2700
2779
  valueLeftByWrites,
2701
2780
  writesRunInOrder,
2702
2781
  writtenSourcesOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/resolution",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "type": "module",
5
5
  "description": "The rules suss uses to follow a name in TypeScript, Python or Ruby to the function it comes down to.",
6
6
  "keywords": [
@@ -56,6 +56,6 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@suss/datalog": "0.31.0"
59
+ "@suss/datalog": "0.31.1"
60
60
  }
61
61
  }