@sheetxl/sdk 0.7.28-beta.3 → 0.8.0-beta.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.
Files changed (66) hide show
  1. package/browser/cjs/{BX6A6xGDOr_td9mw.cjs → BatsV2UUraVDCRXm.cjs} +1 -1
  2. package/browser/cjs/CUz53T65cRGQRgvv.cjs +33 -0
  3. package/browser/cjs/{CWCKcp58KidyndaL.cjs → DU0kYUtdFa3jK-im.cjs} +1 -1
  4. package/browser/cjs/{B4PKKgaDTG9aSO-f.cjs → Dv4yIPLV98M5zLHa.cjs} +1 -1
  5. package/browser/cjs/a0Z-SXs_N71CtqYb.cjs +1 -0
  6. package/browser/cjs/index.cjs +2 -2
  7. package/browser/esm/{Dv8FyXyeygJPXoHo.mjs → 6JKSOvc5czaQB2Bj.mjs} +3 -3
  8. package/browser/esm/{DZfds6s3ayTZrozO.mjs → B0HS42a7lga1k1U_.mjs} +1 -1
  9. package/browser/esm/{C78IaCXqwCcraBPe.mjs → BtuJ9AGzeURtAsGo.mjs} +6946 -5790
  10. package/browser/esm/{CX1NO_a1QPjXDvTU.mjs → DGU6sM25u0EWCKl1.mjs} +3 -3
  11. package/browser/esm/{B1gpnr-7kLU7jTy-.mjs → DlPMeIyxT9tG_WDk.mjs} +2 -2
  12. package/browser/esm/{CPgz9_iM7xyAcytg.mjs → f2G3QRgEbvFJcoa2.mjs} +2 -2
  13. package/browser/esm/index.mjs +17 -14
  14. package/browser/esm/{Rj8e7SashcMlcdZJ.mjs → uyWBMSIxMlt8n5Z3.mjs} +2 -2
  15. package/node/esm/{8VtzCFSmLPKxJjOa.mjs → -pxdn3M3tc6PLHs7.mjs} +20 -20
  16. package/node/esm/{BkY99r-c3BhlT_Kv.mjs → 0n5bslM9jhO1JnI2.mjs} +1 -1
  17. package/node/esm/{DE3eIaqkk5xzmV1c.mjs → Cku7XiUvw1xrRasi.mjs} +1 -1
  18. package/node/esm/index.mjs +9650 -8487
  19. package/package.json +6 -6
  20. package/types/scripting-enums.json +6 -0
  21. package/types/scripting.d.ts +586 -27
  22. package/types/src/anchored/IAnchored.d.ts +27 -0
  23. package/types/src/calculation/Calculation.d.ts +26 -12
  24. package/types/src/calculation/ICalculation.d.ts +178 -0
  25. package/types/src/calculation/executor/CalcExecutor.d.ts +314 -14
  26. package/types/src/calculation/executor/CalcExecutorUtils.d.ts +31 -0
  27. package/types/src/calculation/executor/CircularReference.d.ts +81 -0
  28. package/types/src/calculation/grid/CalcGrid.d.ts +3 -1
  29. package/types/src/calculation/grid/CalcGridUtils.d.ts +3 -2
  30. package/types/src/calculation/grid/ICalcGrid.d.ts +12 -1
  31. package/types/src/calculation/spatial/CircularEntry.d.ts +18 -0
  32. package/types/src/calculation/spatial/index.d.ts +1 -0
  33. package/types/src/calculation/state/CalcNode.d.ts +44 -17
  34. package/types/src/calculation/state/CalcState.d.ts +78 -15
  35. package/types/src/calculation/state/CalculationContext.d.ts +42 -1
  36. package/types/src/calculation/state/IContextContainer.d.ts +31 -0
  37. package/types/src/calculation/state/_ReadObservedGrid.d.ts +90 -0
  38. package/types/src/cell/ICell.d.ts +16 -0
  39. package/types/src/cellRange/ICellRange.d.ts +51 -21
  40. package/types/src/condition/AnchoredConditional.d.ts +26 -6
  41. package/types/src/conditionalFormat/ConditionalFormat.d.ts +8 -1
  42. package/types/src/conditionalFormat/IConditionalFormat.d.ts +4 -1
  43. package/types/src/conditionalFormat/IConditionalFormatCollection.d.ts +6 -2
  44. package/types/src/dataValidation/IDataValidationCollection.d.ts +6 -2
  45. package/types/src/hyperlink/IHyperlink.d.ts +3 -1
  46. package/types/src/hyperlink/_Hyperlink.d.ts +6 -0
  47. package/types/src/lib/utils/_Utils.d.ts +7 -0
  48. package/types/src/lib/utils/utils.browser.d.ts +5 -0
  49. package/types/src/license/LicenseDetails.d.ts +5 -0
  50. package/types/src/primitives/CalcSignal.d.ts +20 -0
  51. package/types/src/primitives/PrimitiveUtils.d.ts +4 -2
  52. package/types/src/primitives/Range.d.ts +3 -1
  53. package/types/src/protectedRange/IProtectedRangeCollection.d.ts +7 -2
  54. package/types/src/scripting/FunctionArguments.d.ts +22 -0
  55. package/types/src/scripting/TypedFunction.d.ts +60 -0
  56. package/types/src/scripting/_ScriptUtils.d.ts +13 -0
  57. package/types/src/sheet/Sheet.d.ts +41 -2
  58. package/types/src/sheet/SheetCell.d.ts +2 -0
  59. package/types/src/sheet/SheetRange.d.ts +3 -0
  60. package/types/src/sheet/_SheetAutoFitUtils.d.ts +28 -0
  61. package/types/src/sheet/_SheetStateTypes.d.ts +14 -1
  62. package/types/src/style/IStyle.d.ts +5 -0
  63. package/types/src/table/AnchoredTable.d.ts +17 -1
  64. package/types/src/text/TextUtils.d.ts +18 -0
  65. package/browser/cjs/1mH2uhx_Dmceu88H.cjs +0 -33
  66. package/browser/cjs/D6cjnwWR_iUSe1f6.cjs +0 -1
@@ -4,6 +4,8 @@ import { ISpatialMap, ISpatialMap3D } from '../../collections';
4
4
  import { FormulaSpatialValue, PrecedentSpatialValue, PrecedentKind } from '../spatial';
5
5
  import { ICalcGrid } from '../grid';
6
6
  import { IContextContainer, CalcNode } from '../state';
7
+ import { ReadHook } from '../state/_ReadObservedGrid';
8
+ import { CircularReference, CycleHost } from './CircularReference';
7
9
  /**
8
10
  * Executes a calculation cycle: collect → mark-and-plan → evaluate.
9
11
  *
@@ -15,18 +17,129 @@ export declare class CalcExecutor {
15
17
  protected _container: IContextContainer;
16
18
  protected _context: IFormulaContext;
17
19
  private readonly _requestCalculate;
18
- protected _pendingCalcs: Set<CalcNode>;
20
+ protected _pendingCalcs: Map<CalcNode, number>;
21
+ /** Debounced drain of {@link _pendingCalcs} into the volatile set; one instance for the executor's lifetime. */
22
+ private readonly _flushPendingCalcs;
19
23
  /**
20
- * Nodes the topological sort could not order — a reference cycle and everything downstream of it.
21
- * Rewritten by every `doMarkAndPlan` that detects one; meaningless otherwise.
24
+ * The last pass's circular roster: every node it evaluated by demand (a `doMarkAndPlan` that
25
+ * could not order, or the iteration guard's hand-over) plus every node whose dynamic read
26
+ * landed on its own cell, each carrying its `cycleRole`. The module scratch `_circularNodes`,
27
+ * this many; a node in it is flagged `CalcState.Rostered`. Emptied when a pass starts.
22
28
  */
23
- protected _circularNodes: CalcNode[];
29
+ protected _circularNodesLength: number;
30
+ /** The nodes the current `doMarkAndPlan` handed to demand evaluation, in worklist order. */
31
+ protected _demandBatch: CalcNode[];
32
+ /** {@link _onPassRead} as a bound hook — one closure for the executor's lifetime. */
33
+ protected _passHook: ReadHook;
24
34
  /**
25
- * Identifies the cycle last reported, so an unchanged one is not re-announced. A circular
26
- * reference persists until the user edits it away, and every pass re-detects it — including
27
- * volatile ones, which would otherwise log forever. `null` once a pass completes cycle-free.
35
+ * Marks a node downstream of the cycle `_taintGroup` names (design §2.1: every cell that reads
36
+ * a cycle, however it came to, is `0`). It skips evaluation — `Done` with a demand result of
37
+ * `0` — and taints its own dependents when the plan reaches it. A node already in a cycle keeps
38
+ * its role.
28
39
  */
29
- protected _circularReported: string | null;
40
+ protected _taintDownstream: (calc: CalcNode) => void;
41
+ protected _taintGroup: number;
42
+ /**
43
+ * Iterative calculation for the pass in progress (design §6.3); `null` with it off. Under it a
44
+ * read of a `Waiting` node is not an abort: the reader goes on with the node's previous value
45
+ * and the slice is marked once the reader returns (`_iterHits`); a read of a member's result
46
+ * makes the reader downstream without aborting it either; and {@link _demandSweep}
47
+ * re-evaluates the members until they settle.
48
+ */
49
+ protected _iterate: CalcExecutor.IterateOptions | null;
50
+ /** Inside {@link doDemandEvaluate} — where a `Waiting` read under iteration is marked in place. */
51
+ protected _demandActive: boolean;
52
+ /**
53
+ * Lengths of the module scratch lists of one {@link doDemandEvaluate}: `_iterHits`, the
54
+ * `Waiting` nodes the evaluation in progress read under iteration (each flagged
55
+ * `CalcState.IterHit`; consumed when it returns); `_iterMembers`, the members the call marked,
56
+ * in marking order — the sweep order; `_iterDownstream`, the nodes it marked downstream,
57
+ * evaluated once more after the sweeps. On the executor rather than local because the lists
58
+ * grow across the runs a sweep nests.
59
+ */
60
+ protected _iterHitsLength: number;
61
+ protected _iterMembersLength: number;
62
+ protected _iterDownstreamLength: number;
63
+ /** The pass the hook answers for; inert otherwise. Set by `doMarkAndPlan`, released with `_gridUpdates`. */
64
+ protected _passActive: boolean;
65
+ protected _passCycleId: number;
66
+ protected _passMarkId: number;
67
+ /**
68
+ * Commits `doEvaluate`'s pending batch — installed for the length of a `doEvaluate`. A read
69
+ * that lands on a node evaluated but not yet committed needs nothing more than this: the
70
+ * value is right, it is just not in the grid yet.
71
+ */
72
+ protected _flush: (() => void) | null;
73
+ /**
74
+ * The cycle table (design §7.3): per cycle id, one live {@link CircularReference} holding the
75
+ * nodes the sheet index currently shows as its members and as downstream of it — the object
76
+ * the index payloads carry and `ICell.getCircularReference()` answers. Persists across passes
77
+ * (a node moves when a commit writes its delta, `_trackCircular`) and is confirmed against the
78
+ * graph on read, since a deleted formula's node lingers here until something re-evaluates its
79
+ * cell. A cycle is created on first sight and dropped when its last node leaves.
80
+ */
81
+ protected _cycles: Map<number, CircularReference>;
82
+ /** What a {@link CircularReference} may ask of this executor; built once, shared by all. */
83
+ protected _cycleHost: CycleHost;
84
+ /** Last cycle id handed out by a demand pass; ids are unique across passes. */
85
+ protected _cycleGroupNext: number;
86
+ /**
87
+ * Counters over every demand pass since construction: nodes evaluated by demand and
88
+ * `NeedsFirst` retries. A retry is the cost of the path (a formula run and thrown away), so a
89
+ * chain Kahn could not order should show about one per node at most.
90
+ */
91
+ protected _demandStats: {
92
+ evaluations: number;
93
+ retries: number;
94
+ };
95
+ /**
96
+ * Counters and per-phase timings over every plan since construction — the measurement rig the
97
+ * performance plan's §1.6 rollout asks for, and what validates the §1.2/§1.3 caches while
98
+ * {@link planCacheMode} is `'assert'`.
99
+ */
100
+ protected _planStats: CalcExecutor.PlanStats;
101
+ /**
102
+ * The plan caches of CALC-PERFORMANCE-PLAN.md §1.2/§1.3.
103
+ *
104
+ * - `'on'` (default): an eligible pass skips the in-degree + Kahn phases (plan = affected set
105
+ * sorted by order stamp) and discovery reuses cached `dependents`.
106
+ * - `'assert'` (the §1.6 rollout mode; the diagnostic to flip back to if values ever look
107
+ * stale — `__sxlCalc.mode('assert')`): stamps are written and eligibility computed, but every
108
+ * pass still runs the full search + Kahn phases; the fast path's order and the cached
109
+ * `dependents` are VERIFIED against them, mismatches counted in
110
+ * `planAssertFailures`/`dependentsAssertFailures` and logged. Costs the old planning price
111
+ * plus epsilon.
112
+ * - `'off'`: stamps are neither written nor consulted.
113
+ */
114
+ planCacheMode: 'off' | 'assert' | 'on';
115
+ /** Next order-stamp plan id to hand out; ids are unique across passes. */
116
+ protected _planIdNext: number;
117
+ /** Id of the last fully-ordered plan that stamped (§1.2); `0` = none yet. */
118
+ protected _lastPlanId: number;
119
+ /** Length of the last stamped plan — the `orderIndex` range the fast path's bucket order spans. */
120
+ protected _lastStampedLength: number;
121
+ /**
122
+ * Whole-closure reuse (§1.4): remembered fully-ordered passes — seeds, plan, and the
123
+ * `precedentEpoch` each was planned at (`-1` = empty). A pass with the identical seed set at
124
+ * the same epoch has the identical affected closure and the same valid order, so discovery and
125
+ * the sort are skipped outright: the plan is reused verbatim and each node gets only its
126
+ * per-pass reset. TWO slots, because one was not enough for the common workflow — "type a value,
127
+ * then fill it down": the single-cell pass overwrote the remembered bulk plan every time. Slots
128
+ * are recycled least-recently-USED (a hit refreshes a memo's clock), so the two plans in play
129
+ * both stay resident: bulk → small edits → same bulk hits, and two alternating bulk edits both
130
+ * hit. NOT "keep the largest": a full calc's plan is the same size as a bulk edit's over the
131
+ * same sheet, and that tie evicted the bulk memo on the very next small edit. Per executor, not
132
+ * module scope (§1.5).
133
+ */
134
+ protected _planMemos: [CalcExecutor.PlanMemo, CalcExecutor.PlanMemo];
135
+ /** The memos' LRU clock. Its own counter, NOT `PlanStats.passes`: a stats reset must not reorder the slots. */
136
+ protected _planMemoClock: number;
137
+ /**
138
+ * Nodes with an async promise in flight. Per executor, NOT module scope — module scope would
139
+ * cross-pollute workbook instances (undo to 0 formulas needs to clear these; TODO - revisit
140
+ * how we manage promises).
141
+ */
142
+ protected _pendingPromises: Set<CalcNode>;
30
143
  _gridUpdates: Map<string, ICalcGrid.IncrementalUpdates> | null;
31
144
  _addDynamicPrecedent: ((node: CalcNode, range: IRange.Coords | undefined, kind: PrecedentKind) => void) | null;
32
145
  constructor(container: IContextContainer, context: IFormulaContext, requestCalculate: () => void);
@@ -45,14 +158,130 @@ export declare class CalcExecutor {
45
158
  */
46
159
  execute(cycleId: number, options: CalcExecutor.ExecuteOptions, state: CalcExecutor.ExecuteState): CalcExecutor.ExecuteResult | null;
47
160
  /**
48
- * Reports the reference cycle found by the preceding {@link doMarkAndPlan}.
161
+ * The read hook of a pass (DESIGN-CIRCULAR-CALC.md §4.3, §5), shared by both paths: armed for
162
+ * every reference while a node is evaluated by demand, and for dynamic-branch references
163
+ * (`OFFSET`/`INDIRECT`/`INDEX` results) on the ordinary path. A read that lands on a node of
164
+ * THIS pass decides: `Waiting` (on the in-progress stack — transitively waiting on the reader)
165
+ * is a cycle; `Unevaluated` suspends the reader until it is done; `Evaluated` (in the pending
166
+ * batch) is committed on the spot and read; `Done` with a cycle role makes the reader
167
+ * downstream. Any other cell is a value. Inert outside a pass, so a reference built during one
168
+ * and read later (a spill committed afterwards) is plain values.
169
+ */
170
+ private _onPassRead;
171
+ /**
172
+ * Under iteration, a read of a `Done` cycle node by the node being evaluated: the reader is
173
+ * downstream of that cycle (design §6.3) unless it is in one itself. Nothing is aborted — the
174
+ * reader goes on with the value — so the mark is made here, on the reader.
175
+ */
176
+ private _iterRead;
177
+ /**
178
+ * The nodes of this pass inside `coords` that are still `Unevaluated`, written into `into` from
179
+ * slot `length` on; returns the new length.
180
+ */
181
+ private _collectUnevaluated;
182
+ /**
183
+ * Evaluates `worklist` by demand (DESIGN-CIRCULAR-CALC.md §4.3): each node is evaluated with the
184
+ * read hook armed; a read that lands on one not yet evaluated suspends the reader and evaluates
185
+ * that one first (`NeedsFirst`); a read that lands on one already suspended — waiting,
186
+ * transitively, on the reader — is a cycle (`Circular`), and the in-progress stack from it up
187
+ * to the reader are the members, written `0`. A node that reads a member is downstream and
188
+ * writes `0` too. Everything else simply computes: an unorderable static edge that is never
189
+ * read (`OFFSET`'s anchor, `ROW`'s argument, an untaken `IF` branch) makes no cycle.
49
190
  *
50
191
  * @remarks
51
- * One line per calculation pass, naming a representative cell the way Excel's status bar does
52
- * (`Worksheet.CircularReference` likewise returns a single cell, not the whole set). The count
53
- * covers everything downstream of the cycle too, so it is normally larger than the cycle itself.
192
+ * Two callers: `doEvaluate` when the plan reaches the nodes `doMarkAndPlan` could not order
193
+ * (the batch, after everything ordered is committed), and `doEvaluate` again for a single
194
+ * ordered node whose dynamic read landed on a node not yet evaluated — the same suspension,
195
+ * on the ordinary path (§5). Every node evaluated here is flagged `circular`, so the plan
196
+ * takes its `demandValue` when it reaches it, and its finalized scalar is already in the grid
197
+ * for later lazy reads; `flush` commits the ordered path's pending batch when a read needs a
198
+ * value still sitting in it. Flat: the stack is an array, so a 1,560-row chain is 1,560
199
+ * evaluations, not 1,560 JS frames. A node is retried at most once per `NeedsFirst` it raises,
200
+ * and with the prefetch of its static precedents that is normally once.
54
201
  */
55
- protected reportCircular(): void;
202
+ protected doDemandEvaluate(cycleId: number, getUpdates: (scope: string) => ICalcGrid.IncrementalUpdates, worklist: readonly CalcNode[], flush: () => void): void;
203
+ /**
204
+ * The finalized scalar goes into the grid now, so a later lazy read sees it; `doEvaluate`
205
+ * commits the same value with the notifications when it reaches the node.
206
+ */
207
+ private _writeEarly;
208
+ /** The worklist loop of {@link doDemandEvaluate}; re-entered by a sweep for the nodes a read needs first. */
209
+ private _demandRun;
210
+ /**
211
+ * Marks the started (`Waiting`) nodes on the in-progress stack from `read` up to the top as one
212
+ * cycle — the slice a `Waiting` read names (design §4.3). A slice touching a member of an
213
+ * earlier cycle joins that cycle. The started nodes are on the stack in READ order — each is
214
+ * there because the one below it read it, and the reader on top read `read` — so the slice is
215
+ * the walk `getPath` answers: each member is linked to the next, the last back to the first.
216
+ * The demand state is left as it is: the caller finishes the members (iteration off) or lets
217
+ * them run on (iteration on). Returns the stack index of `read`.
218
+ */
219
+ private _markCycleSlice;
220
+ /**
221
+ * Iterative calculation (design §6.3) over the members {@link _demandRun} marked, which it
222
+ * evaluated once each — sweep 1. Every further sweep re-evaluates them in marking order, each
223
+ * read seeing the latest value (Gauss–Seidel, as Excel: `probe-iterative.ps1`), until no member
224
+ * moved by `maxChange` or `maxIterations` sweeps ran. The limits are global, as in Excel — one
225
+ * unsettled member keeps every cycle of the pass sweeping. Downstream nodes read the members
226
+ * once more afterwards; they never feed a cycle, so they need no sweeping. The members are then
227
+ * registered as volatile so the next recalculation re-runs them, which is how `=A1+1` advances
228
+ * per recalc (Excel re-iterates every cycle on every `Calculate()`).
229
+ */
230
+ private _demandSweep;
231
+ /** Clears the `IterHit` flags of the hits not consumed by {@link _demandRun}'s loop and empties the list. */
232
+ private _drainIterHits;
233
+ /**
234
+ * One evaluation of a `Done` node under iteration, with the read hook armed. A read that
235
+ * needs a node not yet evaluated (a handed-over node's sweep runs before the batch) runs those
236
+ * by demand and retries; a `Waiting` read cannot occur (nothing is waiting) and a member's
237
+ * result is a value (`_iterRead`), so nothing aborts.
238
+ */
239
+ private _demandEvaluateOne;
240
+ /**
241
+ * @internal
242
+ * Every cell the sheets currently show in a cycle or downstream of one, as dirty regions — what
243
+ * a change of the iterative-calculation setting re-evaluates.
244
+ */
245
+ getCycleCells(): ISpatialMap.Entry[];
246
+ /** Whether the pass in progress found a cycle member. */
247
+ protected _hasCycle(): boolean;
248
+ /** Adds `node` to the pass's circular roster, once. */
249
+ private _addCircular;
250
+ /** Empties the roster for a new pass, clearing each node's `Rostered` flag on the way out. */
251
+ private _resetCircular;
252
+ /**
253
+ * Records the difference between what the sheet's circular index shows for `node` and what
254
+ * this pass decided, in the cycle table and in the pass's conflating delta — the same path a
255
+ * pending status takes to the sheet (design §7.2). Called only when one of the two is set.
256
+ */
257
+ protected _trackCircular(node: CalcNode, updates: ICalcGrid.IncrementalUpdates, major: number, minor: number, isColumn: boolean): void;
258
+ /** The public face of cycle `group`, created on first sight. */
259
+ private _cycleOf;
260
+ /**
261
+ * Whether `node` is still the node registered for its own cell.
262
+ *
263
+ * @remarks
264
+ * A `CalcNode` is a plain graph object reachable by identity long after the grid has replaced or
265
+ * dropped its block — the same hazard `doCollectVolatileCalcs` guards against with `isLiveBlock`.
266
+ */
267
+ protected isLiveNode(node: CalcNode): boolean;
268
+ /**
269
+ * @internal
270
+ * A copy of the last pass's circular roster — see {@link _circularNodesLength} — each node
271
+ * carrying the {@link CycleRole} its reads gave it. Describes the last pass only.
272
+ */
273
+ getCircularNodes(): readonly CalcNode[];
274
+ /**
275
+ * @internal
276
+ * Demand-pass counters since construction — evaluations and `NeedsFirst` retries — plus the
277
+ * plan-phase counters and timings (see {@link _planStats}).
278
+ */
279
+ getDemandStats(): {
280
+ evaluations: number;
281
+ retries: number;
282
+ } & CalcExecutor.PlanStats;
283
+ /** @internal Zeroes every counter/timing so a measurement window starts clean (`__sxlCalc.reset()`). */
284
+ resetStats(): void;
56
285
  /**
57
286
  * Collects and marks all volatile CalcNodes.
58
287
  *
@@ -79,7 +308,18 @@ export declare class CalcExecutor {
79
308
  * @remarks
80
309
  * Uses Kahn's algorithm.
81
310
  */
82
- doMarkAndPlan(cycleId: number, markId: number, initial: FixedPackedArray<CalcNode>, plan: FixedPackedArray<CalcNode>): boolean;
311
+ doMarkAndPlan(cycleId: number, markId: number, initial: FixedPackedArray<CalcNode>, plan: FixedPackedArray<CalcNode>, forceDemand?: boolean): boolean;
312
+ /**
313
+ * The remembered plan whose seed set is exactly this pass's, at this epoch — or `null`. The
314
+ * length check first: it is what rejects the common miss (a single-cell pass against a bulk
315
+ * memo) before any node is touched.
316
+ */
317
+ private _matchPlanMemo;
318
+ /**
319
+ * Records this pass's seeds and plan for whole-closure reuse (§1.4). The victim slot is a stale
320
+ * one if there is one, else the least recently used.
321
+ */
322
+ private _rememberPlan;
83
323
  doEvaluate(cycleId: number, getUpdates: (scope: string) => ICalcGrid.IncrementalUpdates, scheduleRecalculation: (cycleId: number, calcNode?: CalcNode) => void, commit: (calcNodes: CalcNode[], calcNodesLength: number) => void, forceFull: boolean, plan: FixedPackedArray<CalcNode>): ISpatialMap | undefined;
84
324
  }
85
325
  export declare namespace CalcExecutor {
@@ -96,6 +336,24 @@ export declare namespace CalcExecutor {
96
336
  * The grid to seed the pass FROM — `ICalculation.CalculateOptions.scope`, i.e. "Calculate Sheet".
97
337
  */
98
338
  scopeGridId?: string;
339
+ /**
340
+ * Iterative calculation (design §6.3) for the pass; `null`/absent with it off.
341
+ */
342
+ iterate?: IterateOptions | null;
343
+ }
344
+ /** The two limits of `ICalculation.IterativeCalculation`. */
345
+ interface IterateOptions {
346
+ maxIterations: number;
347
+ maxChange: number;
348
+ }
349
+ /** How a cycle's last iterated evaluation ended — what `ICircularReference` answers. */
350
+ interface IterationResult {
351
+ /** Every member moved by less than `maxChange` in the last sweep. */
352
+ converged: boolean;
353
+ /** Sweeps run, the first evaluation included. */
354
+ iterationCount: number;
355
+ /** The largest change any member made in the last sweep. */
356
+ lastChange: number;
99
357
  }
100
358
  /** Snapshot of Calculation's persistent state for one pass */
101
359
  interface ExecuteState {
@@ -105,6 +363,48 @@ export declare namespace CalcExecutor {
105
363
  /** Volatiles accumulated before this cycle — drained and cleared by execute() */
106
364
  oldVolatiles: Set<CalcNode>;
107
365
  }
366
+ /** A remembered fully-ordered pass for whole-closure reuse (§1.4) — see `CalcExecutor._planMemos`. */
367
+ interface PlanMemo {
368
+ seeds: CalcNode[];
369
+ seedsLength: number;
370
+ plan: CalcNode[];
371
+ planLength: number;
372
+ /** The `precedentEpoch` the plan was made at; `-1` = empty. */
373
+ epoch: number;
374
+ /** `CalcExecutor._planMemoClock` when last written or matched — the LRU key for slot recycling. */
375
+ used: number;
376
+ }
377
+ /** Plan-phase counters and timings — see `CalcExecutor.planCacheMode` and performance plan §1.6. */
378
+ interface PlanStats {
379
+ /** In-degree formula lookups answered by the single-entry block cache. */
380
+ formulaCacheHits: number;
381
+ formulaCacheMisses: number;
382
+ /** doMarkAndPlan invocations. */
383
+ passes: number;
384
+ /** Passes whose whole affected set carried valid order stamps (fast-path candidates). */
385
+ fastEligible: number;
386
+ /** Eligible passes that actually skipped in-degree + Kahn (`'on'` mode only). */
387
+ fastHits: number;
388
+ /** Passes whose seed set and precedent epoch matched the last plan (§1.4 candidates). */
389
+ closureEligible: number;
390
+ /** Closure-eligible passes that reused the last plan verbatim, skipping discovery too (`'on'` only). */
391
+ closureHits: number;
392
+ /** `'assert'` mode: closure-eligible passes whose fresh affected set differed from the last plan. */
393
+ closureAssertFailures: number;
394
+ /** Fully-ordered plans that wrote order stamps. */
395
+ stampedPlans: number;
396
+ /** `'assert'` mode: eligible passes whose stamp order contradicted a real static edge. */
397
+ planAssertFailures: number;
398
+ /** `'assert'` mode: cached `dependents` that did not match a fresh search. */
399
+ dependentsAssertFailures: number;
400
+ /** Discovery searches skipped (or verified) because `dependentsEpoch` was current. */
401
+ dependentsCacheHits: number;
402
+ dependentsCacheMisses: number;
403
+ discoveryMs: number;
404
+ inDegreeMs: number;
405
+ kahnMs: number;
406
+ evaluateMs: number;
407
+ }
108
408
  /** Updated spatial maps and committed grid batches returned to Calculation */
109
409
  interface ExecuteResult {
110
410
  precedentSpatial: ISpatialMap3D<PrecedentSpatialValue>;
@@ -1,7 +1,38 @@
1
1
  import { IRange, Scalar } from '@sheetxl/primitives';
2
2
  import { Tuple, ISpatialMap } from '../../collections';
3
+ import { CalcSignal } from '../../primitives/CalcSignal';
3
4
  import { CalcNode } from '../state';
4
5
  import { PrecedentSpatialValue, FormulaSpatialValue } from '../spatial';
6
+ /**
7
+ * Raised from a read that landed on unorderable nodes not yet evaluated: the reader is suspended,
8
+ * `nodes` are evaluated first, and the reader is retried from scratch.
9
+ */
10
+ export declare class NeedsFirstSignal extends CalcSignal {
11
+ readonly nodes: readonly CalcNode[];
12
+ readonly count: number;
13
+ /**
14
+ * @param nodes A scratch array the executor reuses across reads; only the first `count` slots
15
+ * belong to this read, and the catcher consumes them before the next read can overwrite them.
16
+ */
17
+ constructor(nodes: readonly CalcNode[], count: number);
18
+ }
19
+ /**
20
+ * Raised from a read that landed on a node already suspended — one that is, transitively,
21
+ * waiting on the reader. The in-progress stack from `node` up to the reader is a cycle.
22
+ */
23
+ export declare class CircularSignal extends CalcSignal {
24
+ readonly node: CalcNode;
25
+ constructor(node: CalcNode);
26
+ }
27
+ /**
28
+ * Raised from a read that landed on a cycle member or a cell downstream of one: the reader is
29
+ * downstream too and writes `0` without finishing (Excel, iterative calc off — `=A1+100` beside
30
+ * `=A1+1` in A1 is `0`, not `100`).
31
+ */
32
+ export declare class DownstreamSignal extends CalcSignal {
33
+ readonly node: CalcNode;
34
+ constructor(node: CalcNode);
35
+ }
5
36
  export declare function rangeToTuples(range: IRange, offsetMajor?: number, offsetMinor?: number): Tuple.BoundedTuples<Scalar>;
6
37
  /**
7
38
  * TODO - document and/or rationalize with visitPrecedentBlock
@@ -0,0 +1,81 @@
1
+ import { IRange } from '@sheetxl/primitives';
2
+ import { ICalculation } from '../ICalculation';
3
+ import { CalcNode } from '../state';
4
+ import { CircularEntry } from '../spatial';
5
+ import { CalcExecutor } from './CalcExecutor';
6
+ /**
7
+ * The two graph questions a {@link CircularReference} cannot answer on its own; built once by
8
+ * the executor, shared by every cycle.
9
+ */
10
+ export interface CycleHost {
11
+ /** Whether `node` is still the node registered for its own cell (a deleted formula's is not). */
12
+ isLiveNode(node: CalcNode): boolean;
13
+ /** The sheet name for a grid id, or `undefined` for a virtual grid (a named formula's body). */
14
+ resolveScope(gridId: string): string | undefined;
15
+ }
16
+ /**
17
+ * One reference cycle (DESIGN-CIRCULAR-CALC.md §7.3): the executor's record of it and its public
18
+ * face in one — the object a `Feature.Circular` region carries as its payload and
19
+ * `ICell.getCircularReference()` answers. It holds the nodes the sheet index currently shows as
20
+ * its members and as downstream of it, the interned index payloads, and how the sweep that
21
+ * settled it ended. It persists across passes — a node moves between cycles when a commit writes
22
+ * its delta (`_trackCircular`) — and is dropped when its last node leaves.
23
+ *
24
+ * @remarks
25
+ * Every read filters through {@link CycleHost.isLiveNode}: a pass that deletes the offending
26
+ * formula never plans, so the sets cannot clear themselves, and a member is counted only while
27
+ * its cell still holds the block the cycle was found in (design §7.3, trap 5).
28
+ *
29
+ * The iteration answers come from the sweep that settled the cycle (design §6.3). A cycle id is
30
+ * minted by the pass that finds it, so a cycle evaluated with iteration off is a different object
31
+ * with no record — `isIterative()` is exactly "a sweep settled this one".
32
+ */
33
+ export declare class CircularReference implements ICalculation.ICircularReference {
34
+ protected readonly _host: CycleHost;
35
+ protected readonly _cycleId: number;
36
+ /** @internal The nodes the sheet index shows in the cycle; maintained by the executor. */
37
+ readonly members: Set<CalcNode>;
38
+ /** @internal The nodes the sheet index shows downstream of the cycle; maintained by the executor. */
39
+ readonly downstream: Set<CalcNode>;
40
+ /** One payload per kind, so equal payloads are one object and adjacent cells one region. */
41
+ protected _memberEntry: CircularEntry | null;
42
+ protected _downstreamEntry: CircularEntry | null;
43
+ protected _iteration: CalcExecutor.IterationResult | null;
44
+ constructor(host: CycleHost, cycleId: number);
45
+ /** @internal The executor's key for this cycle. */
46
+ getCycleId(): number;
47
+ /** @internal Whether the sheet index shows no cell in or downstream of the cycle any more. */
48
+ isEmpty(): boolean;
49
+ /** @internal The index payload for the cycle's members or for its downstream cells. */
50
+ getEntry(isMember: boolean): CircularEntry;
51
+ /** @internal How the sweep that settled this cycle ended; written by the executor. */
52
+ setIteration(result: CalcExecutor.IterationResult): void;
53
+ /** @inheritdoc ICalculation.ICircularReference.getRepresentative */
54
+ getRepresentative(): IRange.CellCoords | null;
55
+ /** @inheritdoc ICalculation.ICircularReference.getMembers */
56
+ getMembers(): readonly IRange.CellCoords[];
57
+ /** @inheritdoc ICalculation.ICircularReference.getMemberCount */
58
+ getMemberCount(): number;
59
+ /** @inheritdoc ICalculation.ICircularReference.getDownstreamCount */
60
+ getDownstreamCount(): number;
61
+ /** @inheritdoc ICalculation.ICircularReference.isMember */
62
+ isMember(coords: IRange.CellCoords): boolean;
63
+ /** @inheritdoc ICalculation.ICircularReference.getPath */
64
+ getPath(from: IRange.CellCoords): readonly IRange.CellCoords[];
65
+ /** The live member at `coords`, or `null`. */
66
+ protected _memberAt(coords: IRange.CellCoords): CalcNode | null;
67
+ /** @inheritdoc ICalculation.ICircularReference.isIterative */
68
+ isIterative(): boolean;
69
+ /** @inheritdoc ICalculation.ICircularReference.isConverged */
70
+ isConverged(): boolean;
71
+ /** @inheritdoc ICalculation.ICircularReference.getIterationCount */
72
+ getIterationCount(): number;
73
+ /** @inheritdoc ICalculation.ICircularReference.getLastChange */
74
+ getLastChange(): number;
75
+ /** The members still on the graph, in row-major order (virtual grids first, then by sheet). */
76
+ protected _liveMembers(): CalcNode[];
77
+ protected _countLive(nodes: Set<CalcNode>): number;
78
+ protected _scopeOf(node: CalcNode): string | null;
79
+ protected _toCoords(node: CalcNode): IRange.CellCoords;
80
+ }
81
+ //# sourceMappingURL=CircularReference.d.ts.map
@@ -3,7 +3,7 @@ import { IListener } from '../../listener';
3
3
  import { ListenerEmitter } from '../../listener/ListenerEmitter';
4
4
  import { Tuple, ISpatialQuery, ISparseGrid, ISpatialMap, ISpatialMap3D } from '../../collections';
5
5
  import { IContextContainer, CalcNode } from '../state';
6
- import { SpillEntry, FormulaSpatialValue, ContentfulSpatialValue, PrecedentSpatialValue } from '../spatial';
6
+ import { SpillEntry, FormulaSpatialValue, ContentfulSpatialValue, PrecedentSpatialValue, CircularEntry } from '../spatial';
7
7
  import { IFormulaBlock } from '../formula';
8
8
  import { ReferenceCoords } from '../Types';
9
9
  import { ICalculation } from '../ICalculation';
@@ -20,12 +20,14 @@ export declare class CalcGrid implements ICalcGrid {
20
20
  _spillSpatial: ISpatialMap<SpillEntry>;
21
21
  _errorSpatial: ISpatialMap<string>;
22
22
  _pendingSpatial: ISpatialMap<boolean>;
23
+ _circularSpatial: ISpatialMap<CircularEntry>;
23
24
  _contentfulSpatial: ISpatialMap<ContentfulSpatialValue>;
24
25
  _notifyValues: ISpatialMap;
25
26
  _notifySpillsInsert: ISpatialMap.Entry<SpillEntry>[];
26
27
  _notifySpillsRemove: IRange.Coords[];
27
28
  _notifyFormats: ISpatialMap;
28
29
  _notifyPending: ISpatialMap<boolean>;
30
+ _notifyCircular: ISpatialMap<CircularEntry | false>;
29
31
  _cloneKey?: CalcGrid;
30
32
  _version: number;
31
33
  _iterationId: number;
@@ -1,7 +1,7 @@
1
1
  import { IRange, Scalar } from '@sheetxl/primitives';
2
2
  import { Tuple, ISparseGrid, ISpatialMap, ISpatialMap3D } from '../../collections';
3
3
  import { CalcNode } from '../state';
4
- import { ContentfulSpatialValue, FormulaSpatialValue, SpillEntry, PrecedentSpatialValue, PrecedentKind } from '../spatial';
4
+ import { ContentfulSpatialValue, FormulaSpatialValue, SpillEntry, PrecedentSpatialValue, CircularEntry, PrecedentKind } from '../spatial';
5
5
  import { ReferenceCoords } from '../Types';
6
6
  export declare function anchorCompareValues(a: ISpatialMap.Entry<FormulaSpatialValue>, removing: ISpatialMap.Entry<FormulaSpatialValue>): boolean;
7
7
  /**
@@ -19,7 +19,8 @@ export declare function isTopLeft(rowIndex: number, colIndex: number, range: IRa
19
19
  * This code will inline spill errors but spill results are handled in calculations.
20
20
  */
21
21
  export declare function applyContentful(cycleId: number, removes: readonly IRange.Coords[] | null | undefined, inserts: ISpatialMap.Entry[] | null | undefined, contentfulSpatial: ISpatialMap<ContentfulSpatialValue>, spillSpatial: ISpatialMap<SpillEntry>, values: ISparseGrid<Scalar>, addDirty?: (colStart: number, rowStart: number, colEnd: number, rowEnd: number) => void, notifyValuesSpatial?: ISpatialMap, notifySpills?: (insert: ISpatialMap.Entry<SpillEntry>, remove: ISpatialMap.Entry<SpillEntry>) => void, notifyPending?: ISpatialMap<boolean>, errorSpatial?: ISpatialMap<string>, // notifyErrorSpatial,
22
- isColumn?: boolean, notifyCalc?: boolean): IRange.Coords | null;
22
+ isColumn?: boolean, notifyCalc?: boolean, // should notify. this is to differentiate between user set values and calc set values
23
+ circularSpatial?: ISpatialMap<CircularEntry>): IRange.Coords | null;
23
24
  /**
24
25
  * Creates a map from tuples
25
26
  *
@@ -5,7 +5,7 @@ import { IListener } from '../../listener';
5
5
  import { IHistory } from '../../history';
6
6
  import { ISpatialQuery, ISpatialMap, ISparseGrid, Tuple } from '../../collections';
7
7
  import { IFormulaBlock } from '../formula';
8
- import { ContentfulSpatialValue, SpillEntry } from '../spatial';
8
+ import { ContentfulSpatialValue, SpillEntry, CircularEntry } from '../spatial';
9
9
  import { ReferenceCoords, NamedReference } from '../Types';
10
10
  import { ICalculation } from '../ICalculation';
11
11
  /**
@@ -311,6 +311,14 @@ export declare namespace ICalcGrid {
311
311
  * These are guaranteed to not overlap.
312
312
  */
313
313
  pendingUpdates?: ISpatialMap.Entry<boolean>[];
314
+ /**
315
+ * Cells that entered or left a reference cycle (or the set downstream of one). A `null`
316
+ * value is a removal.
317
+ *
318
+ * @remarks
319
+ * These are guaranteed to not overlap.
320
+ */
321
+ circularUpdates?: ISpatialMap.Entry<CircularEntry | null>[];
314
322
  }
315
323
  /**
316
324
  * Returns from `ICalcGrid.beginUpdates()`.
@@ -324,13 +332,16 @@ export declare namespace ICalcGrid {
324
332
  spillSpatial: ISpatialMap<SpillEntry>;
325
333
  errorSpatial: ISpatialMap<string>;
326
334
  pendingSpatial: ISpatialMap<boolean>;
335
+ circularSpatial: ISpatialMap<CircularEntry>;
327
336
  contentfulSpatial: ISpatialMap<ContentfulSpatialValue>;
328
337
  notifyValues: ISpatialMap;
329
338
  notifySpillsInsert: ISpatialMap.Entry<SpillEntry>[];
330
339
  notifySpillsRemove: IRange.Coords[];
331
340
  notifyFormats: ISpatialMap;
332
341
  notifyPending: ISpatialMap<boolean>;
342
+ notifyCircular: ISpatialMap<CircularEntry | false>;
333
343
  pendingConflating: ISpatialMap.IConflatingEntries<boolean> | null;
344
+ circularConflating: ISpatialMap.IConflatingEntries<CircularEntry | false> | null;
334
345
  errorRemovesConflating: ISpatialMap.IConflatingEntries<null> | null;
335
346
  errorInsertsConflating: ISpatialMap.IConflatingEntries<string> | null;
336
347
  formatsConflating: ISpatialMap.IConflatingEntries | null;
@@ -0,0 +1,18 @@
1
+ import { ICalculation } from '../ICalculation';
2
+ /**
3
+ * What a sheet's circular index says about a cell (DESIGN-CIRCULAR-CALC.md §7.2): which
4
+ * reference cycle it belongs to, and whether it is in the cycle or merely downstream of it.
5
+ *
6
+ * @remarks
7
+ * The value stays what it is (`0`, Excel's answer with iterative calculation off); this is
8
+ * metadata beside it, the way `SpillEntry` marks a spill zone. Entries are interned per
9
+ * `(cycle, kind)` by the executor, so equal payloads are the same object and adjacent cells of
10
+ * one cycle coalesce into one region — and a region's payload IS the cycle, no id to look up.
11
+ */
12
+ export interface CircularEntry {
13
+ /** The cycle; one live object per cycle, shared by every cell in it or downstream of it. */
14
+ readonly cycle: ICalculation.ICircularReference;
15
+ /** In the cycle, or a reader of it (transitively). Both evaluate to `0`. */
16
+ readonly kind: 'member' | 'downstream';
17
+ }
18
+ //# sourceMappingURL=CircularEntry.d.ts.map
@@ -1,4 +1,5 @@
1
1
  export * from './SpillEntry';
2
+ export * from './CircularEntry';
2
3
  export * from './ContentfulSpatialValue';
3
4
  export * from './FormulaSpatialValue';
4
5
  export * from './PrecedentSpatialValue';