@sheetxl/sdk 0.7.28-beta.4 → 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 (37) hide show
  1. package/browser/cjs/{CD2RXdnfqSYNc6gX.cjs → BatsV2UUraVDCRXm.cjs} +1 -1
  2. package/browser/cjs/CUz53T65cRGQRgvv.cjs +33 -0
  3. package/browser/cjs/{BXtvdT1Z-MsB_o5F.cjs → DU0kYUtdFa3jK-im.cjs} +1 -1
  4. package/browser/cjs/{DcLpXGexEtOxi-dD.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/{CoA-sUxjiIUIpTUF.mjs → 6JKSOvc5czaQB2Bj.mjs} +3 -3
  8. package/browser/esm/{DBIu6NUHIcxOOBQp.mjs → B0HS42a7lga1k1U_.mjs} +1 -1
  9. package/browser/esm/{Dy4Sdpju1T2icURu.mjs → BtuJ9AGzeURtAsGo.mjs} +5797 -5410
  10. package/browser/esm/{0Mj5WKM362q6CECb.mjs → DGU6sM25u0EWCKl1.mjs} +3 -3
  11. package/browser/esm/{CKk58ZLcrllMzfou.mjs → DlPMeIyxT9tG_WDk.mjs} +2 -2
  12. package/browser/esm/{CPTtL2f6_4iblmFM.mjs → f2G3QRgEbvFJcoa2.mjs} +2 -2
  13. package/browser/esm/index.mjs +6 -6
  14. package/browser/esm/{CHSahmMFYNB0Ajkf.mjs → uyWBMSIxMlt8n5Z3.mjs} +2 -2
  15. package/node/esm/{DqgWsVXROpSgKq6c.mjs → -pxdn3M3tc6PLHs7.mjs} +20 -20
  16. package/node/esm/{BtGxbEbBm79YvF_k.mjs → 0n5bslM9jhO1JnI2.mjs} +1 -1
  17. package/node/esm/{DUYP6faJJHCaW1Xf.mjs → Cku7XiUvw1xrRasi.mjs} +1 -1
  18. package/node/esm/index.mjs +8279 -7901
  19. package/package.json +6 -6
  20. package/types/scripting-enums.json +4 -0
  21. package/types/scripting.d.ts +321 -19
  22. package/types/src/calculation/Calculation.d.ts +5 -0
  23. package/types/src/calculation/executor/CalcExecutor.d.ts +112 -3
  24. package/types/src/calculation/state/CalcNode.d.ts +20 -0
  25. package/types/src/calculation/state/CalculationContext.d.ts +3 -1
  26. package/types/src/calculation/state/IContextContainer.d.ts +31 -0
  27. package/types/src/cellRange/ICellRange.d.ts +39 -18
  28. package/types/src/lib/utils/_Utils.d.ts +7 -0
  29. package/types/src/primitives/PrimitiveUtils.d.ts +4 -2
  30. package/types/src/primitives/Range.d.ts +3 -1
  31. package/types/src/scripting/FunctionArguments.d.ts +22 -0
  32. package/types/src/scripting/TypedFunction.d.ts +60 -0
  33. package/types/src/sheet/Sheet.d.ts +8 -0
  34. package/types/src/sheet/SheetRange.d.ts +3 -0
  35. package/types/src/sheet/_SheetAutoFitUtils.d.ts +28 -0
  36. package/browser/cjs/BF14ZEAnaqxXroSj.cjs +0 -1
  37. package/browser/cjs/Bc4JjVVZfFo3R4He.cjs +0 -33
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sheetxl/sdk",
3
3
  "description": "SDK - A high-performance, embeddable spreadsheet engine.",
4
- "version": "0.7.28-beta.4",
4
+ "version": "0.8.0-beta.1",
5
5
  "license": "SEE LICENSE IN <LICENSE>",
6
6
  "type": "module",
7
7
  "exports": {
@@ -43,11 +43,11 @@
43
43
  "automation"
44
44
  ],
45
45
  "dependencies": {
46
- "@sheetxl/formulas": "^0.7.28-beta.4",
47
- "@sheetxl/formulas-extended": "^0.7.28-beta.4",
48
- "@sheetxl/primitives": "^0.7.28-beta.4",
49
- "@sheetxl/scripting": "^0.7.28-beta.4",
50
- "@sheetxl/utils": "^0.7.28-beta.4",
46
+ "@sheetxl/formulas": "^0.8.0-beta.1",
47
+ "@sheetxl/formulas-extended": "^0.8.0-beta.1",
48
+ "@sheetxl/primitives": "^0.8.0-beta.1",
49
+ "@sheetxl/scripting": "^0.8.0-beta.1",
50
+ "@sheetxl/utils": "^0.8.0-beta.1",
51
51
  "bahttext": "^2.4.0",
52
52
  "bessel": "^1.0.2",
53
53
  "chalk": "^6.0.0",
@@ -58,6 +58,10 @@
58
58
  "Column": "column",
59
59
  "Row": "row"
60
60
  },
61
+ "CompatFeatures": {
62
+ "noMemberFilter": "Members of a repeating scalar parameter are coerced rather than filtered; one that cannot be is #VALUE!.",
63
+ "noReferenceLift": "A multi-cell reference at a non-repeating scalar parameter is #VALUE! rather than a broadcast."
64
+ },
61
65
  "DateUnit": {
62
66
  "Day": "d",
63
67
  "Month": "m",
@@ -639,6 +639,65 @@ declare global {
639
639
  }
640
640
  }
641
641
 
642
+ /**
643
+ * Family aliases: one name an author writes that expands to several {@link CompatFeatures}.
644
+ *
645
+ * @remarks
646
+ * Excel's Analysis-ToolPak heritage is ONE measured rule with two halves that act on disjoint
647
+ * parameter kinds — `noReferenceLift` at a non-repeating scalar slot, `noMemberFilter` at a
648
+ * repeating one — so every function in the family carries the same two bits and `@compat atp`
649
+ * says so in one word. The alias is expanded by the linter; only the atomic features are ever
650
+ * stored, so `@compat atp` and `@compat noReferenceLift noMemberFilter` emit the same declaration.
651
+ * The atomic names stay available for the day a function measures as disagreeing on one half.
652
+ */
653
+ export const CompatAliases: Readonly<Record<string, readonly CompatFeature[]>>;
654
+
655
+ /**
656
+ * One of the {@link CompatFeatures} names.
657
+ */
658
+ export type CompatFeature = keyof typeof CompatFeatures;
659
+
660
+ /**
661
+ * The behaviors `@compat` can turn on, and what each one does.
662
+ *
663
+ * @remarks
664
+ * A feature earns a place here only if the BODY cannot do it — the decision happens before the
665
+ * body runs, or instead of running it. Everything a body can decide for itself belongs in the
666
+ * body, where `FormulaContext.getArgs()` and `Omittable` already reach.
667
+ *
668
+ * Function-level until a measurement forces otherwise: `noReferenceLift` is per-function in
669
+ * Excel, and no function is known whose parameters disagree.
670
+ *
671
+ * @see {@link https://www.sheetxl.com/docs/guides/concepts/udf | UDF authoring guide}
672
+ */
673
+ export const CompatFeatures: {
674
+ /**
675
+ * A multi-cell REFERENCE at a non-repeating scalar parameter is `#VALUE!` rather than a
676
+ * broadcast.
677
+ *
678
+ * Excel's Analysis-ToolPak family answers `CUMIPMT(G1:G2,120,100000,13,24,0)` with `#VALUE!`
679
+ * where the native `ROUND(B1:B3,3)` spills three results. A multi-cell reference has no way to
680
+ * become a scalar there, because these functions never intersect. An ARRAY at the same slot
681
+ * still lifts (`MROUND({3,4,5},5)` spills), so the refusal is about references specifically,
682
+ * and a REPEATING slot absorbs a container as members either way (`GCD(C1:C3)` is 4) — so this
683
+ * never applies to one.
684
+ */
685
+ readonly noReferenceLift: "A multi-cell reference at a non-repeating scalar parameter is #VALUE! rather than a broadcast.";
686
+ /**
687
+ * Members of a REPEATING scalar parameter are coerced rather than filtered: numeric text
688
+ * parses, a blank inside a multi-cell range reads 0, and a boolean or non-numeric text member
689
+ * is `#VALUE!` for the whole call.
690
+ *
691
+ * The native family FILTERS — `SUM({1,"8",TRUE})` is 1, the text and the boolean skipped, and a
692
+ * blank cell contributes nothing. Excel's Analysis-ToolPak family coerces instead:
693
+ * `GCD({6,"8",12})` is 2, `GCD({6,TRUE,12})` is `#VALUE!`, `LCM(6,A1:A2)` over two blank cells
694
+ * is 0. A direct argument follows the same rule (`GCD(6,TRUE,12)` is `#VALUE!` where
695
+ * `SUM(6,TRUE,12)` is 19), and a one-cell blank reference or an omitted argument is still
696
+ * skipped — it never became a member.
697
+ */
698
+ readonly noMemberFilter: "Members of a repeating scalar parameter are coerced rather than filtered; one that cannot be is #VALUE!.";
699
+ };
700
+
642
701
  /**
643
702
  * A compiled collection of functions — the serialized form of one module.
644
703
  *
@@ -5770,6 +5829,22 @@ declare global {
5770
5829
  * Returns the absolute bounds for the range.
5771
5830
  */
5772
5831
  getBounds(): Bounds;
5832
+ /**
5833
+ * The size this range's content needs — what {@link getBounds} would report if the range had
5834
+ * been {@link autoFit}. A pure query: nothing is measured onto the model and nothing is committed.
5835
+ *
5836
+ * @param options Measurement options. Default `{ axes: IRange.AxisSelection.Both }`.
5837
+ *
5838
+ * @remarks
5839
+ * - The width is the sum of the per-column best fits and the height the sum of the per-row best
5840
+ * fits, so a single-column range answers "how wide must this list be" directly.
5841
+ * - An axis left out of {@link BestFitOptions.axes} reports its current size.
5842
+ * - Only the range's own content is measured, so the answer never depends on cells outside it.
5843
+ * (`autoFit` also scans outside the range, to prove a shrink is safe.)
5844
+ * - Sheet protection is not consulted: reading a size cannot violate it, and a protected sheet
5845
+ * answers the same as an unprotected one.
5846
+ */
5847
+ getBestFit(options?: ICellRange.BestFitOptions): Size;
5773
5848
  /**
5774
5849
  * Indicates if the range is readonly only.
5775
5850
  *
@@ -6256,9 +6331,9 @@ declare global {
6256
6331
  includeMergeOverflow?: boolean;
6257
6332
  }
6258
6333
  /**
6259
- * Options for {@link ICellRange.autoFit}.
6334
+ * Options for {@link ICellRange.getBestFit} — measurement only.
6260
6335
  */
6261
- interface AutoFitOptions extends ITransaction.OperationOptions {
6336
+ interface BestFitOptions {
6262
6337
  /**
6263
6338
  * Best fit using the minimum amount of space required.
6264
6339
  *
@@ -6269,21 +6344,6 @@ declare global {
6269
6344
  * For example allowing numbers to round or use scientific notation.
6270
6345
  */
6271
6346
  minContent?: boolean;
6272
- /**
6273
- * Restricts autoFit to only grow, never shrink.
6274
- *
6275
- * @defaultValue false - adjust in both directions.
6276
- *
6277
- * @remarks
6278
- * This is useful when adjusting fonts and wan the columns to resize but not the rows.
6279
- */
6280
- expandOnly?: boolean | IRange.Axis;
6281
- /**
6282
- * If this is set to `true` then any headers that have custom sizes will be ignored.
6283
- *
6284
- * @defaultValue false
6285
- */
6286
- skipCustomSize?: boolean;
6287
6347
  /**
6288
6348
  * The axes to autoFit.
6289
6349
  *
@@ -6305,6 +6365,26 @@ declare global {
6305
6365
  */
6306
6366
  formulaView?: boolean;
6307
6367
  }
6368
+ /**
6369
+ * Options for {@link ICellRange.autoFit}.
6370
+ */
6371
+ interface AutoFitOptions extends BestFitOptions, ITransaction.OperationOptions {
6372
+ /**
6373
+ * Restricts autoFit to only grow, never shrink.
6374
+ *
6375
+ * @defaultValue false - adjust in both directions.
6376
+ *
6377
+ * @remarks
6378
+ * This is useful when adjusting fonts and wan the columns to resize but not the rows.
6379
+ */
6380
+ expandOnly?: boolean | IRange.Axis;
6381
+ /**
6382
+ * If this is set to `true` then any headers that have custom sizes will be ignored.
6383
+ *
6384
+ * @defaultValue false
6385
+ */
6386
+ skipCustomSize?: boolean;
6387
+ }
6308
6388
  /**
6309
6389
  * Options for {@link ICellRanges.getFlattened}.
6310
6390
  */
@@ -12500,6 +12580,7 @@ declare global {
12500
12580
  * | Category | Key methods |
12501
12581
  * |---|---|
12502
12582
  * | Evaluation lifecycle | `markVolatile`, `getRow`, `getColumn`, `getEntireCoords` |
12583
+ * | Argument reflection | `getArgs` |
12503
12584
  * | Output hints | `formatResults` |
12504
12585
  * | Range construction | `createRange`, `filterHidden` |
12505
12586
  * | Dynamic references | `getReference`, `getValueAt`, `getFormulaAt`, `getSpillAt` |
@@ -12954,6 +13035,22 @@ declare global {
12954
13035
  * Mostly for diagnostics; the built-in `INFO` function is built on it.
12955
13036
  */
12956
13037
  getRuntime(): IRuntime;
13038
+ /**
13039
+ * The current call's arguments — how many the caller supplied, in which positions, of what kind,
13040
+ * and what each value was BEFORE coercion.
13041
+ *
13042
+ * @remarks
13043
+ * Argument REFLECTION, for the one thing a declaration cannot say: what happened at *this* call.
13044
+ * It exists to serve Excel's declared `any`, which means "no contract, the body decides" — every
13045
+ * other Excel type maps onto a type we already have and the marshaller handles it.
13046
+ *
13047
+ * The returned view is valid ONLY while the body that asked for it is running. Views are pooled
13048
+ * by dispatch depth, so retaining one across an `await`, a callback, or a nested evaluation hands
13049
+ * you another call's arguments.
13050
+ *
13051
+ * @throws If called outside a function dispatch — there are no arguments to reflect on.
13052
+ */
13053
+ getArgs(): IFunctionArguments;
12957
13054
  }
12958
13055
 
12959
13056
  /**
@@ -13615,9 +13712,164 @@ declare global {
13615
13712
  * @defaultValue false
13616
13713
  */
13617
13714
  cancelable?: boolean;
13715
+ /**
13716
+ * Named engine behaviors this function opts into, from the `@compat` doc tag.
13717
+ *
13718
+ * @remarks
13719
+ * Canonical spellings, deduplicated and in declaration order; a family alias the author wrote
13720
+ * (`@compat atp`) has already been expanded to its atomic features. An unknown name never
13721
+ * reaches here — the linter fails the build on one, deliberately: a compatibility rule that
13722
+ * silently no-ops is the worst failure available, because the fixtures keep passing and the
13723
+ * behavior is simply absent.
13724
+ *
13725
+ * @see {@link CompatFeatures}
13726
+ * @see {@link CompatAliases}
13727
+ */
13728
+ compat?: CompatFeature[];
13618
13729
  }
13619
13730
  }
13620
13731
 
13732
+ /**
13733
+ * The current call's arguments, as the body sees them — argument **reflection**.
13734
+ *
13735
+ * @remarks
13736
+ * A declaration answers "what does this parameter mean?", and the marshaller coerces every
13737
+ * argument to it. That is right for the overwhelming majority of parameters. What it cannot answer
13738
+ * is anything about *this particular call*:
13739
+ *
13740
+ * - What did the caller write there — a value, a cell, an array, nothing at all?
13741
+ * - What was the value BEFORE the parameter's type coerced it?
13742
+ * - Was that empty slot an omission, or a blank cell the caller pointed at?
13743
+ *
13744
+ * Excel has measured behaviors that turn on exactly those questions, and they cluster at the
13745
+ * parameters Excel declares `any` — where there is nothing to express as a type, because Excel
13746
+ * declined to express one. `CUMIPMT` refuses a boolean at every slot while `FV` coerces one at
13747
+ * every slot; the difference is not the values, it is that `CUMIPMT`'s slots are `any` and its
13748
+ * body decides.
13749
+ *
13750
+ * It is REFLECTION and nothing else — two questions, and no third:
13751
+ *
13752
+ * | question | answers |
13753
+ * | --- | --- |
13754
+ * | *what was supplied* | {@link getLength}, {@link isOmittedAt} |
13755
+ * | *what it is* | {@link getRawAt} |
13756
+ *
13757
+ * Coercion is deliberately absent. A body that wants a number calls
13758
+ * {@link IFormulaContext.parseAsNumber} on the value this hands it — that already takes a value,
13759
+ * already propagates an incoming error with its identity, and already answers `#VALUE!` for a
13760
+ * boolean, which IS the rule an `any` slot needs. There is no coercion an index can do that a
13761
+ * value cannot.
13762
+ *
13763
+ * Obtained from `FormulaContext.getArgs()`, and valid ONLY while the body that asked for it is
13764
+ * running. A body must not retain it across an `await`, a callback, or a nested evaluation — the
13765
+ * views are pooled by dispatch depth, so the one at your depth is handed to the next call at that
13766
+ * depth.
13767
+ *
13768
+ * ```typescript
13769
+ * export function CUMIPMT(rate: Scalar, nper: Scalar): number {
13770
+ * const args = FormulaContext.getArgs();
13771
+ * if (args.isOmittedAt(0)) return FormulaError.BuiltIn.NA; // `CUMIPMT(,120,…)` is #N/A
13772
+ * const asNumber = FormulaContext.parseAsNumber(args.getRawAt(0) as Scalar); // TRUE is #VALUE!
13773
+ * // …
13774
+ * }
13775
+ * ```
13776
+ *
13777
+ * @see {@link IFormulaContext.getArgs}
13778
+ */
13779
+ export interface IFunctionArguments {
13780
+ /**
13781
+ * How many PARAMETERS the caller supplied.
13782
+ *
13783
+ * @remarks
13784
+ * Indexing throughout matches the BODY's own signature, never the raw call positions
13785
+ * (see {@link getRawAt}). `index >= getLength()` is an ABSENT argument — one the caller never
13786
+ * wrote, whose own JS initializer supplied the value. That is a different thing from an OMITTED
13787
+ * one; see {@link isOmittedAt}.
13788
+ */
13789
+ getLength(): number;
13790
+ /**
13791
+ * This invocation's value at `index`, uncoerced.
13792
+ *
13793
+ * @param index The PARAMETER index — the position in the body's own signature.
13794
+ *
13795
+ * @remarks
13796
+ * A `Scalar` means the argument IS a value; an {@link IRange} means a container whose members
13797
+ * the body reads itself (a reference materializes on demand); a {@link Lambda} means a callable.
13798
+ *
13799
+ * At a REPEATING parameter (`Repeating<T>`, or a rest parameter) this is the ARRAY of that
13800
+ * group's values — one entry per argument the caller wrote — and {@link getRepeatingArgsAt} is the
13801
+ * per-argument view of the same thing. Indexing by parameter rather than by call position is
13802
+ * what lets a body ask its questions in the vocabulary it declared:
13803
+ *
13804
+ * ```typescript
13805
+ * // IFS(condition: Repeating<[logicalTest: boolean, logicalTrue: AcceptError<any>]>)
13806
+ * args.getRawAt(0); // [test1, value1, test2, value2, …]
13807
+ * args.getRepeatingArgsAt(0).getRawAt(0); // test1
13808
+ * ```
13809
+ *
13810
+ * **Under a lift** the call broadcasts and the body runs once per output cell; this reports the
13811
+ * value for THIS invocation — the member at the current broadcast position for a lifted argument,
13812
+ * the whole argument for one that is not lifting. That is what every identified caller wants: a
13813
+ * body checking for a boolean must see the member, not the array around it.
13814
+ *
13815
+ * One boundary is worth knowing: under a lift at a parameter that declares a CONCRETE scalar
13816
+ * type, the member has already been converted by that declaration, so the raw and coerced answers
13817
+ * coincide there. At the `Scalar` slots this interface exists for, nothing coerces and the value
13818
+ * is the member exactly as the caller's range holds it.
13819
+ */
13820
+ getRawAt(index: number): IFunctionArguments.RawArg | IFunctionArguments.RawArg[];
13821
+ /**
13822
+ * A nested view over one REPEATING parameter's arguments, so a body can ask the same questions
13823
+ * one level down — `getRepeatingArgsAt(0).isOmittedAt(1)`.
13824
+ *
13825
+ * @param index The PARAMETER index of the repeating group (or rest parameter).
13826
+ *
13827
+ * @remarks
13828
+ * The nested view is FLAT over the group's arguments: a period-2 group repeated three times reads
13829
+ * as six, in call order. That is where "which one was omitted" has an answer at all — the group
13830
+ * as a whole cannot be omitted, only one of its arguments can.
13831
+ *
13832
+ * At a non-repeating parameter this returns an EMPTY view (`getLength()` of 0) rather than
13833
+ * throwing, so a body that folds over its groups needs no special case.
13834
+ */
13835
+ getRepeatingArgsAt(index: number): IFunctionArguments;
13836
+ /**
13837
+ * True for an OMITTED argument — one the caller supplied EMPTY, as the trailing comma of
13838
+ * `CUMIPMT(,120,100000,13,24,0)`.
13839
+ *
13840
+ * @remarks
13841
+ * There are THREE positions, not two, and Excel answers each differently:
13842
+ *
13843
+ * | form | position | Excel |
13844
+ * | --- | --- | --- |
13845
+ * | `CUMIPMT(0.01,120,100000,13,24)` | ABSENT | refused at entry |
13846
+ * | `CUMIPMT(,120,100000,13,24,0)` | **OMITTED** | `#N/A` |
13847
+ * | `CUMIPMT(0.01,120,100000,13,24,E1)`, E1 blank | a blank CELL | the ordinary answer |
13848
+ *
13849
+ * This separates the middle row from the other two. ABSENT is `index >= getLength()`; a blank
13850
+ * cell is an ordinary value the caller pointed at and is not omitted.
13851
+ */
13852
+ isOmittedAt(index: number): boolean;
13853
+ }
13854
+
13855
+ /**
13856
+ * {@inheritDoc IFunctionArguments}
13857
+ *
13858
+ * @see
13859
+ * ### **Interface**
13860
+ *
13861
+ * {@link IFunctionArguments}
13862
+ */
13863
+ export namespace IFunctionArguments {
13864
+ /**
13865
+ * One argument as the caller wrote it — a value, a container, a callable, or nothing.
13866
+ *
13867
+ * @remarks
13868
+ * `undefined` is an OMITTED or ABSENT argument; `null` is a blank, which is a value.
13869
+ */
13870
+ type RawArg = Scalar | IRange | Lambda | undefined;
13871
+ }
13872
+
13621
13873
  /**
13622
13874
  * Collections of {@link IFunction}.
13623
13875
  */
@@ -17052,11 +17304,28 @@ declare global {
17052
17304
  */
17053
17305
  filter<TS extends Scalar = T>(callbackFn: (value: TS, coords: IRange.CellCoords, range: IRange<T>) => MaybeError<boolean>, options?: IRange.TraversalAxis | IRange.IteratorOptions): this;
17054
17306
  /**
17055
- * Returns the values as an array of values.
17307
+ * Linearizes the range into a single column by flattened ordinal — the member at ordinal `i`
17308
+ * lands at row `i`, column 0.
17056
17309
  *
17057
17310
  * @param options {@link IRange.IteratorOptions}
17311
+ *
17312
+ * @remarks
17313
+ * `options.axis` is the TRAVERSAL order feeding the ordinal, not the orientation of the result:
17314
+ * the result is always Nx1, and a row is `flatten().transpose()`. The default is row-major,
17315
+ * which is the order Excel pairs two range operands in.
17316
+ *
17317
+ * `options.type` carries a read type onto the result, so a member the type filters out becomes
17318
+ * a HOLE at its ordinal rather than compacting the column. That is what lets two operands be
17319
+ * paired position by position — see {@link alignValues}.
17320
+ *
17321
+ * `options.includeEmpty` has no meaning here and is ignored: a range is inherently sparse, so
17322
+ * an empty position is simply absent. Sparsity is preserved and nothing is materialized.
17323
+ *
17324
+ * Equivalent to `reshape(rows * columns, 1)`, which is where the ordinal rule is stated.
17325
+ *
17326
+ * @see {@link getValuesFlat} for the same linearization as a JS array.
17058
17327
  */
17059
- flatten<TS extends Scalar = T>(options?: IRange.IteratorOptions): TS[];
17328
+ flatten(options?: IRange.IteratorOptions): this;
17060
17329
  /**
17061
17330
  * Iterates over the cells within the range, applying the provided callback function to each cell.
17062
17331
  *
@@ -17134,6 +17403,16 @@ declare global {
17134
17403
  * @param options {@link IRange.ValueOptions}
17135
17404
  */
17136
17405
  getValueAt<TS extends Scalar = T>(rowIndex: number, colIndex: number, options?: IRange.ValueOptions): TS;
17406
+ /**
17407
+ * Returns the members as a flat JS array.
17408
+ *
17409
+ * @param options {@link IRange.IteratorOptions}
17410
+ *
17411
+ * @remarks
17412
+ * The array form of {@link flatten}. Skips empty members unless `includeEmpty` is set, where
17413
+ * `flatten` keeps them as holes in a range.
17414
+ */
17415
+ getValuesFlat<TS extends Scalar = T>(options?: IRange.IteratorOptions): TS[];
17137
17416
  /**
17138
17417
  * Returns the number of columns of the range.
17139
17418
  */
@@ -28563,6 +28842,7 @@ declare global {
28563
28842
  c?: boolean;
28564
28843
  p?: MinifiedParameter[];
28565
28844
  r?: MinifiedValue;
28845
+ x?: CompatFeature[];
28566
28846
  }
28567
28847
 
28568
28848
  export type MinifiedDeclarations = {
@@ -29473,6 +29753,28 @@ declare global {
29473
29753
  */
29474
29754
  export type Repeating<T> = T[];
29475
29755
 
29756
+ /**
29757
+ * Resolves a `@compat` feature name, CASE-INSENSITIVELY, to its canonical spelling.
29758
+ *
29759
+ * @param name The name as the author wrote it.
29760
+ * @returns The canonical name, or `null` when no feature goes by it.
29761
+ *
29762
+ * @remarks
29763
+ * Authors write `@compat noReferenceLift`; `@Compat NOREFERENCELIFT` means the same thing. Only
29764
+ * the canonical spelling is ever stored, so the declaration a build emits does not depend on how
29765
+ * the tag was typed.
29766
+ */
29767
+ export function resolveCompatFeature(name: string): CompatFeature | null;
29768
+
29769
+ /**
29770
+ * Resolves a `@compat` name — a feature or a family alias — CASE-INSENSITIVELY, to the canonical
29771
+ * features it turns on.
29772
+ *
29773
+ * @param name The name as the author wrote it.
29774
+ * @returns The canonical features, or `null` when no feature or alias goes by that name.
29775
+ */
29776
+ export function resolveCompatFeatures(name: string): readonly CompatFeature[] | null;
29777
+
29476
29778
  /**
29477
29779
  * Flatten a locale's declared `inherit` into a concrete lookup order, most-specific first.
29478
29780
  *
@@ -38,6 +38,11 @@ export declare class Calculation extends AbstractModel<State, ICalculation.JSON,
38
38
  protected _getRanges: (address: ICellRanges.Address, options?: ICellRange.GetRangeOptions) => ICellRanges;
39
39
  protected _calcExecutor: CalcExecutor;
40
40
  protected _cycleId: number;
41
+ protected _staticEpoch: number;
42
+ protected _precedentEpoch: number;
43
+ protected _epochSeenFormulas: ISpatialMap3D<FormulaSpatialValue> | null;
44
+ protected _epochSeenPrecedents: ISpatialMap3D<PrecedentSpatialValue> | null;
45
+ protected _epochSeenDynamics: Map<FormulaSpatialValue, ISpatialMap.Entry<PrecedentSpatialValue>[]> | null;
41
46
  protected _volatiles: Set<CalcNode>;
42
47
  protected _calculatedCommitId: string | null;
43
48
  protected _pendingActions: (() => Promise<unknown>)[] | null;
@@ -17,7 +17,9 @@ export declare class CalcExecutor {
17
17
  protected _container: IContextContainer;
18
18
  protected _context: IFormulaContext;
19
19
  private readonly _requestCalculate;
20
- 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;
21
23
  /**
22
24
  * The last pass's circular roster: every node it evaluated by demand (a `doMarkAndPlan` that
23
25
  * could not order, or the iteration guard's hand-over) plus every node whose dynamic read
@@ -90,6 +92,54 @@ export declare class CalcExecutor {
90
92
  evaluations: number;
91
93
  retries: number;
92
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>;
93
143
  _gridUpdates: Map<string, ICalcGrid.IncrementalUpdates> | null;
94
144
  _addDynamicPrecedent: ((node: CalcNode, range: IRange.Coords | undefined, kind: PrecedentKind) => void) | null;
95
145
  constructor(container: IContextContainer, context: IFormulaContext, requestCalculate: () => void);
@@ -221,11 +271,17 @@ export declare class CalcExecutor {
221
271
  * carrying the {@link CycleRole} its reads gave it. Describes the last pass only.
222
272
  */
223
273
  getCircularNodes(): readonly CalcNode[];
224
- /** @internal Demand-pass counters since construction: evaluations and `NeedsFirst` retries. */
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
+ */
225
279
  getDemandStats(): {
226
280
  evaluations: number;
227
281
  retries: number;
228
- };
282
+ } & CalcExecutor.PlanStats;
283
+ /** @internal Zeroes every counter/timing so a measurement window starts clean (`__sxlCalc.reset()`). */
284
+ resetStats(): void;
229
285
  /**
230
286
  * Collects and marks all volatile CalcNodes.
231
287
  *
@@ -253,6 +309,17 @@ export declare class CalcExecutor {
253
309
  * Uses Kahn's algorithm.
254
310
  */
255
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;
256
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;
257
324
  }
258
325
  export declare namespace CalcExecutor {
@@ -296,6 +363,48 @@ export declare namespace CalcExecutor {
296
363
  /** Volatiles accumulated before this cycle — drained and cleared by execute() */
297
364
  oldVolatiles: Set<CalcNode>;
298
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
+ }
299
408
  /** Updated spatial maps and committed grid batches returned to Calculation */
300
409
  interface ExecuteResult {
301
410
  precedentSpatial: ISpatialMap3D<PrecedentSpatialValue>;
@@ -63,6 +63,26 @@ export interface CalcNode {
63
63
  * {@link cycleGroup}; the role it shows is the `CalcState.ShownRoleMask` field of {@link state}.
64
64
  */
65
65
  shownGroup: number;
66
+ /**
67
+ * Position of this node in the last FULLY-ORDERED plan that stamped it (CALC-PERFORMANCE-PLAN.md
68
+ * §1.2). Meaningful only when {@link planId} and {@link planEpoch} match the executor's current
69
+ * plan id and the container's static epoch; `-1` until first stamped.
70
+ */
71
+ orderIndex: number;
72
+ /**
73
+ * Id of the Kahn pass that stamped {@link orderIndex}; `0` = never stamped. Stamps from
74
+ * different passes are NOT mutually comparable — the fast path requires every affected node to
75
+ * carry the executor's `_lastPlanId`.
76
+ */
77
+ planId: number;
78
+ /** The container's `staticEpoch` when {@link orderIndex} was stamped; stale under a newer epoch. */
79
+ planEpoch: number;
80
+ /**
81
+ * The container's `precedentEpoch` when {@link dependents} was last computed
82
+ * (CALC-PERFORMANCE-PLAN.md §1.3). Discovery reuses the cached array while this matches;
83
+ * `0` = never computed.
84
+ */
85
+ dependentsEpoch: number;
66
86
  /**
67
87
  * Bit mask, laid out by `CalcState`: the pending flags, `Skip`, `Circular`, and the
68
88
  * `DemandState`/`CycleRole` fields of the current pass.