@sheetxl/primitives 0.8.0-beta.14 → 0.8.0-beta.16

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/LICENSE.md CHANGED
@@ -1,11 +1,21 @@
1
- # Repository Licensing
1
+ MIT License
2
2
 
3
- SheetXL contains a mix of open-source and commercially licensed packages. Each package is individually licensed and contains its own license file.
3
+ Copyright (c) 2026-present SheetXL Inc. & Michael T. Ford
4
4
 
5
- For the specific terms and conditions applicable to a package, please see the `LICENSE.md` file located in that package's root directory.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
6
11
 
7
- Generally, packages intended for public distribution (such as CLIs, adapters, and vanilla wrappers) are licensed under the MIT license. All other core packages are proprietary and require a commercial license for use.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
8
14
 
9
- For more details on commercial licensing, please visit our website:
10
-
11
- https://www.sheetxl.com/docs/guides/licensing-pricing
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @license @sheetxl/primitives - Primitives - Base types for SheetXL functions. - v0.8.0-beta.14
2
+ * @license @sheetxl/primitives - Primitives - Base types for SheetXL functions. - v0.8.0-beta.16
3
3
  *
4
4
  * (C) 2026-present SheetXL Inc. & Michael T. Ford
5
- * License: The license can be found at https://www.sheetxl.com/license.
5
+ * License: MIT
6
6
  */
7
7
  var e;
8
8
  (function(e) {
@@ -492,8 +492,11 @@ var v = class {
492
492
  createCriteria(e, t) {
493
493
  throw x("createCriteria");
494
494
  }
495
- createGrouping(e, t) {
496
- throw x("createGrouping");
495
+ createPivot(e, t, n) {
496
+ throw x("createPivot");
497
+ }
498
+ getPivotAt(e) {
499
+ return null;
497
500
  }
498
501
  parseAsLiteral(e) {
499
502
  let t = e.toUpperCase();
@@ -576,8 +579,11 @@ var v = class {
576
579
  createCriteria(e, t) {
577
580
  return this._getScope().createCriteria(e, t);
578
581
  }
579
- createGrouping(e, t) {
580
- return this._getScope().createGrouping(e, t);
582
+ createPivot(e, t, n) {
583
+ return this._getScope().createPivot(e, t, n);
584
+ }
585
+ getPivotAt(e) {
586
+ return this._getScope().getPivotAt?.(e) ?? null;
581
587
  }
582
588
  parseAsLiteral(e) {
583
589
  let t = this._getScope();
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @license @sheetxl/primitives - Primitives - Base types for SheetXL functions. - v0.8.0-beta.14
2
+ * @license @sheetxl/primitives - Primitives - Base types for SheetXL functions. - v0.8.0-beta.16
3
3
  *
4
4
  * (C) 2026-present SheetXL Inc. & Michael T. Ford
5
- * License: The license can be found at https://www.sheetxl.com/license.
5
+ * License: MIT
6
6
  */
7
7
  var e;
8
8
  (function(e) {
@@ -314,7 +314,7 @@ var t, r, o, n, s = {
314
314
  }
315
315
  }
316
316
  e.Field = f;
317
- class b extends o {
317
+ class A extends o {
318
318
  constructor(e, o) {
319
319
  super(t.Calc, r.Calc, e, o);
320
320
  }
@@ -322,8 +322,8 @@ var t, r, o, n, s = {
322
322
  this.code = n.Code.Calc;
323
323
  }
324
324
  }
325
- e.Calc = b;
326
- class A extends o {
325
+ e.Calc = A;
326
+ class b extends o {
327
327
  constructor(e, o) {
328
328
  super(t.Busy, r.Busy, e, o);
329
329
  }
@@ -331,7 +331,7 @@ var t, r, o, n, s = {
331
331
  this.code = n.Code.Busy;
332
332
  }
333
333
  }
334
- e.Busy = A;
334
+ e.Busy = b;
335
335
  class N extends o {
336
336
  constructor(e, o) {
337
337
  super(t.Python, r.Python, e, o);
@@ -406,7 +406,7 @@ var f = class {
406
406
  "function" == typeof n && n();
407
407
  }, o;
408
408
  }
409
- }, b = class {
409
+ }, A = class {
410
410
  createRange(e, t, r) {
411
411
  throw N("createRange");
412
412
  }
@@ -497,8 +497,11 @@ var f = class {
497
497
  createCriteria(e, t) {
498
498
  throw N("createCriteria");
499
499
  }
500
- createGrouping(e, t) {
501
- throw N("createGrouping");
500
+ createPivot(e, t, r) {
501
+ throw N("createPivot");
502
+ }
503
+ getPivotAt(e) {
504
+ return null;
502
505
  }
503
506
  parseAsLiteral(e) {
504
507
  const t = e.toUpperCase();
@@ -509,7 +512,7 @@ var f = class {
509
512
  }
510
513
  compare(e, t, r) {
511
514
  if (e === t) return 0;
512
- const o = A(e), n = A(t);
515
+ const o = b(e), n = b(t);
513
516
  return o !== n ? o < n ? -1 : 1 : e < t ? -1 : e > t ? 1 : 0;
514
517
  }
515
518
  getRuntime() {
@@ -521,7 +524,7 @@ var f = class {
521
524
  createRichData(e, t) {
522
525
  throw new w(y);
523
526
  }
524
- }, A = (e) => {
527
+ }, b = (e) => {
525
528
  if (null == e) return 0;
526
529
  switch (typeof e) {
527
530
  case "number": return 1;
@@ -540,8 +543,8 @@ var f = class {
540
543
  rowEnd: Math.pow(2, 20) - 1
541
544
  }), R = {
542
545
  isDate1904: () => !1,
543
- getLocale: () => k.tag,
544
- getLocaleProfile: () => k,
546
+ getLocale: () => T.tag,
547
+ getLocaleProfile: () => T,
545
548
  isR1C1: () => !1,
546
549
  getNow: () => 0,
547
550
  getRandom: () => Math.random(),
@@ -550,7 +553,7 @@ var f = class {
550
553
  getOS: () => "unknown",
551
554
  getUser: () => "unidentified",
552
555
  getDescription: () => "Stubbed Formula Context"
553
- }, x = 2958465, F = new b(), T = class {
556
+ }, x = 2958465, F = new A(), P = class {
554
557
  constructor() {
555
558
  this._scope = F, this._argsFn = null;
556
559
  }
@@ -581,8 +584,11 @@ var f = class {
581
584
  createCriteria(e, t) {
582
585
  return this._getScope().createCriteria(e, t);
583
586
  }
584
- createGrouping(e, t) {
585
- return this._getScope().createGrouping(e, t);
587
+ createPivot(e, t, r) {
588
+ return this._getScope().createPivot(e, t, r);
589
+ }
590
+ getPivotAt(e) {
591
+ return this._getScope().getPivotAt?.(e) ?? null;
586
592
  }
587
593
  parseAsLiteral(e) {
588
594
  const t = this._getScope();
@@ -696,7 +702,7 @@ var f = class {
696
702
  toString() {
697
703
  return this._getScope()?.toString?.() ?? "{STUB SCOPE}";
698
704
  }
699
- }, k = Object.freeze({
705
+ }, T = Object.freeze({
700
706
  tag: "en-US",
701
707
  numberingSystem: "latn",
702
708
  decimal: ".",
@@ -776,5 +782,5 @@ var f = class {
776
782
  sensitivity: "base",
777
783
  numeric: !0
778
784
  })
779
- }), P = new T();
780
- export { k as DEFAULT_LOCALE_PROFILE, P as FormulaContext, n as FormulaError, t as IRange, o as IRangeStats, a as InteropScalarType, f as Observable, e as Scalar, s as ScalarType, T as ScopedFormulaContext, b as StubbedFormulaContext, i as ValueKind, l as WildCard, d as isLazyValue };
785
+ }), k = new P();
786
+ export { T as DEFAULT_LOCALE_PROFILE, k as FormulaContext, n as FormulaError, t as IRange, o as IRangeStats, a as InteropScalarType, f as Observable, e as Scalar, s as ScalarType, P as ScopedFormulaContext, A as StubbedFormulaContext, i as ValueKind, l as WildCard, d as isLazyValue };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@sheetxl/primitives",
3
3
  "description": "Primitives - Base types for SheetXL functions.",
4
- "version": "0.8.0-beta.14",
5
- "license": "SEE LICENSE IN <LICENSE>",
4
+ "version": "0.8.0-beta.16",
5
+ "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": {
@@ -94,12 +94,16 @@ export declare class StubbedFormulaContext implements IFormulaContext {
94
94
  */
95
95
  createCriteria(criteria: Scalar, options?: IFormulaContext.CriteriaOptions): IFormulaContext.CriteriaEvaluator;
96
96
  /**
97
- * Unimplemented. See {@link IFormulaContext.createGrouping}.
97
+ * Unimplemented. See {@link IFormulaContext.createPivot}.
98
98
  *
99
99
  * @remarks
100
- * Grouping needs the engine's key equality, which this stub does not have.
100
+ * A pivot needs the engine's key equality, which this stub does not have.
101
101
  */
102
- createGrouping(keys: IRange, options?: IFormulaContext.GroupingOptions): IFormulaContext.Grouping;
102
+ createPivot(rows: IRange | null, columns: IRange | null, options?: IFormulaContext.PivotOptions): IFormulaContext.Pivot;
103
+ /**
104
+ * No pivot tables. See {@link IFormulaContext.getPivotAt}.
105
+ */
106
+ getPivotAt(_reference: IReferenceRange): IFormulaContext.PivotView | null;
103
107
  /**
104
108
  * Parses the canonical English boolean and error literals.
105
109
  * See {@link IFormulaContext.parseAsLiteral}.
@@ -190,8 +194,10 @@ export declare class ScopedFormulaContext implements IFormulaContext {
190
194
  parseAsNumber(textOrNumber: Scalar | undefined, options?: IFormulaContext.ParseNumberOptions): number | undefined;
191
195
  /** @inheritdoc IFormulaContext.createCriteria */
192
196
  createCriteria(criteria: Scalar, options?: IFormulaContext.CriteriaOptions): IFormulaContext.CriteriaEvaluator;
193
- /** @inheritdoc IFormulaContext.createGrouping */
194
- createGrouping(keys: IRange, options?: IFormulaContext.GroupingOptions): IFormulaContext.Grouping;
197
+ /** @inheritdoc IFormulaContext.createPivot */
198
+ createPivot(rows: IRange | null, columns: IRange | null, options?: IFormulaContext.PivotOptions): IFormulaContext.Pivot;
199
+ /** @inheritdoc IFormulaContext.getPivotAt */
200
+ getPivotAt(reference: IReferenceRange): IFormulaContext.PivotView | null;
195
201
  /** @inheritdoc IFormulaContext.parseAsLiteral */
196
202
  parseAsLiteral(text: string): boolean | FormulaError.Known | undefined;
197
203
  /** @inheritdoc IFormulaContext.toText */
@@ -39,7 +39,7 @@ import { IRichData } from './IRichData';
39
39
  * | Dates | `getNow`, `toSerialDate`, `fromSerialDate`, `getDateParts`, `getSerialDate`, `parseAsDate`, `parseAsSerialDate`, `isValidDate` |
40
40
  * | Coercion | `parseAsNumber`, `parseAsLiteral`, `toText`, `compare` |
41
41
  * | Criteria | `createCriteria` |
42
- * | Grouping | `createGrouping` |
42
+ * | Grouping | `createPivot`, `getPivotAt` |
43
43
  * | Sheet introspection | `getSheetCount`, `getSheetIndex`, `getAddress`, `getNumberFormat` |
44
44
  *
45
45
  * ### Implementors
@@ -351,32 +351,50 @@ export interface IFormulaContext {
351
351
  */
352
352
  createCriteria(criteria: Scalar, options?: IFormulaContext.CriteriaOptions): IFormulaContext.CriteriaEvaluator;
353
353
  /**
354
- * Groups the records of a range — its rows — by their values.
354
+ * Groups the records of a range two ways at once — by row keys and by column keys — at every
355
+ * key level of each, and crosses every row group with every column group.
355
356
  *
356
- * @param keys The key columns. Each row is one record, and records with equal values in every
357
- * column form one group.
358
- * @param options Which records take part. See {@link IFormulaContext.GroupingOptions}.
359
- * @returns The groups, numbered in the order they first appear.
357
+ * @param rows The row key columns, or `null` for none: every record is then in the one row group.
358
+ * @param columns The column key columns, or `null` for none.
359
+ * @param options Which records take part. See {@link IFormulaContext.PivotOptions}.
360
+ * @returns The two axes of groups and what their crossings hold.
361
+ * @throws `#VALUE!` for more than 32 key columns on one axis, or more groups and crossings than
362
+ * the pivot can number.
360
363
  *
361
364
  * @remarks
362
- * Two keys are equal by the rule `UNIQUE` uses: text ignores case, and types never mix, so `1`,
363
- * `"1"` and `TRUE` are three groups. A blank key is a key of its own, distinct from `""`, and an
364
- * error is a key like any other value. Every row is a record, blank rows included.
365
+ * Every row is a record, blank rows included. Two keys are equal by the rule `UNIQUE` uses: text
366
+ * ignores case, and types never mix, so `1`, `"1"` and `TRUE` are three groups. A blank key is a
367
+ * key of its own, distinct from `""`, and an error is a key like any other value.
365
368
  *
366
- * Group ONCE and ask the grouping for each group's members. The grouping does the work a
367
- * per-group scan would repeat, so it stays proportional to the data rather than to the data
368
- * times the number of groups.
369
+ * Each axis is a tree: its root is every kept record, the root's children are the groups of the
370
+ * first key column, theirs add the second, and so on, each in the order its first record appears.
371
+ * A subtotal is then one group, never a union of its children.
372
+ *
373
+ * One pivot answers what a grouping per key level, and another per pair of levels, would — in
374
+ * one pass, which is what `PIVOTBY` and a pivot table need.
369
375
  *
370
376
  * @example
371
377
  * ```typescript
372
- * const grouping = FormulaContext.createGrouping(regions);
373
- * for (let g = 0; g < grouping.getGroupCount(); g++) {
374
- * const [region] = grouping.getKeys(g);
375
- * const sales = grouping.getMembers(amounts, [g]);
378
+ * const pivot = FormulaContext.createPivot(regions, years);
379
+ * for (const region of pivot.getRows().getRoot().getChildren()) {
380
+ * const total = pivot.getMembers(sales, region); // every year
381
+ * const [name] = region.getKeys();
376
382
  * }
377
383
  * ```
378
384
  */
379
- createGrouping(keys: IRange, options?: IFormulaContext.GroupingOptions): IFormulaContext.Grouping;
385
+ createPivot(rows: IRange | null, columns: IRange | null, options?: IFormulaContext.PivotOptions): IFormulaContext.Pivot;
386
+ /**
387
+ * The pivot table a reference touches, as the values it shows.
388
+ *
389
+ * @param reference Any cell or range of the table, its page fields included.
390
+ * @returns The view, or `null` when the reference touches no pivot table, or the table's values
391
+ * are not computed (it is not modeled, or its view has not loaded yet).
392
+ *
393
+ * @remarks
394
+ * What `GETPIVOTDATA` reads. The view answers for the values the table shows; matching a request
395
+ * to its fields and items by name is the caller's.
396
+ */
397
+ getPivotAt(reference: IReferenceRange): IFormulaContext.PivotView | null;
380
398
  /**
381
399
  * Reads a typed literal in the workbook's language — the parse side of {@link toText}.
382
400
  *
@@ -724,9 +742,9 @@ export declare namespace IFormulaContext {
724
742
  anchor?: 'whole' | 'start';
725
743
  }
726
744
  /**
727
- * Options for {@link IFormulaContext.createGrouping}.
745
+ * Options for {@link IFormulaContext.createPivot}.
728
746
  */
729
- interface GroupingOptions {
747
+ interface PivotOptions {
730
748
  /**
731
749
  * One column, as tall as the keys, selecting the records that take part. A record is kept
732
750
  * when its member is `TRUE` or a nonzero number, and left out otherwise.
@@ -734,48 +752,148 @@ export declare namespace IFormulaContext {
734
752
  filter?: IRange;
735
753
  }
736
754
  /**
737
- * The records of a range divided into groups — see {@link IFormulaContext.createGrouping}.
755
+ * The records of a range grouped by rows and by columns — see {@link IFormulaContext.createPivot}.
738
756
  */
739
- interface Grouping {
757
+ interface Pivot {
740
758
  /**
741
759
  * The number of records kept by the filter.
742
760
  */
743
761
  getRecordCount(): number;
744
762
  /**
745
- * The number of groups.
763
+ * The row groups.
746
764
  */
747
- getGroupCount(): number;
765
+ getRows(): PivotAxis;
748
766
  /**
749
- * The number of records in a group.
767
+ * The column groups.
750
768
  */
751
- getSize(group: number): number;
769
+ getColumns(): PivotAxis;
752
770
  /**
753
- * A group's key, one value per key column, as spelled by the group's first record. A blank
754
- * key is `null`.
771
+ * The members of `values` in both a row group and a column group, as one column in record order.
772
+ *
773
+ * @param values One column, as tall as the keys.
774
+ * @param row A group of this pivot's rows; omit it, or pass the root, for every row.
775
+ * @param column A group of this pivot's columns; omit it, or pass the root, for every column.
776
+ * @returns An array, never a reference, or `null` when no kept record is in both groups.
755
777
  */
756
- getKeys(group: number): readonly Scalar[];
778
+ getMembers(values: IRange, row?: PivotGroup, column?: PivotGroup): IRange | null;
757
779
  /**
758
- * The group a record belongs to, or `-1` when the filter left it out.
780
+ * What `fn` returns over the members {@link getMembers} would give, computed without them.
781
+ *
782
+ * @returns `null` when no kept record is in both groups, `undefined` when the pivot does not
783
+ * compute `fn` this way: call `fn` over {@link getMembers} instead.
759
784
  *
760
- * @param record The record's row within the keys.
785
+ * @remarks
786
+ * Optional. Where it is present it answers for the built-in aggregates it knows (`SUM`,
787
+ * `COUNT`, `AVERAGE`, ...) with exactly the value the call would return, the first error
788
+ * member included.
789
+ */
790
+ getAggregate?(values: IRange, fn: IFunction, row?: PivotGroup, column?: PivotGroup): Scalar | null | undefined;
791
+ }
792
+ /**
793
+ * A pivot table as the values it shows — see {@link IFormulaContext.getPivotAt}.
794
+ */
795
+ interface PivotView {
796
+ /**
797
+ * The table's values, in its order.
761
798
  */
762
- getGroup(record: number): number;
799
+ getDataFields(): readonly PivotViewDataField[];
763
800
  /**
764
- * The row of a group's first record within the keys.
801
+ * The fields on the table's rows, columns and pages, in that order.
802
+ */
803
+ getFields(): readonly PivotViewField[];
804
+ /**
805
+ * A value the table shows.
765
806
  *
766
- * @remarks
767
- * With {@link getGroup} on another grouping of the same records, this maps a group of one
768
- * grouping onto the group of another — a finer grouping's group onto its parent's.
807
+ * @param dataField The value, by index into {@link getDataFields}.
808
+ * @param items Per field, by index into {@link getFields}, the item by index into its
809
+ * {@link PivotViewField.items}. A field left out is totaled over.
810
+ * @returns The value, or `undefined` when the table does not show it: a line it does not show
811
+ * (a hidden or collapsed item, a subtotal or grand total turned off), an item not on its page,
812
+ * or a crossing with no records.
769
813
  */
770
- getFirstRecord(group: number): number;
814
+ getValue(dataField: number, items: ReadonlyMap<number, number>): Scalar | undefined;
771
815
  /**
772
- * The members of `values` that belong to some groups, as one column in record order.
816
+ * Whether the table's values are known here.
773
817
  *
774
- * @param values One column, as tall as the keys.
775
- * @param groups The groups to take members from. Omit it for every kept record.
776
- * @returns An array, never a reference. A blank member stays blank.
818
+ * @returns `false` for a table that is there but whose values the host does not compute: its
819
+ * fields and values are empty, and a caller that answered from it before keeps that answer, as
820
+ * the table only changes on a refresh. Absent means `true`.
821
+ */
822
+ isComputed?(): boolean;
823
+ }
824
+ /**
825
+ * A value of a {@link PivotView}.
826
+ */
827
+ interface PivotViewDataField {
828
+ /** As the table shows it (`Sum of Sales`). */
829
+ readonly name: string;
830
+ /** The name of the source field it summarizes (`Sales`). */
831
+ readonly sourceName: string;
832
+ }
833
+ /**
834
+ * A field of a {@link PivotView}.
835
+ */
836
+ interface PivotViewField {
837
+ /** As the table shows it: its caption, or the source field's name. */
838
+ readonly name: string;
839
+ readonly items: readonly PivotViewItem[];
840
+ }
841
+ /**
842
+ * An item of a {@link PivotViewField}.
843
+ */
844
+ interface PivotViewItem {
845
+ /** The item's value; `null` for the blank item. */
846
+ readonly value: Scalar;
847
+ /** The value as the table shows it, in the field's number format; `(blank)` for the blank item. */
848
+ readonly text: string;
849
+ /** The caption the item was renamed to, if it was. */
850
+ readonly name?: string;
851
+ }
852
+ /**
853
+ * One axis of a {@link Pivot}: its groups at every key level, as a tree under one root.
854
+ */
855
+ interface PivotAxis {
856
+ /**
857
+ * The group of every kept record, at level `0`.
858
+ */
859
+ getRoot(): PivotGroup;
860
+ /**
861
+ * The number of groups, the root included.
862
+ */
863
+ getGroupCount(): number;
864
+ }
865
+ /**
866
+ * One group of a {@link PivotAxis}. The axis answers the same object for a group every time, so a
867
+ * group can key a map.
868
+ */
869
+ interface PivotGroup {
870
+ /**
871
+ * The number of key columns the group is keyed by: `0` for the root, `1` for the groups of the
872
+ * first key column, and so on.
873
+ */
874
+ getLevel(): number;
875
+ /**
876
+ * The group one level up, or `null` for the root.
877
+ */
878
+ getParent(): PivotGroup | null;
879
+ /**
880
+ * The groups one level down, in the order their first records appear.
881
+ */
882
+ getChildren(): readonly PivotGroup[];
883
+ /**
884
+ * The keys, one per level down to this group's own. Each level's key is spelled by the first
885
+ * record of the group at that level, so a group's keys begin with its parent's. A blank key is
886
+ * `null`.
887
+ */
888
+ getKeys(): readonly Scalar[];
889
+ /**
890
+ * The number of kept records in the group.
891
+ */
892
+ getSize(): number;
893
+ /**
894
+ * The row of the group's first record within the keys.
777
895
  */
778
- getMembers(values: IRange, groups?: readonly number[]): IRange;
896
+ getFirstRecord(): number;
779
897
  }
780
898
  /**
781
899
  * A serial date split into fields — see {@link IFormulaContext.getDateParts}.