@lotics/cli 0.143.0 → 0.143.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/render_page.js +215 -63
- package/dist/src/cli.js +229 -66
- package/dist/src/client.d.ts +4 -0
- package/docs/building_an_app.md +18 -4
- package/docs/cli_reference.md +1 -1
- package/package.json +1 -1
package/dist/render_page.js
CHANGED
|
@@ -4563,7 +4563,7 @@ ${e.toString()}`);
|
|
|
4563
4563
|
}
|
|
4564
4564
|
};
|
|
4565
4565
|
var type2Number = { "boolean": 3, "string": 2, "number": 1 };
|
|
4566
|
-
var
|
|
4566
|
+
var Infix2 = {
|
|
4567
4567
|
compareOp: (value1, infix, value2, isArray1, isArray2) => {
|
|
4568
4568
|
if (value1 == null) value1 = 0;
|
|
4569
4569
|
if (value2 == null) value2 = 0;
|
|
@@ -4654,7 +4654,7 @@ ${e.toString()}`);
|
|
|
4654
4654
|
module2.exports = {
|
|
4655
4655
|
Prefix,
|
|
4656
4656
|
Postfix,
|
|
4657
|
-
Infix,
|
|
4657
|
+
Infix: Infix2,
|
|
4658
4658
|
Operators: {
|
|
4659
4659
|
compareOp: ["<", ">", "=", "<>", "<=", ">="],
|
|
4660
4660
|
concatOp: ["&"],
|
|
@@ -4669,7 +4669,7 @@ ${e.toString()}`);
|
|
|
4669
4669
|
"../../node_modules/fast-formula-parser/formulas/functions/math.js"(exports2, module2) {
|
|
4670
4670
|
var FormulaError = require_error();
|
|
4671
4671
|
var { FormulaHelpers, Types, Factorials, Criteria } = require_helpers();
|
|
4672
|
-
var { Infix } = require_operators();
|
|
4672
|
+
var { Infix: Infix2 } = require_operators();
|
|
4673
4673
|
var H = FormulaHelpers;
|
|
4674
4674
|
var f = [];
|
|
4675
4675
|
var fd2 = [];
|
|
@@ -5192,7 +5192,7 @@ ${e.toString()}`);
|
|
|
5192
5192
|
if (criteria.match === criteria.value.test(value2)) {
|
|
5193
5193
|
sum += valueToAdd;
|
|
5194
5194
|
}
|
|
5195
|
-
} else if (
|
|
5195
|
+
} else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
|
|
5196
5196
|
sum += valueToAdd;
|
|
5197
5197
|
}
|
|
5198
5198
|
});
|
|
@@ -11472,7 +11472,7 @@ ${e.toString()}`);
|
|
|
11472
11472
|
"../../node_modules/fast-formula-parser/formulas/functions/statistical.js"(exports2, module2) {
|
|
11473
11473
|
var FormulaError = require_error();
|
|
11474
11474
|
var { FormulaHelpers, Types, Criteria, Address } = require_helpers();
|
|
11475
|
-
var { Infix } = require_operators();
|
|
11475
|
+
var { Infix: Infix2 } = require_operators();
|
|
11476
11476
|
var H = FormulaHelpers;
|
|
11477
11477
|
var { DistributionFunctions } = require_distribution();
|
|
11478
11478
|
var StatisticalFunctions = {
|
|
@@ -11534,7 +11534,7 @@ ${e.toString()}`);
|
|
|
11534
11534
|
sum += valueToAdd;
|
|
11535
11535
|
cnt++;
|
|
11536
11536
|
}
|
|
11537
|
-
} else if (
|
|
11537
|
+
} else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
|
|
11538
11538
|
sum += valueToAdd;
|
|
11539
11539
|
cnt++;
|
|
11540
11540
|
}
|
|
@@ -11573,7 +11573,7 @@ ${e.toString()}`);
|
|
|
11573
11573
|
if (criteria.op === "wc") {
|
|
11574
11574
|
if (criteria.match === criteria.value.test(value2))
|
|
11575
11575
|
cnt++;
|
|
11576
|
-
} else if (
|
|
11576
|
+
} else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
|
|
11577
11577
|
cnt++;
|
|
11578
11578
|
}
|
|
11579
11579
|
});
|
|
@@ -21126,7 +21126,7 @@ ${e.toString()}`);
|
|
|
21126
21126
|
"../../node_modules/fast-formula-parser/grammar/utils.js"(exports2, module2) {
|
|
21127
21127
|
var FormulaError = require_error();
|
|
21128
21128
|
var { Address } = require_helpers();
|
|
21129
|
-
var { Prefix, Postfix, Infix, Operators } = require_operators();
|
|
21129
|
+
var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
|
|
21130
21130
|
var Collection = require_collection();
|
|
21131
21131
|
var MAX_ROW = 1048576;
|
|
21132
21132
|
var MAX_COLUMN = 16384;
|
|
@@ -21250,11 +21250,11 @@ ${e.toString()}`);
|
|
|
21250
21250
|
if (this.isFormulaError(val2))
|
|
21251
21251
|
return val2;
|
|
21252
21252
|
if (Operators.compareOp.includes(infix))
|
|
21253
|
-
return
|
|
21253
|
+
return Infix2.compareOp(val1, infix, val2, isArray1, isArray2);
|
|
21254
21254
|
else if (Operators.concatOp.includes(infix))
|
|
21255
|
-
return
|
|
21255
|
+
return Infix2.concatOp(val1, infix, val2, isArray1, isArray2);
|
|
21256
21256
|
else if (Operators.mathOp.includes(infix))
|
|
21257
|
-
return
|
|
21257
|
+
return Infix2.mathOp(val1, infix, val2, isArray1, isArray2);
|
|
21258
21258
|
else
|
|
21259
21259
|
throw new Error(`Unrecognized infix: ${infix}`);
|
|
21260
21260
|
}
|
|
@@ -21806,7 +21806,7 @@ ${e.toString()}`);
|
|
|
21806
21806
|
"../../node_modules/fast-formula-parser/grammar/dependency/utils.js"(exports2, module2) {
|
|
21807
21807
|
var FormulaError = require_error();
|
|
21808
21808
|
var { FormulaHelpers, Types, Address } = require_helpers();
|
|
21809
|
-
var { Prefix, Postfix, Infix, Operators } = require_operators();
|
|
21809
|
+
var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
|
|
21810
21810
|
var Collection = require_collection();
|
|
21811
21811
|
var MAX_ROW = 1048576;
|
|
21812
21812
|
var MAX_COLUMN = 16384;
|
|
@@ -53990,14 +53990,14 @@ ${e.toString()}`);
|
|
|
53990
53990
|
if (!range) return void 0;
|
|
53991
53991
|
const header = [];
|
|
53992
53992
|
for (let col = range.startCol; col <= range.endCol; col++) {
|
|
53993
|
-
const cell = sheet.
|
|
53993
|
+
const cell = sheet.getCellAt(range.startRow, col);
|
|
53994
53994
|
header.push(formatHeader(cell?.value));
|
|
53995
53995
|
}
|
|
53996
53996
|
const records = [];
|
|
53997
53997
|
for (let row = range.startRow + 1; row <= range.endRow; row++) {
|
|
53998
53998
|
const rec = [];
|
|
53999
53999
|
for (let col = range.startCol; col <= range.endCol; col++) {
|
|
54000
|
-
const cell = sheet.
|
|
54000
|
+
const cell = sheet.getCellAt(row, col);
|
|
54001
54001
|
rec.push(coerceValue(cell?.value));
|
|
54002
54002
|
}
|
|
54003
54003
|
records.push(rec);
|
|
@@ -54041,23 +54041,70 @@ ${e.toString()}`);
|
|
|
54041
54041
|
get size() {
|
|
54042
54042
|
return this.store.size;
|
|
54043
54043
|
}
|
|
54044
|
+
/**
|
|
54045
|
+
* The populated extent of this map, memoized until a mutation invalidates it.
|
|
54046
|
+
*
|
|
54047
|
+
* It lives HERE rather than on `SheetModel` because it is a pure function of
|
|
54048
|
+
* the cell set: a cache held next to the data cannot outlive it, so replacing
|
|
54049
|
+
* a sheet's map can never serve the previous map's extent. Every mutator
|
|
54050
|
+
* below drops it, including the packed pair — those bypass `set`/`delete`
|
|
54051
|
+
* entirely and are what the row/column commands use.
|
|
54052
|
+
*
|
|
54053
|
+
* Walking PACKED keys is the other half. `keys()` unpacks each key into an A1
|
|
54054
|
+
* string, which the caller then reparsed with a regex, so the old form packed,
|
|
54055
|
+
* unpacked and reparsed the whole sheet on every call — and it is called once
|
|
54056
|
+
* per oversized range during a recompute.
|
|
54057
|
+
*/
|
|
54058
|
+
usedRange() {
|
|
54059
|
+
if (!this.usedRangeCache) {
|
|
54060
|
+
let minRow2 = Infinity;
|
|
54061
|
+
let maxRow2 = 0;
|
|
54062
|
+
let minCol2 = Infinity;
|
|
54063
|
+
let maxCol2 = 0;
|
|
54064
|
+
for (const packed of this.store.keys()) {
|
|
54065
|
+
const { row, col } = unpackRowCol(packed);
|
|
54066
|
+
if (row < minRow2) minRow2 = row;
|
|
54067
|
+
if (row > maxRow2) maxRow2 = row;
|
|
54068
|
+
if (col < minCol2) minCol2 = col;
|
|
54069
|
+
if (col > maxCol2) maxCol2 = col;
|
|
54070
|
+
}
|
|
54071
|
+
this.usedRangeCache = maxRow2 === 0 ? { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 } : { minRow: minRow2, maxRow: maxRow2, minCol: minCol2, maxCol: maxCol2 };
|
|
54072
|
+
}
|
|
54073
|
+
const { minRow, maxRow, minCol, maxCol } = this.usedRangeCache;
|
|
54074
|
+
return { minRow, maxRow, minCol, maxCol };
|
|
54075
|
+
}
|
|
54044
54076
|
get(ref2) {
|
|
54045
54077
|
return this.store.get(packRef(ref2));
|
|
54046
54078
|
}
|
|
54079
|
+
/**
|
|
54080
|
+
* Read by coordinate, skipping the A1 round trip.
|
|
54081
|
+
*
|
|
54082
|
+
* `get(ref)` costs a number→string→regex→number conversion per call, which
|
|
54083
|
+
* is invisible until a caller reads a range cell by cell: on one real
|
|
54084
|
+
* workbook `refToRowCol` alone was 39% of a full recompute. A caller already
|
|
54085
|
+
* holding (row, col) — the range resolver, any grid walk — should use this.
|
|
54086
|
+
*/
|
|
54087
|
+
getAt(row, col) {
|
|
54088
|
+
return this.store.get(packRowCol(row, col));
|
|
54089
|
+
}
|
|
54047
54090
|
set(ref2, cell) {
|
|
54048
54091
|
const key = packRef(ref2);
|
|
54049
54092
|
if (key < 0) return this;
|
|
54050
54093
|
this.store.set(key, cell);
|
|
54094
|
+
this.usedRangeCache = void 0;
|
|
54051
54095
|
return this;
|
|
54052
54096
|
}
|
|
54053
54097
|
has(ref2) {
|
|
54054
54098
|
return this.store.has(packRef(ref2));
|
|
54055
54099
|
}
|
|
54056
54100
|
delete(ref2) {
|
|
54057
|
-
|
|
54101
|
+
const deleted = this.store.delete(packRef(ref2));
|
|
54102
|
+
if (deleted) this.usedRangeCache = void 0;
|
|
54103
|
+
return deleted;
|
|
54058
54104
|
}
|
|
54059
54105
|
clear() {
|
|
54060
54106
|
this.store.clear();
|
|
54107
|
+
this.usedRangeCache = void 0;
|
|
54061
54108
|
}
|
|
54062
54109
|
*keys() {
|
|
54063
54110
|
for (const k of this.store.keys()) yield unpackRef(k);
|
|
@@ -54085,10 +54132,13 @@ ${e.toString()}`);
|
|
|
54085
54132
|
/** Direct packed-key set, for the same hot loops. */
|
|
54086
54133
|
setPacked(packed, cell) {
|
|
54087
54134
|
this.store.set(packed, cell);
|
|
54135
|
+
this.usedRangeCache = void 0;
|
|
54088
54136
|
}
|
|
54089
54137
|
/** Direct packed-key delete. */
|
|
54090
54138
|
deletePacked(packed) {
|
|
54091
|
-
|
|
54139
|
+
const deleted = this.store.delete(packed);
|
|
54140
|
+
if (deleted) this.usedRangeCache = void 0;
|
|
54141
|
+
return deleted;
|
|
54092
54142
|
}
|
|
54093
54143
|
};
|
|
54094
54144
|
var StyleRegistry = class {
|
|
@@ -54271,6 +54321,10 @@ ${e.toString()}`);
|
|
|
54271
54321
|
getCell(ref2) {
|
|
54272
54322
|
return this.cells.get(ref2);
|
|
54273
54323
|
}
|
|
54324
|
+
/** Read by coordinate, skipping the A1 round trip — see `CellMap.getAt`. */
|
|
54325
|
+
getCellAt(row, col) {
|
|
54326
|
+
return this.cells.getAt(row, col);
|
|
54327
|
+
}
|
|
54274
54328
|
/**
|
|
54275
54329
|
* Set cell value with optional inline style. Interns style via the
|
|
54276
54330
|
* StyleRegistry when available. When `style` is supplied and matches the
|
|
@@ -54365,22 +54419,12 @@ ${e.toString()}`);
|
|
|
54365
54419
|
this.set(ref2, label, style);
|
|
54366
54420
|
});
|
|
54367
54421
|
}
|
|
54368
|
-
/**
|
|
54422
|
+
/**
|
|
54423
|
+
* The populated extent of the sheet. Derived from — and cached by — the cell
|
|
54424
|
+
* map, so it cannot outlive the data it describes.
|
|
54425
|
+
*/
|
|
54369
54426
|
getUsedRange() {
|
|
54370
|
-
|
|
54371
|
-
let maxRow = 0;
|
|
54372
|
-
let minCol = Infinity;
|
|
54373
|
-
let maxCol = 0;
|
|
54374
|
-
for (const ref2 of this.cells.keys()) {
|
|
54375
|
-
const rc = refToRowCol(ref2);
|
|
54376
|
-
if (!rc) continue;
|
|
54377
|
-
if (rc.row < minRow) minRow = rc.row;
|
|
54378
|
-
if (rc.row > maxRow) maxRow = rc.row;
|
|
54379
|
-
if (rc.col < minCol) minCol = rc.col;
|
|
54380
|
-
if (rc.col > maxCol) maxCol = rc.col;
|
|
54381
|
-
}
|
|
54382
|
-
if (maxRow === 0) return { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 };
|
|
54383
|
-
return { minRow, maxRow, minCol, maxCol };
|
|
54427
|
+
return this.cells.usedRange();
|
|
54384
54428
|
}
|
|
54385
54429
|
};
|
|
54386
54430
|
var WorkbookModel = class {
|
|
@@ -54627,7 +54671,7 @@ ${e.toString()}`);
|
|
|
54627
54671
|
let visible = true;
|
|
54628
54672
|
for (const filterCol of activeFilters) {
|
|
54629
54673
|
const col = startCol + filterCol.colIndex;
|
|
54630
|
-
const cell = sheet.
|
|
54674
|
+
const cell = sheet.getCellAt(r, col);
|
|
54631
54675
|
const cellText = cell?.displayValue ?? "";
|
|
54632
54676
|
if (filterCol.filterValues && filterCol.filterValues.length > 0) {
|
|
54633
54677
|
if (!filterCol.filterValues.includes(cellText)) {
|
|
@@ -61857,6 +61901,28 @@ ${e.toString()}`);
|
|
|
61857
61901
|
const bang = key.indexOf("!");
|
|
61858
61902
|
return { sheet: key.slice(0, bang), ref: key.slice(bang + 1) };
|
|
61859
61903
|
}
|
|
61904
|
+
var COARSE_COL_SUFFIX = ":*";
|
|
61905
|
+
var COARSE_ROW_PREFIX = "*:";
|
|
61906
|
+
function columnKey(sheet, column) {
|
|
61907
|
+
return `${sheet}!${column}${COARSE_COL_SUFFIX}`;
|
|
61908
|
+
}
|
|
61909
|
+
function rowKey(sheet, row) {
|
|
61910
|
+
return `${sheet}!${COARSE_ROW_PREFIX}${row}`;
|
|
61911
|
+
}
|
|
61912
|
+
function isCoarseKey(key) {
|
|
61913
|
+
return key.endsWith(COARSE_COL_SUFFIX) || key.includes(`!${COARSE_ROW_PREFIX}`);
|
|
61914
|
+
}
|
|
61915
|
+
var A1 = /^([A-Z]+)(\d+)$/;
|
|
61916
|
+
function coarseKeysFor(cellKeyStr) {
|
|
61917
|
+
const bang = cellKeyStr.indexOf("!");
|
|
61918
|
+
if (bang < 0) return [];
|
|
61919
|
+
const sheet = cellKeyStr.slice(0, bang);
|
|
61920
|
+
const ref2 = cellKeyStr.slice(bang + 1);
|
|
61921
|
+
const m = A1.exec(ref2);
|
|
61922
|
+
if (!m) return [];
|
|
61923
|
+
return [columnKey(sheet, m[1]), rowKey(sheet, Number(m[2]))];
|
|
61924
|
+
}
|
|
61925
|
+
var EMPTY = /* @__PURE__ */ new Set();
|
|
61860
61926
|
var DependencyGraph = class {
|
|
61861
61927
|
constructor() {
|
|
61862
61928
|
// Forward edges: cell → cells it references (precedents)
|
|
@@ -61891,19 +61957,35 @@ ${e.toString()}`);
|
|
|
61891
61957
|
}
|
|
61892
61958
|
/** Get all cells that directly depend on the given cell. */
|
|
61893
61959
|
getDirectDependents(cell) {
|
|
61894
|
-
return Array.from(this.
|
|
61960
|
+
return Array.from(this.dependentsOf(cell));
|
|
61961
|
+
}
|
|
61962
|
+
/**
|
|
61963
|
+
* Everything that references this cell — directly, or through the column or
|
|
61964
|
+
* row node that contains it. The single place `dependents` is read, so a
|
|
61965
|
+
* whole-column reference cannot be forgotten by one traversal and honoured by
|
|
61966
|
+
* another.
|
|
61967
|
+
*/
|
|
61968
|
+
dependentsOf(cell) {
|
|
61969
|
+
const direct = this.dependents.get(cell);
|
|
61970
|
+
if (isCoarseKey(cell)) return direct ?? EMPTY;
|
|
61971
|
+
const coarse = coarseKeysFor(cell);
|
|
61972
|
+
let widened;
|
|
61973
|
+
for (const key of coarse) {
|
|
61974
|
+
const via = this.dependents.get(key);
|
|
61975
|
+
if (!via || via.size === 0) continue;
|
|
61976
|
+
widened ?? (widened = new Set(direct));
|
|
61977
|
+
for (const d of via) widened.add(d);
|
|
61978
|
+
}
|
|
61979
|
+
return widened ?? direct ?? EMPTY;
|
|
61895
61980
|
}
|
|
61896
61981
|
/** Get all cells that need recalculation when the given cells change, in topological order. */
|
|
61897
61982
|
getRecalcOrder(changed) {
|
|
61898
|
-
const changedSet = new Set(changed);
|
|
61899
61983
|
const affected = /* @__PURE__ */ new Set();
|
|
61900
61984
|
const queue = [...changed];
|
|
61901
61985
|
let head = 0;
|
|
61902
61986
|
while (head < queue.length) {
|
|
61903
61987
|
const cell = queue[head++];
|
|
61904
|
-
const
|
|
61905
|
-
if (!deps) continue;
|
|
61906
|
-
for (const dep of deps) {
|
|
61988
|
+
for (const dep of this.dependentsOf(cell)) {
|
|
61907
61989
|
if (!affected.has(dep)) {
|
|
61908
61990
|
affected.add(dep);
|
|
61909
61991
|
queue.push(dep);
|
|
@@ -61912,15 +61994,11 @@ ${e.toString()}`);
|
|
|
61912
61994
|
}
|
|
61913
61995
|
if (affected.size === 0) return [];
|
|
61914
61996
|
const inDegree = /* @__PURE__ */ new Map();
|
|
61997
|
+
for (const cell of affected) inDegree.set(cell, 0);
|
|
61915
61998
|
for (const cell of affected) {
|
|
61916
|
-
|
|
61917
|
-
|
|
61918
|
-
if (prec) {
|
|
61919
|
-
for (const p of prec) {
|
|
61920
|
-
if (affected.has(p)) count++;
|
|
61921
|
-
}
|
|
61999
|
+
for (const dep of this.dependentsOf(cell)) {
|
|
62000
|
+
if (affected.has(dep)) inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
|
|
61922
62001
|
}
|
|
61923
|
-
inDegree.set(cell, count);
|
|
61924
62002
|
}
|
|
61925
62003
|
const result = [];
|
|
61926
62004
|
const ready = [];
|
|
@@ -61931,9 +62009,7 @@ ${e.toString()}`);
|
|
|
61931
62009
|
while (readyHead < ready.length) {
|
|
61932
62010
|
const cell = ready[readyHead++];
|
|
61933
62011
|
result.push(cell);
|
|
61934
|
-
const
|
|
61935
|
-
if (!deps) continue;
|
|
61936
|
-
for (const dep of deps) {
|
|
62012
|
+
for (const dep of this.dependentsOf(cell)) {
|
|
61937
62013
|
if (!affected.has(dep)) continue;
|
|
61938
62014
|
const newDeg = (inDegree.get(dep) ?? 1) - 1;
|
|
61939
62015
|
inDegree.set(dep, newDeg);
|
|
@@ -61954,12 +62030,13 @@ ${e.toString()}`);
|
|
|
61954
62030
|
const stack = new Array();
|
|
61955
62031
|
const prec = this.precedents.get(cell);
|
|
61956
62032
|
if (!prec) return null;
|
|
62033
|
+
const originCoarse = new Set(coarseKeysFor(cell));
|
|
61957
62034
|
for (const p of prec) {
|
|
61958
62035
|
stack.push({ cell: p, path: [cell, p] });
|
|
61959
62036
|
}
|
|
61960
62037
|
while (stack.length > 0) {
|
|
61961
62038
|
const { cell: current, path } = stack.pop();
|
|
61962
|
-
if (current === cell) return path;
|
|
62039
|
+
if (current === cell || originCoarse.has(current)) return path;
|
|
61963
62040
|
if (visited.has(current)) continue;
|
|
61964
62041
|
visited.add(current);
|
|
61965
62042
|
const nextPrec = this.precedents.get(current);
|
|
@@ -62072,6 +62149,20 @@ ${e.toString()}`);
|
|
|
62072
62149
|
};
|
|
62073
62150
|
}
|
|
62074
62151
|
|
|
62152
|
+
// ../xlsx/src/blank_comparison.ts
|
|
62153
|
+
var import_operators = __toESM(require_operators(), 1);
|
|
62154
|
+
var patched = false;
|
|
62155
|
+
function applyExcelBlankSemantics() {
|
|
62156
|
+
if (patched) return;
|
|
62157
|
+
patched = true;
|
|
62158
|
+
const original = import_operators.Infix.compareOp;
|
|
62159
|
+
import_operators.Infix.compareOp = (value1, infix, value2, isArray1, isArray2) => {
|
|
62160
|
+
if (value1 == null && typeof value2 === "string") value1 = "";
|
|
62161
|
+
if (value2 == null && typeof value1 === "string") value2 = "";
|
|
62162
|
+
return original(value1, infix, value2, isArray1, isArray2);
|
|
62163
|
+
};
|
|
62164
|
+
}
|
|
62165
|
+
|
|
62075
62166
|
// ../xlsx/src/formula_engine.ts
|
|
62076
62167
|
var unwrapArg = (a) => a !== null && typeof a === "object" && "value" in a ? a.value : a;
|
|
62077
62168
|
function adaptFormulajs(fn) {
|
|
@@ -62114,9 +62205,24 @@ ${e.toString()}`);
|
|
|
62114
62205
|
bridgeCtxCache = withCtx;
|
|
62115
62206
|
return withCtx;
|
|
62116
62207
|
}
|
|
62208
|
+
var MAX_EXPANDED_CELLS = 1e4;
|
|
62117
62209
|
var _FormulaEngine = class _FormulaEngine {
|
|
62118
62210
|
constructor(workbook) {
|
|
62119
62211
|
this.graph = new DependencyGraph();
|
|
62212
|
+
/**
|
|
62213
|
+
* Formula cells (by key) whose text the parser could not read AT ALL, found
|
|
62214
|
+
* while building the graph from a loaded file.
|
|
62215
|
+
*
|
|
62216
|
+
* They are held apart from every other failure because the honest response
|
|
62217
|
+
* differs: a formula that evaluates TO an error has been understood, so
|
|
62218
|
+
* writing `#DIV/0!` reports a real answer, while one we cannot parse has not
|
|
62219
|
+
* been — and overwriting the value its author computed would replace a
|
|
62220
|
+
* correct number with a guess. These keep their cached value and are named in
|
|
62221
|
+
* the report instead. The engine's own rule, one level down: degrade what you
|
|
62222
|
+
* cannot draw, refuse what you cannot read.
|
|
62223
|
+
*/
|
|
62224
|
+
this.unreadableFormulas = /* @__PURE__ */ new Set();
|
|
62225
|
+
applyExcelBlankSemantics();
|
|
62120
62226
|
this.workbook = workbook;
|
|
62121
62227
|
this.parser = new import_fast_formula_parser.default({
|
|
62122
62228
|
onCell: (ref2) => this.resolveCell(ref2),
|
|
@@ -62137,11 +62243,13 @@ ${e.toString()}`);
|
|
|
62137
62243
|
/** Build dependency graph from all formula cells in the workbook. */
|
|
62138
62244
|
buildGraph() {
|
|
62139
62245
|
this.graph.clear();
|
|
62246
|
+
this.unreadableFormulas.clear();
|
|
62140
62247
|
for (const sheet of this.workbook.sheets) {
|
|
62141
62248
|
for (const [ref2, cell] of sheet.cells) {
|
|
62142
62249
|
if (!cell.formula) continue;
|
|
62143
62250
|
const key = cellKey({ sheet: sheet.name, ref: ref2 });
|
|
62144
|
-
const deps = this.
|
|
62251
|
+
const { deps, readable } = this.analyzeFormula(cell.formula, sheet.name);
|
|
62252
|
+
if (!readable) this.unreadableFormulas.add(key);
|
|
62145
62253
|
this.graph.setDependencies(key, deps);
|
|
62146
62254
|
}
|
|
62147
62255
|
}
|
|
@@ -62197,8 +62305,13 @@ ${e.toString()}`);
|
|
|
62197
62305
|
onCellChanged(sheetName, ref2, formula) {
|
|
62198
62306
|
const key = cellKey({ sheet: sheetName, ref: ref2 });
|
|
62199
62307
|
if (formula) {
|
|
62200
|
-
const deps = this.
|
|
62308
|
+
const { deps, readable } = this.analyzeFormula(formula, sheetName);
|
|
62201
62309
|
this.graph.setDependencies(key, deps);
|
|
62310
|
+
this.unreadableFormulas.delete(key);
|
|
62311
|
+
if (!readable) {
|
|
62312
|
+
this.setCellError(key, "#ERROR!");
|
|
62313
|
+
return;
|
|
62314
|
+
}
|
|
62202
62315
|
const cycle = this.graph.detectCycle(key);
|
|
62203
62316
|
if (cycle) {
|
|
62204
62317
|
this.setCellError(key, "#CIRC!");
|
|
@@ -62207,6 +62320,7 @@ ${e.toString()}`);
|
|
|
62207
62320
|
this.evaluateCell(key);
|
|
62208
62321
|
} else {
|
|
62209
62322
|
this.graph.removeCell(key);
|
|
62323
|
+
this.unreadableFormulas.delete(key);
|
|
62210
62324
|
}
|
|
62211
62325
|
const order = this.graph.getRecalcOrder([key]);
|
|
62212
62326
|
for (const depKey of order) {
|
|
@@ -62223,6 +62337,7 @@ ${e.toString()}`);
|
|
|
62223
62337
|
// ===========================================================================
|
|
62224
62338
|
/** Evaluate a single formula cell and update its value. */
|
|
62225
62339
|
evaluateCell(key) {
|
|
62340
|
+
if (this.unreadableFormulas.has(key)) return;
|
|
62226
62341
|
const { sheet: sheetName, ref: ref2 } = parseCellKey(key);
|
|
62227
62342
|
const sheetIndex = this.workbook.sheets.findIndex((s) => s.name === sheetName);
|
|
62228
62343
|
if (sheetIndex < 0) return;
|
|
@@ -62271,26 +62386,60 @@ ${e.toString()}`);
|
|
|
62271
62386
|
// ===========================================================================
|
|
62272
62387
|
/** Extract cell/range references from a formula string. Returns empty array for unparseable formulas. */
|
|
62273
62388
|
extractDependencies(formula, currentSheet) {
|
|
62274
|
-
|
|
62275
|
-
|
|
62276
|
-
|
|
62389
|
+
return this.analyzeFormula(formula, currentSheet).deps;
|
|
62390
|
+
}
|
|
62391
|
+
/**
|
|
62392
|
+
* Dependencies plus whether the parser could read the formula at all.
|
|
62393
|
+
*
|
|
62394
|
+
* `fast-formula-parser` reports a formula it cannot LEX by throwing, the same
|
|
62395
|
+
* channel it uses for `#VALUE!`, so the lexer failure of a single cell used to
|
|
62396
|
+
* escape `buildGraph` and abort the recompute for the entire workbook —
|
|
62397
|
+
* observed on a real workbook whose `VLOOKUP` pointed at another file
|
|
62398
|
+
* (`'[1]DATA'!B:C`, an external-workbook reference the lexer has no rule for).
|
|
62399
|
+
* Anything the grammar does not cover lands here the same way, so this catches
|
|
62400
|
+
* the class rather than that one syntax.
|
|
62401
|
+
*/
|
|
62402
|
+
analyzeFormula(formula, currentSheet) {
|
|
62403
|
+
let deps;
|
|
62404
|
+
try {
|
|
62405
|
+
const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
|
|
62406
|
+
const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
|
|
62407
|
+
deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
|
|
62408
|
+
} catch {
|
|
62409
|
+
return { deps: [], readable: false };
|
|
62410
|
+
}
|
|
62277
62411
|
const refs = [];
|
|
62278
62412
|
for (const dep of deps) {
|
|
62279
62413
|
const depSheet = dep.sheet ?? currentSheet;
|
|
62280
62414
|
if (dep.from && dep.to) {
|
|
62281
|
-
const
|
|
62282
|
-
|
|
62283
|
-
|
|
62284
|
-
|
|
62415
|
+
const rows = dep.to.row - dep.from.row + 1;
|
|
62416
|
+
const cols = dep.to.col - dep.from.col + 1;
|
|
62417
|
+
if (rows * cols > MAX_EXPANDED_CELLS) {
|
|
62418
|
+
if (cols <= rows) {
|
|
62419
|
+
for (let c = dep.from.col; c <= dep.to.col; c++) {
|
|
62420
|
+
refs.push(columnKey(depSheet, colNumToLetters(c)));
|
|
62421
|
+
}
|
|
62422
|
+
} else {
|
|
62423
|
+
for (let r = dep.from.row; r <= dep.to.row; r++) {
|
|
62424
|
+
refs.push(rowKey(depSheet, r));
|
|
62425
|
+
}
|
|
62426
|
+
}
|
|
62427
|
+
continue;
|
|
62428
|
+
}
|
|
62429
|
+
for (let r = dep.from.row; r <= dep.to.row; r++) {
|
|
62430
|
+
for (let c = dep.from.col; c <= dep.to.col; c++) {
|
|
62285
62431
|
refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(r, c) }));
|
|
62286
|
-
count++;
|
|
62287
62432
|
}
|
|
62288
62433
|
}
|
|
62289
62434
|
} else if (dep.row != null && dep.col != null) {
|
|
62290
62435
|
refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(dep.row, dep.col) }));
|
|
62291
62436
|
}
|
|
62292
62437
|
}
|
|
62293
|
-
return refs;
|
|
62438
|
+
return { deps: refs, readable: true };
|
|
62439
|
+
}
|
|
62440
|
+
/** Formula cells the parser could not read, by key. Read by the recompute report. */
|
|
62441
|
+
unreadable() {
|
|
62442
|
+
return this.unreadableFormulas;
|
|
62294
62443
|
}
|
|
62295
62444
|
// ===========================================================================
|
|
62296
62445
|
// Cell value resolution callbacks
|
|
@@ -62320,8 +62469,7 @@ ${e.toString()}`);
|
|
|
62320
62469
|
for (let r = ref2.from.row; r <= lastRow; r++) {
|
|
62321
62470
|
const row = [];
|
|
62322
62471
|
for (let c = ref2.from.col; c <= lastCol; c++) {
|
|
62323
|
-
|
|
62324
|
-
row.push(cell?.value ?? null);
|
|
62472
|
+
row.push(sheet.getCellAt(r, c)?.value ?? null);
|
|
62325
62473
|
}
|
|
62326
62474
|
result.push(row);
|
|
62327
62475
|
}
|
|
@@ -62411,15 +62559,19 @@ ${e.toString()}`);
|
|
|
62411
62559
|
const engine = new FormulaEngine(workbook);
|
|
62412
62560
|
engine.buildGraph();
|
|
62413
62561
|
engine.evaluateAll();
|
|
62414
|
-
return collectRecomputeReport(workbook);
|
|
62562
|
+
return collectRecomputeReport(workbook, engine.unreadable());
|
|
62415
62563
|
}
|
|
62416
|
-
function collectRecomputeReport(workbook) {
|
|
62564
|
+
function collectRecomputeReport(workbook, unreadable) {
|
|
62417
62565
|
let total = 0;
|
|
62418
62566
|
const unevaluated = [];
|
|
62419
62567
|
for (const sheet of workbook.sheets) {
|
|
62420
62568
|
for (const [ref2, cell] of sheet.cells) {
|
|
62421
62569
|
if (!cell.formula) continue;
|
|
62422
62570
|
total++;
|
|
62571
|
+
if (unreadable.has(cellKey({ sheet: sheet.name, ref: ref2 }))) {
|
|
62572
|
+
unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: "#UNREADABLE!" });
|
|
62573
|
+
continue;
|
|
62574
|
+
}
|
|
62423
62575
|
if (cell.value === null) {
|
|
62424
62576
|
unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: cell.error ?? "#ERROR!" });
|
|
62425
62577
|
}
|
package/dist/src/cli.js
CHANGED
|
@@ -16769,7 +16769,7 @@ var require_operators = __commonJS({
|
|
|
16769
16769
|
}
|
|
16770
16770
|
};
|
|
16771
16771
|
var type2Number = { "boolean": 3, "string": 2, "number": 1 };
|
|
16772
|
-
var
|
|
16772
|
+
var Infix2 = {
|
|
16773
16773
|
compareOp: (value1, infix, value2, isArray1, isArray22) => {
|
|
16774
16774
|
if (value1 == null) value1 = 0;
|
|
16775
16775
|
if (value2 == null) value2 = 0;
|
|
@@ -16860,7 +16860,7 @@ var require_operators = __commonJS({
|
|
|
16860
16860
|
module2.exports = {
|
|
16861
16861
|
Prefix,
|
|
16862
16862
|
Postfix,
|
|
16863
|
-
Infix,
|
|
16863
|
+
Infix: Infix2,
|
|
16864
16864
|
Operators: {
|
|
16865
16865
|
compareOp: ["<", ">", "=", "<>", "<=", ">="],
|
|
16866
16866
|
concatOp: ["&"],
|
|
@@ -16875,7 +16875,7 @@ var require_math = __commonJS({
|
|
|
16875
16875
|
"../../node_modules/fast-formula-parser/formulas/functions/math.js"(exports2, module2) {
|
|
16876
16876
|
var FormulaError = require_error();
|
|
16877
16877
|
var { FormulaHelpers, Types, Factorials, Criteria } = require_helpers();
|
|
16878
|
-
var { Infix } = require_operators();
|
|
16878
|
+
var { Infix: Infix2 } = require_operators();
|
|
16879
16879
|
var H = FormulaHelpers;
|
|
16880
16880
|
var f = [];
|
|
16881
16881
|
var fd2 = [];
|
|
@@ -17398,7 +17398,7 @@ var require_math = __commonJS({
|
|
|
17398
17398
|
if (criteria.match === criteria.value.test(value2)) {
|
|
17399
17399
|
sum2 += valueToAdd;
|
|
17400
17400
|
}
|
|
17401
|
-
} else if (
|
|
17401
|
+
} else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
|
|
17402
17402
|
sum2 += valueToAdd;
|
|
17403
17403
|
}
|
|
17404
17404
|
});
|
|
@@ -23678,7 +23678,7 @@ var require_statistical = __commonJS({
|
|
|
23678
23678
|
"../../node_modules/fast-formula-parser/formulas/functions/statistical.js"(exports2, module2) {
|
|
23679
23679
|
var FormulaError = require_error();
|
|
23680
23680
|
var { FormulaHelpers, Types, Criteria, Address } = require_helpers();
|
|
23681
|
-
var { Infix } = require_operators();
|
|
23681
|
+
var { Infix: Infix2 } = require_operators();
|
|
23682
23682
|
var H = FormulaHelpers;
|
|
23683
23683
|
var { DistributionFunctions } = require_distribution();
|
|
23684
23684
|
var StatisticalFunctions = {
|
|
@@ -23740,7 +23740,7 @@ var require_statistical = __commonJS({
|
|
|
23740
23740
|
sum2 += valueToAdd;
|
|
23741
23741
|
cnt++;
|
|
23742
23742
|
}
|
|
23743
|
-
} else if (
|
|
23743
|
+
} else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
|
|
23744
23744
|
sum2 += valueToAdd;
|
|
23745
23745
|
cnt++;
|
|
23746
23746
|
}
|
|
@@ -23779,7 +23779,7 @@ var require_statistical = __commonJS({
|
|
|
23779
23779
|
if (criteria.op === "wc") {
|
|
23780
23780
|
if (criteria.match === criteria.value.test(value2))
|
|
23781
23781
|
cnt++;
|
|
23782
|
-
} else if (
|
|
23782
|
+
} else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
|
|
23783
23783
|
cnt++;
|
|
23784
23784
|
}
|
|
23785
23785
|
});
|
|
@@ -33332,7 +33332,7 @@ var require_utils2 = __commonJS({
|
|
|
33332
33332
|
"../../node_modules/fast-formula-parser/grammar/utils.js"(exports2, module2) {
|
|
33333
33333
|
var FormulaError = require_error();
|
|
33334
33334
|
var { Address } = require_helpers();
|
|
33335
|
-
var { Prefix, Postfix, Infix, Operators } = require_operators();
|
|
33335
|
+
var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
|
|
33336
33336
|
var Collection = require_collection();
|
|
33337
33337
|
var MAX_ROW2 = 1048576;
|
|
33338
33338
|
var MAX_COLUMN2 = 16384;
|
|
@@ -33456,11 +33456,11 @@ var require_utils2 = __commonJS({
|
|
|
33456
33456
|
if (this.isFormulaError(val2))
|
|
33457
33457
|
return val2;
|
|
33458
33458
|
if (Operators.compareOp.includes(infix))
|
|
33459
|
-
return
|
|
33459
|
+
return Infix2.compareOp(val1, infix, val2, isArray1, isArray22);
|
|
33460
33460
|
else if (Operators.concatOp.includes(infix))
|
|
33461
|
-
return
|
|
33461
|
+
return Infix2.concatOp(val1, infix, val2, isArray1, isArray22);
|
|
33462
33462
|
else if (Operators.mathOp.includes(infix))
|
|
33463
|
-
return
|
|
33463
|
+
return Infix2.mathOp(val1, infix, val2, isArray1, isArray22);
|
|
33464
33464
|
else
|
|
33465
33465
|
throw new Error(`Unrecognized infix: ${infix}`);
|
|
33466
33466
|
}
|
|
@@ -34012,7 +34012,7 @@ var require_utils3 = __commonJS({
|
|
|
34012
34012
|
"../../node_modules/fast-formula-parser/grammar/dependency/utils.js"(exports2, module2) {
|
|
34013
34013
|
var FormulaError = require_error();
|
|
34014
34014
|
var { FormulaHelpers, Types, Address } = require_helpers();
|
|
34015
|
-
var { Prefix, Postfix, Infix, Operators } = require_operators();
|
|
34015
|
+
var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
|
|
34016
34016
|
var Collection = require_collection();
|
|
34017
34017
|
var MAX_ROW2 = 1048576;
|
|
34018
34018
|
var MAX_COLUMN2 = 16384;
|
|
@@ -73018,6 +73018,7 @@ async function appDeploy(client, args) {
|
|
|
73018
73018
|
}
|
|
73019
73019
|
if (liveAfter) {
|
|
73020
73020
|
warnIfUnbranded(liveAfter);
|
|
73021
|
+
warnIfUndescribed(liveAfter);
|
|
73021
73022
|
warnIfUnboundAliases(liveAfter, called);
|
|
73022
73023
|
warnIfInertAgentTools(liveAfter);
|
|
73023
73024
|
}
|
|
@@ -73056,6 +73057,15 @@ function warnIfUnbranded(app) {
|
|
|
73056
73057
|
Find an icon: lotics run search_app_icons '{"query":"<word>"}'`
|
|
73057
73058
|
);
|
|
73058
73059
|
}
|
|
73060
|
+
function warnIfUndescribed(app) {
|
|
73061
|
+
if (app.description && app.description.trim() !== "") return;
|
|
73062
|
+
console.error(
|
|
73063
|
+
`
|
|
73064
|
+
\u26A0 This app has no description \u2014 the capability catalog its chat agent reads every turn
|
|
73065
|
+
lists the aliases and says nothing about what to do with them.
|
|
73066
|
+
Set it: lotics run update_app '{"app_id":"${app.id}","description":"<what the app is for, and the standing job it does>"}'`
|
|
73067
|
+
);
|
|
73068
|
+
}
|
|
73059
73069
|
async function readLiveQueries(client, appId) {
|
|
73060
73070
|
try {
|
|
73061
73071
|
const app = await client.getApp(appId);
|
|
@@ -73163,6 +73173,7 @@ async function appCheck(client, args = {}) {
|
|
|
73163
73173
|
warnIfInertAgentTools(app);
|
|
73164
73174
|
warnIfProjectFootguns(projectDir, sourceText);
|
|
73165
73175
|
warnIfUnbranded(app);
|
|
73176
|
+
warnIfUndescribed(app);
|
|
73166
73177
|
if (!nothingPending(pending)) {
|
|
73167
73178
|
process.exit(1);
|
|
73168
73179
|
}
|
|
@@ -84774,14 +84785,14 @@ function readSourceData(workbook, cache) {
|
|
|
84774
84785
|
if (!range2) return void 0;
|
|
84775
84786
|
const header = [];
|
|
84776
84787
|
for (let col = range2.startCol; col <= range2.endCol; col++) {
|
|
84777
|
-
const cell = sheet.
|
|
84788
|
+
const cell = sheet.getCellAt(range2.startRow, col);
|
|
84778
84789
|
header.push(formatHeader(cell?.value));
|
|
84779
84790
|
}
|
|
84780
84791
|
const records = [];
|
|
84781
84792
|
for (let row = range2.startRow + 1; row <= range2.endRow; row++) {
|
|
84782
84793
|
const rec = [];
|
|
84783
84794
|
for (let col = range2.startCol; col <= range2.endCol; col++) {
|
|
84784
|
-
const cell = sheet.
|
|
84795
|
+
const cell = sheet.getCellAt(row, col);
|
|
84785
84796
|
rec.push(coerceValue(cell?.value));
|
|
84786
84797
|
}
|
|
84787
84798
|
records.push(rec);
|
|
@@ -86325,23 +86336,70 @@ var CellMap = class {
|
|
|
86325
86336
|
get size() {
|
|
86326
86337
|
return this.store.size;
|
|
86327
86338
|
}
|
|
86339
|
+
/**
|
|
86340
|
+
* The populated extent of this map, memoized until a mutation invalidates it.
|
|
86341
|
+
*
|
|
86342
|
+
* It lives HERE rather than on `SheetModel` because it is a pure function of
|
|
86343
|
+
* the cell set: a cache held next to the data cannot outlive it, so replacing
|
|
86344
|
+
* a sheet's map can never serve the previous map's extent. Every mutator
|
|
86345
|
+
* below drops it, including the packed pair — those bypass `set`/`delete`
|
|
86346
|
+
* entirely and are what the row/column commands use.
|
|
86347
|
+
*
|
|
86348
|
+
* Walking PACKED keys is the other half. `keys()` unpacks each key into an A1
|
|
86349
|
+
* string, which the caller then reparsed with a regex, so the old form packed,
|
|
86350
|
+
* unpacked and reparsed the whole sheet on every call — and it is called once
|
|
86351
|
+
* per oversized range during a recompute.
|
|
86352
|
+
*/
|
|
86353
|
+
usedRange() {
|
|
86354
|
+
if (!this.usedRangeCache) {
|
|
86355
|
+
let minRow2 = Infinity;
|
|
86356
|
+
let maxRow2 = 0;
|
|
86357
|
+
let minCol2 = Infinity;
|
|
86358
|
+
let maxCol2 = 0;
|
|
86359
|
+
for (const packed of this.store.keys()) {
|
|
86360
|
+
const { row, col } = unpackRowCol(packed);
|
|
86361
|
+
if (row < minRow2) minRow2 = row;
|
|
86362
|
+
if (row > maxRow2) maxRow2 = row;
|
|
86363
|
+
if (col < minCol2) minCol2 = col;
|
|
86364
|
+
if (col > maxCol2) maxCol2 = col;
|
|
86365
|
+
}
|
|
86366
|
+
this.usedRangeCache = maxRow2 === 0 ? { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 } : { minRow: minRow2, maxRow: maxRow2, minCol: minCol2, maxCol: maxCol2 };
|
|
86367
|
+
}
|
|
86368
|
+
const { minRow, maxRow, minCol, maxCol } = this.usedRangeCache;
|
|
86369
|
+
return { minRow, maxRow, minCol, maxCol };
|
|
86370
|
+
}
|
|
86328
86371
|
get(ref2) {
|
|
86329
86372
|
return this.store.get(packRef(ref2));
|
|
86330
86373
|
}
|
|
86374
|
+
/**
|
|
86375
|
+
* Read by coordinate, skipping the A1 round trip.
|
|
86376
|
+
*
|
|
86377
|
+
* `get(ref)` costs a number→string→regex→number conversion per call, which
|
|
86378
|
+
* is invisible until a caller reads a range cell by cell: on one real
|
|
86379
|
+
* workbook `refToRowCol` alone was 39% of a full recompute. A caller already
|
|
86380
|
+
* holding (row, col) — the range resolver, any grid walk — should use this.
|
|
86381
|
+
*/
|
|
86382
|
+
getAt(row, col) {
|
|
86383
|
+
return this.store.get(packRowCol(row, col));
|
|
86384
|
+
}
|
|
86331
86385
|
set(ref2, cell) {
|
|
86332
86386
|
const key = packRef(ref2);
|
|
86333
86387
|
if (key < 0) return this;
|
|
86334
86388
|
this.store.set(key, cell);
|
|
86389
|
+
this.usedRangeCache = void 0;
|
|
86335
86390
|
return this;
|
|
86336
86391
|
}
|
|
86337
86392
|
has(ref2) {
|
|
86338
86393
|
return this.store.has(packRef(ref2));
|
|
86339
86394
|
}
|
|
86340
86395
|
delete(ref2) {
|
|
86341
|
-
|
|
86396
|
+
const deleted = this.store.delete(packRef(ref2));
|
|
86397
|
+
if (deleted) this.usedRangeCache = void 0;
|
|
86398
|
+
return deleted;
|
|
86342
86399
|
}
|
|
86343
86400
|
clear() {
|
|
86344
86401
|
this.store.clear();
|
|
86402
|
+
this.usedRangeCache = void 0;
|
|
86345
86403
|
}
|
|
86346
86404
|
*keys() {
|
|
86347
86405
|
for (const k of this.store.keys()) yield unpackRef(k);
|
|
@@ -86369,10 +86427,13 @@ var CellMap = class {
|
|
|
86369
86427
|
/** Direct packed-key set, for the same hot loops. */
|
|
86370
86428
|
setPacked(packed, cell) {
|
|
86371
86429
|
this.store.set(packed, cell);
|
|
86430
|
+
this.usedRangeCache = void 0;
|
|
86372
86431
|
}
|
|
86373
86432
|
/** Direct packed-key delete. */
|
|
86374
86433
|
deletePacked(packed) {
|
|
86375
|
-
|
|
86434
|
+
const deleted = this.store.delete(packed);
|
|
86435
|
+
if (deleted) this.usedRangeCache = void 0;
|
|
86436
|
+
return deleted;
|
|
86376
86437
|
}
|
|
86377
86438
|
};
|
|
86378
86439
|
var StyleRegistry = class {
|
|
@@ -86555,6 +86616,10 @@ var SheetModel = class {
|
|
|
86555
86616
|
getCell(ref2) {
|
|
86556
86617
|
return this.cells.get(ref2);
|
|
86557
86618
|
}
|
|
86619
|
+
/** Read by coordinate, skipping the A1 round trip — see `CellMap.getAt`. */
|
|
86620
|
+
getCellAt(row, col) {
|
|
86621
|
+
return this.cells.getAt(row, col);
|
|
86622
|
+
}
|
|
86558
86623
|
/**
|
|
86559
86624
|
* Set cell value with optional inline style. Interns style via the
|
|
86560
86625
|
* StyleRegistry when available. When `style` is supplied and matches the
|
|
@@ -86649,22 +86714,12 @@ var SheetModel = class {
|
|
|
86649
86714
|
this.set(ref2, label, style);
|
|
86650
86715
|
});
|
|
86651
86716
|
}
|
|
86652
|
-
/**
|
|
86717
|
+
/**
|
|
86718
|
+
* The populated extent of the sheet. Derived from — and cached by — the cell
|
|
86719
|
+
* map, so it cannot outlive the data it describes.
|
|
86720
|
+
*/
|
|
86653
86721
|
getUsedRange() {
|
|
86654
|
-
|
|
86655
|
-
let maxRow = 0;
|
|
86656
|
-
let minCol = Infinity;
|
|
86657
|
-
let maxCol = 0;
|
|
86658
|
-
for (const ref2 of this.cells.keys()) {
|
|
86659
|
-
const rc = refToRowCol(ref2);
|
|
86660
|
-
if (!rc) continue;
|
|
86661
|
-
if (rc.row < minRow) minRow = rc.row;
|
|
86662
|
-
if (rc.row > maxRow) maxRow = rc.row;
|
|
86663
|
-
if (rc.col < minCol) minCol = rc.col;
|
|
86664
|
-
if (rc.col > maxCol) maxCol = rc.col;
|
|
86665
|
-
}
|
|
86666
|
-
if (maxRow === 0) return { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 };
|
|
86667
|
-
return { minRow, maxRow, minCol, maxCol };
|
|
86722
|
+
return this.cells.usedRange();
|
|
86668
86723
|
}
|
|
86669
86724
|
};
|
|
86670
86725
|
var WorkbookModel = class {
|
|
@@ -86911,7 +86966,7 @@ function applyAutoFilterHiddenRows(sheet) {
|
|
|
86911
86966
|
let visible = true;
|
|
86912
86967
|
for (const filterCol of activeFilters) {
|
|
86913
86968
|
const col = startCol + filterCol.colIndex;
|
|
86914
|
-
const cell = sheet.
|
|
86969
|
+
const cell = sheet.getCellAt(r, col);
|
|
86915
86970
|
const cellText = cell?.displayValue ?? "";
|
|
86916
86971
|
if (filterCol.filterValues && filterCol.filterValues.length > 0) {
|
|
86917
86972
|
if (!filterCol.filterValues.includes(cellText)) {
|
|
@@ -94141,6 +94196,28 @@ function parseCellKey(key) {
|
|
|
94141
94196
|
const bang = key.indexOf("!");
|
|
94142
94197
|
return { sheet: key.slice(0, bang), ref: key.slice(bang + 1) };
|
|
94143
94198
|
}
|
|
94199
|
+
var COARSE_COL_SUFFIX = ":*";
|
|
94200
|
+
var COARSE_ROW_PREFIX = "*:";
|
|
94201
|
+
function columnKey(sheet, column) {
|
|
94202
|
+
return `${sheet}!${column}${COARSE_COL_SUFFIX}`;
|
|
94203
|
+
}
|
|
94204
|
+
function rowKey(sheet, row) {
|
|
94205
|
+
return `${sheet}!${COARSE_ROW_PREFIX}${row}`;
|
|
94206
|
+
}
|
|
94207
|
+
function isCoarseKey(key) {
|
|
94208
|
+
return key.endsWith(COARSE_COL_SUFFIX) || key.includes(`!${COARSE_ROW_PREFIX}`);
|
|
94209
|
+
}
|
|
94210
|
+
var A1 = /^([A-Z]+)(\d+)$/;
|
|
94211
|
+
function coarseKeysFor(cellKeyStr) {
|
|
94212
|
+
const bang = cellKeyStr.indexOf("!");
|
|
94213
|
+
if (bang < 0) return [];
|
|
94214
|
+
const sheet = cellKeyStr.slice(0, bang);
|
|
94215
|
+
const ref2 = cellKeyStr.slice(bang + 1);
|
|
94216
|
+
const m = A1.exec(ref2);
|
|
94217
|
+
if (!m) return [];
|
|
94218
|
+
return [columnKey(sheet, m[1]), rowKey(sheet, Number(m[2]))];
|
|
94219
|
+
}
|
|
94220
|
+
var EMPTY = /* @__PURE__ */ new Set();
|
|
94144
94221
|
var DependencyGraph = class {
|
|
94145
94222
|
constructor() {
|
|
94146
94223
|
// Forward edges: cell → cells it references (precedents)
|
|
@@ -94175,19 +94252,35 @@ var DependencyGraph = class {
|
|
|
94175
94252
|
}
|
|
94176
94253
|
/** Get all cells that directly depend on the given cell. */
|
|
94177
94254
|
getDirectDependents(cell) {
|
|
94178
|
-
return Array.from(this.
|
|
94255
|
+
return Array.from(this.dependentsOf(cell));
|
|
94256
|
+
}
|
|
94257
|
+
/**
|
|
94258
|
+
* Everything that references this cell — directly, or through the column or
|
|
94259
|
+
* row node that contains it. The single place `dependents` is read, so a
|
|
94260
|
+
* whole-column reference cannot be forgotten by one traversal and honoured by
|
|
94261
|
+
* another.
|
|
94262
|
+
*/
|
|
94263
|
+
dependentsOf(cell) {
|
|
94264
|
+
const direct = this.dependents.get(cell);
|
|
94265
|
+
if (isCoarseKey(cell)) return direct ?? EMPTY;
|
|
94266
|
+
const coarse = coarseKeysFor(cell);
|
|
94267
|
+
let widened;
|
|
94268
|
+
for (const key of coarse) {
|
|
94269
|
+
const via = this.dependents.get(key);
|
|
94270
|
+
if (!via || via.size === 0) continue;
|
|
94271
|
+
widened ??= new Set(direct);
|
|
94272
|
+
for (const d of via) widened.add(d);
|
|
94273
|
+
}
|
|
94274
|
+
return widened ?? direct ?? EMPTY;
|
|
94179
94275
|
}
|
|
94180
94276
|
/** Get all cells that need recalculation when the given cells change, in topological order. */
|
|
94181
94277
|
getRecalcOrder(changed) {
|
|
94182
|
-
const changedSet = new Set(changed);
|
|
94183
94278
|
const affected = /* @__PURE__ */ new Set();
|
|
94184
94279
|
const queue = [...changed];
|
|
94185
94280
|
let head = 0;
|
|
94186
94281
|
while (head < queue.length) {
|
|
94187
94282
|
const cell = queue[head++];
|
|
94188
|
-
const
|
|
94189
|
-
if (!deps) continue;
|
|
94190
|
-
for (const dep of deps) {
|
|
94283
|
+
for (const dep of this.dependentsOf(cell)) {
|
|
94191
94284
|
if (!affected.has(dep)) {
|
|
94192
94285
|
affected.add(dep);
|
|
94193
94286
|
queue.push(dep);
|
|
@@ -94196,15 +94289,11 @@ var DependencyGraph = class {
|
|
|
94196
94289
|
}
|
|
94197
94290
|
if (affected.size === 0) return [];
|
|
94198
94291
|
const inDegree = /* @__PURE__ */ new Map();
|
|
94292
|
+
for (const cell of affected) inDegree.set(cell, 0);
|
|
94199
94293
|
for (const cell of affected) {
|
|
94200
|
-
|
|
94201
|
-
|
|
94202
|
-
if (prec) {
|
|
94203
|
-
for (const p of prec) {
|
|
94204
|
-
if (affected.has(p)) count++;
|
|
94205
|
-
}
|
|
94294
|
+
for (const dep of this.dependentsOf(cell)) {
|
|
94295
|
+
if (affected.has(dep)) inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
|
|
94206
94296
|
}
|
|
94207
|
-
inDegree.set(cell, count);
|
|
94208
94297
|
}
|
|
94209
94298
|
const result = [];
|
|
94210
94299
|
const ready = [];
|
|
@@ -94215,9 +94304,7 @@ var DependencyGraph = class {
|
|
|
94215
94304
|
while (readyHead < ready.length) {
|
|
94216
94305
|
const cell = ready[readyHead++];
|
|
94217
94306
|
result.push(cell);
|
|
94218
|
-
const
|
|
94219
|
-
if (!deps) continue;
|
|
94220
|
-
for (const dep of deps) {
|
|
94307
|
+
for (const dep of this.dependentsOf(cell)) {
|
|
94221
94308
|
if (!affected.has(dep)) continue;
|
|
94222
94309
|
const newDeg = (inDegree.get(dep) ?? 1) - 1;
|
|
94223
94310
|
inDegree.set(dep, newDeg);
|
|
@@ -94238,12 +94325,13 @@ var DependencyGraph = class {
|
|
|
94238
94325
|
const stack = new Array();
|
|
94239
94326
|
const prec = this.precedents.get(cell);
|
|
94240
94327
|
if (!prec) return null;
|
|
94328
|
+
const originCoarse = new Set(coarseKeysFor(cell));
|
|
94241
94329
|
for (const p of prec) {
|
|
94242
94330
|
stack.push({ cell: p, path: [cell, p] });
|
|
94243
94331
|
}
|
|
94244
94332
|
while (stack.length > 0) {
|
|
94245
94333
|
const { cell: current, path: path12 } = stack.pop();
|
|
94246
|
-
if (current === cell) return path12;
|
|
94334
|
+
if (current === cell || originCoarse.has(current)) return path12;
|
|
94247
94335
|
if (visited.has(current)) continue;
|
|
94248
94336
|
visited.add(current);
|
|
94249
94337
|
const nextPrec = this.precedents.get(current);
|
|
@@ -94356,6 +94444,20 @@ function parseTableRange(ref2) {
|
|
|
94356
94444
|
};
|
|
94357
94445
|
}
|
|
94358
94446
|
|
|
94447
|
+
// ../xlsx/src/blank_comparison.ts
|
|
94448
|
+
var import_operators = __toESM(require_operators(), 1);
|
|
94449
|
+
var patched = false;
|
|
94450
|
+
function applyExcelBlankSemantics() {
|
|
94451
|
+
if (patched) return;
|
|
94452
|
+
patched = true;
|
|
94453
|
+
const original = import_operators.Infix.compareOp;
|
|
94454
|
+
import_operators.Infix.compareOp = (value1, infix, value2, isArray1, isArray22) => {
|
|
94455
|
+
if (value1 == null && typeof value2 === "string") value1 = "";
|
|
94456
|
+
if (value2 == null && typeof value1 === "string") value2 = "";
|
|
94457
|
+
return original(value1, infix, value2, isArray1, isArray22);
|
|
94458
|
+
};
|
|
94459
|
+
}
|
|
94460
|
+
|
|
94359
94461
|
// ../xlsx/src/formula_engine.ts
|
|
94360
94462
|
var unwrapArg = (a) => a !== null && typeof a === "object" && "value" in a ? a.value : a;
|
|
94361
94463
|
function adaptFormulajs(fn) {
|
|
@@ -94398,9 +94500,24 @@ function formulajsBridgeWithContext() {
|
|
|
94398
94500
|
bridgeCtxCache = withCtx;
|
|
94399
94501
|
return withCtx;
|
|
94400
94502
|
}
|
|
94503
|
+
var MAX_EXPANDED_CELLS = 1e4;
|
|
94401
94504
|
var FormulaEngine = class _FormulaEngine {
|
|
94402
94505
|
constructor(workbook) {
|
|
94403
94506
|
this.graph = new DependencyGraph();
|
|
94507
|
+
/**
|
|
94508
|
+
* Formula cells (by key) whose text the parser could not read AT ALL, found
|
|
94509
|
+
* while building the graph from a loaded file.
|
|
94510
|
+
*
|
|
94511
|
+
* They are held apart from every other failure because the honest response
|
|
94512
|
+
* differs: a formula that evaluates TO an error has been understood, so
|
|
94513
|
+
* writing `#DIV/0!` reports a real answer, while one we cannot parse has not
|
|
94514
|
+
* been — and overwriting the value its author computed would replace a
|
|
94515
|
+
* correct number with a guess. These keep their cached value and are named in
|
|
94516
|
+
* the report instead. The engine's own rule, one level down: degrade what you
|
|
94517
|
+
* cannot draw, refuse what you cannot read.
|
|
94518
|
+
*/
|
|
94519
|
+
this.unreadableFormulas = /* @__PURE__ */ new Set();
|
|
94520
|
+
applyExcelBlankSemantics();
|
|
94404
94521
|
this.workbook = workbook;
|
|
94405
94522
|
this.parser = new import_fast_formula_parser.default({
|
|
94406
94523
|
onCell: (ref2) => this.resolveCell(ref2),
|
|
@@ -94421,11 +94538,13 @@ var FormulaEngine = class _FormulaEngine {
|
|
|
94421
94538
|
/** Build dependency graph from all formula cells in the workbook. */
|
|
94422
94539
|
buildGraph() {
|
|
94423
94540
|
this.graph.clear();
|
|
94541
|
+
this.unreadableFormulas.clear();
|
|
94424
94542
|
for (const sheet of this.workbook.sheets) {
|
|
94425
94543
|
for (const [ref2, cell] of sheet.cells) {
|
|
94426
94544
|
if (!cell.formula) continue;
|
|
94427
94545
|
const key = cellKey({ sheet: sheet.name, ref: ref2 });
|
|
94428
|
-
const deps = this.
|
|
94546
|
+
const { deps, readable } = this.analyzeFormula(cell.formula, sheet.name);
|
|
94547
|
+
if (!readable) this.unreadableFormulas.add(key);
|
|
94429
94548
|
this.graph.setDependencies(key, deps);
|
|
94430
94549
|
}
|
|
94431
94550
|
}
|
|
@@ -94481,8 +94600,13 @@ var FormulaEngine = class _FormulaEngine {
|
|
|
94481
94600
|
onCellChanged(sheetName, ref2, formula) {
|
|
94482
94601
|
const key = cellKey({ sheet: sheetName, ref: ref2 });
|
|
94483
94602
|
if (formula) {
|
|
94484
|
-
const deps = this.
|
|
94603
|
+
const { deps, readable } = this.analyzeFormula(formula, sheetName);
|
|
94485
94604
|
this.graph.setDependencies(key, deps);
|
|
94605
|
+
this.unreadableFormulas.delete(key);
|
|
94606
|
+
if (!readable) {
|
|
94607
|
+
this.setCellError(key, "#ERROR!");
|
|
94608
|
+
return;
|
|
94609
|
+
}
|
|
94486
94610
|
const cycle = this.graph.detectCycle(key);
|
|
94487
94611
|
if (cycle) {
|
|
94488
94612
|
this.setCellError(key, "#CIRC!");
|
|
@@ -94491,6 +94615,7 @@ var FormulaEngine = class _FormulaEngine {
|
|
|
94491
94615
|
this.evaluateCell(key);
|
|
94492
94616
|
} else {
|
|
94493
94617
|
this.graph.removeCell(key);
|
|
94618
|
+
this.unreadableFormulas.delete(key);
|
|
94494
94619
|
}
|
|
94495
94620
|
const order = this.graph.getRecalcOrder([key]);
|
|
94496
94621
|
for (const depKey of order) {
|
|
@@ -94507,6 +94632,7 @@ var FormulaEngine = class _FormulaEngine {
|
|
|
94507
94632
|
// ===========================================================================
|
|
94508
94633
|
/** Evaluate a single formula cell and update its value. */
|
|
94509
94634
|
evaluateCell(key) {
|
|
94635
|
+
if (this.unreadableFormulas.has(key)) return;
|
|
94510
94636
|
const { sheet: sheetName, ref: ref2 } = parseCellKey(key);
|
|
94511
94637
|
const sheetIndex = this.workbook.sheets.findIndex((s) => s.name === sheetName);
|
|
94512
94638
|
if (sheetIndex < 0) return;
|
|
@@ -94555,26 +94681,60 @@ var FormulaEngine = class _FormulaEngine {
|
|
|
94555
94681
|
// ===========================================================================
|
|
94556
94682
|
/** Extract cell/range references from a formula string. Returns empty array for unparseable formulas. */
|
|
94557
94683
|
extractDependencies(formula, currentSheet) {
|
|
94558
|
-
|
|
94559
|
-
|
|
94560
|
-
|
|
94684
|
+
return this.analyzeFormula(formula, currentSheet).deps;
|
|
94685
|
+
}
|
|
94686
|
+
/**
|
|
94687
|
+
* Dependencies plus whether the parser could read the formula at all.
|
|
94688
|
+
*
|
|
94689
|
+
* `fast-formula-parser` reports a formula it cannot LEX by throwing, the same
|
|
94690
|
+
* channel it uses for `#VALUE!`, so the lexer failure of a single cell used to
|
|
94691
|
+
* escape `buildGraph` and abort the recompute for the entire workbook —
|
|
94692
|
+
* observed on a real workbook whose `VLOOKUP` pointed at another file
|
|
94693
|
+
* (`'[1]DATA'!B:C`, an external-workbook reference the lexer has no rule for).
|
|
94694
|
+
* Anything the grammar does not cover lands here the same way, so this catches
|
|
94695
|
+
* the class rather than that one syntax.
|
|
94696
|
+
*/
|
|
94697
|
+
analyzeFormula(formula, currentSheet) {
|
|
94698
|
+
let deps;
|
|
94699
|
+
try {
|
|
94700
|
+
const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
|
|
94701
|
+
const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
|
|
94702
|
+
deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
|
|
94703
|
+
} catch {
|
|
94704
|
+
return { deps: [], readable: false };
|
|
94705
|
+
}
|
|
94561
94706
|
const refs = [];
|
|
94562
94707
|
for (const dep of deps) {
|
|
94563
94708
|
const depSheet = dep.sheet ?? currentSheet;
|
|
94564
94709
|
if (dep.from && dep.to) {
|
|
94565
|
-
const
|
|
94566
|
-
|
|
94567
|
-
|
|
94568
|
-
|
|
94710
|
+
const rows = dep.to.row - dep.from.row + 1;
|
|
94711
|
+
const cols = dep.to.col - dep.from.col + 1;
|
|
94712
|
+
if (rows * cols > MAX_EXPANDED_CELLS) {
|
|
94713
|
+
if (cols <= rows) {
|
|
94714
|
+
for (let c = dep.from.col; c <= dep.to.col; c++) {
|
|
94715
|
+
refs.push(columnKey(depSheet, colNumToLetters(c)));
|
|
94716
|
+
}
|
|
94717
|
+
} else {
|
|
94718
|
+
for (let r = dep.from.row; r <= dep.to.row; r++) {
|
|
94719
|
+
refs.push(rowKey(depSheet, r));
|
|
94720
|
+
}
|
|
94721
|
+
}
|
|
94722
|
+
continue;
|
|
94723
|
+
}
|
|
94724
|
+
for (let r = dep.from.row; r <= dep.to.row; r++) {
|
|
94725
|
+
for (let c = dep.from.col; c <= dep.to.col; c++) {
|
|
94569
94726
|
refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(r, c) }));
|
|
94570
|
-
count++;
|
|
94571
94727
|
}
|
|
94572
94728
|
}
|
|
94573
94729
|
} else if (dep.row != null && dep.col != null) {
|
|
94574
94730
|
refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(dep.row, dep.col) }));
|
|
94575
94731
|
}
|
|
94576
94732
|
}
|
|
94577
|
-
return refs;
|
|
94733
|
+
return { deps: refs, readable: true };
|
|
94734
|
+
}
|
|
94735
|
+
/** Formula cells the parser could not read, by key. Read by the recompute report. */
|
|
94736
|
+
unreadable() {
|
|
94737
|
+
return this.unreadableFormulas;
|
|
94578
94738
|
}
|
|
94579
94739
|
// ===========================================================================
|
|
94580
94740
|
// Cell value resolution callbacks
|
|
@@ -94611,8 +94771,7 @@ var FormulaEngine = class _FormulaEngine {
|
|
|
94611
94771
|
for (let r = ref2.from.row; r <= lastRow; r++) {
|
|
94612
94772
|
const row = [];
|
|
94613
94773
|
for (let c = ref2.from.col; c <= lastCol; c++) {
|
|
94614
|
-
|
|
94615
|
-
row.push(cell?.value ?? null);
|
|
94774
|
+
row.push(sheet.getCellAt(r, c)?.value ?? null);
|
|
94616
94775
|
}
|
|
94617
94776
|
result.push(row);
|
|
94618
94777
|
}
|
|
@@ -94696,15 +94855,19 @@ function recomputeAll(workbook) {
|
|
|
94696
94855
|
const engine = new FormulaEngine(workbook);
|
|
94697
94856
|
engine.buildGraph();
|
|
94698
94857
|
engine.evaluateAll();
|
|
94699
|
-
return collectRecomputeReport(workbook);
|
|
94858
|
+
return collectRecomputeReport(workbook, engine.unreadable());
|
|
94700
94859
|
}
|
|
94701
|
-
function collectRecomputeReport(workbook) {
|
|
94860
|
+
function collectRecomputeReport(workbook, unreadable) {
|
|
94702
94861
|
let total = 0;
|
|
94703
94862
|
const unevaluated = [];
|
|
94704
94863
|
for (const sheet of workbook.sheets) {
|
|
94705
94864
|
for (const [ref2, cell] of sheet.cells) {
|
|
94706
94865
|
if (!cell.formula) continue;
|
|
94707
94866
|
total++;
|
|
94867
|
+
if (unreadable.has(cellKey({ sheet: sheet.name, ref: ref2 }))) {
|
|
94868
|
+
unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: "#UNREADABLE!" });
|
|
94869
|
+
continue;
|
|
94870
|
+
}
|
|
94708
94871
|
if (cell.value === null) {
|
|
94709
94872
|
unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: cell.error ?? "#ERROR!" });
|
|
94710
94873
|
}
|
|
@@ -95642,9 +95805,9 @@ function applySheetCells(workbook, sheetIndex, def) {
|
|
|
95642
95805
|
}
|
|
95643
95806
|
}
|
|
95644
95807
|
if (def.rowHeights) {
|
|
95645
|
-
for (const [
|
|
95646
|
-
const row = Number(
|
|
95647
|
-
if (!Number.isInteger(row) || row < 1) fail2(`Invalid row in sheet "${def.name}".rowHeights: ${
|
|
95808
|
+
for (const [rowKey2, height] of Object.entries(def.rowHeights)) {
|
|
95809
|
+
const row = Number(rowKey2);
|
|
95810
|
+
if (!Number.isInteger(row) || row < 1) fail2(`Invalid row in sheet "${def.name}".rowHeights: ${rowKey2}`);
|
|
95648
95811
|
sheet.rowHeights.set(row, height);
|
|
95649
95812
|
}
|
|
95650
95813
|
}
|
package/dist/src/client.d.ts
CHANGED
|
@@ -224,6 +224,10 @@ export declare class LoticsClient {
|
|
|
224
224
|
getApp(app_id: string): Promise<{
|
|
225
225
|
id: string;
|
|
226
226
|
name: string;
|
|
227
|
+
/** What the app is for. Heads the capability catalog the member's chat agent
|
|
228
|
+
* reads every turn the app is open, so it is where a standing process is
|
|
229
|
+
* stated. Null when unset. */
|
|
230
|
+
description?: string | null;
|
|
227
231
|
workspace_id: string;
|
|
228
232
|
current_version_id: string | null;
|
|
229
233
|
/** Launcher icon — a Lucide name, or an image ref. Null when unset. */
|
package/docs/building_an_app.md
CHANGED
|
@@ -109,6 +109,13 @@ writes the schema back into the manifest and refreshes that alias's types in pla
|
|
|
109
109
|
The alias's `description` rides along from the manifest. It is the one line an agent reads when
|
|
110
110
|
choosing between your workflows, so write it rather than leaving the generated placeholder.
|
|
111
111
|
|
|
112
|
+
**Every workflow you declare is also the chat agent's write surface.** So the alias's *shape* is an
|
|
113
|
+
agent-facing decision, not only a screen-facing one — take a list where one job covers many
|
|
114
|
+
records; say in an optional input's own `description` what omitting it means, since the agent
|
|
115
|
+
cannot see the default your body applies; make the write survive running twice on the same input;
|
|
116
|
+
and gate anything irreversible with `wait_for_approval` inside the body. `lotics docs ai` and
|
|
117
|
+
`lotics docs workflows` carry each of those.
|
|
118
|
+
|
|
112
119
|
**Static green is not a run.** `check` and `set` prove parse, types, name resolution and lint —
|
|
113
120
|
they evaluate nothing. Rehearse before the first live run:
|
|
114
121
|
|
|
@@ -196,12 +203,19 @@ lotics app deploy -m "<what changed + why>"
|
|
|
196
203
|
`-m` is optional; the deploy derives a message from what it pushed. Write one when the *why* is
|
|
197
204
|
worth keeping.
|
|
198
205
|
|
|
199
|
-
Then set the icon
|
|
206
|
+
Then set the icon, the colour and the app's own `description` — the most-forgotten step, which is
|
|
207
|
+
why `deploy` and `check` both warn while any of them is unset:
|
|
200
208
|
|
|
201
209
|
```
|
|
202
|
-
lotics run update_app '{"app_id":"…","icon":"<lucide-name>","theme":{"color":"blue"}
|
|
210
|
+
lotics run update_app '{"app_id":"…","icon":"<lucide-name>","theme":{"color":"blue"},
|
|
211
|
+
"description":"<what the app is for, and the standing job it does>"}'
|
|
203
212
|
```
|
|
204
213
|
|
|
214
|
+
The `description` is not a label. It heads the capability listing the member's chat agent reads on
|
|
215
|
+
**every** turn, so a standing process the app expects that agent to carry out belongs there and
|
|
216
|
+
nowhere else — not in workspace instructions and not in a knowledge doc, both of which apply to
|
|
217
|
+
every app at once (`lotics docs ai`).
|
|
218
|
+
|
|
205
219
|
Finally, update the app's own `README.md` on any model or behaviour change and redeploy, so the
|
|
206
220
|
brief travels with the app rather than living in whoever built it.
|
|
207
221
|
|
|
@@ -223,7 +237,7 @@ lotics app deploy -m "…" # pushes pending bindings, then ships
|
|
|
223
237
|
```
|
|
224
238
|
|
|
225
239
|
A deploy pushes any workflow body, agent prose or query that is ahead of the app **before** it
|
|
226
|
-
ships the bundle, so
|
|
227
|
-
|
|
240
|
+
ships the bundle, so a forgotten `set` cannot ship a bundle typed against a binding that does not
|
|
241
|
+
exist. `lotics app check` reports the same set without pushing.
|
|
228
242
|
|
|
229
243
|
Keep the CLI current: an old one silently drops manifest fields it does not model.
|
package/docs/cli_reference.md
CHANGED
|
@@ -37,7 +37,7 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
|
|
|
37
37
|
| `lotics upgrade` | Take the installed package's next version for the app in this directory (`app_id` from the local manifest — and the WORKSPACE from the same manifest, exactly as `lotics app *` does, so the command that knows which workspace it belongs to never rides the ambient profile). Announces its target on stderr before acting. **Applies the version it PREVIEWED**, not "latest" re-resolved server-side, so a release landing mid-command cannot install a contract whose diff was never checked. **Previews first and applies only a CLEAN upgrade**: a breaking contract change, a locally modified artifact, binding drift, or bundled knowledge needing consent (all FOUR sources the preview returns — the server throws for each, so omitting one only swapped the framed refusal for a raw wire 400) is reported per item and REFUSED with exit 1, because each resolves by choosing what to keep and a guess discards work nobody asked to lose. Already-current is a no-op that says so. On success it names the new version and the changelog, and points at `lotics app pull` to bring the checkout in step. The resolutions flow for a conflicted upgrade stays in `opctl` — that case needs a person, and the person is an operator. |
|
|
38
38
|
| `lotics docs` \| `lotics docs <area>` | The index of the reference docs, **resolved out of the packages installed beside this project** — never carried by this CLI. **Both levels are discovered by looking**: every `@lotics/*` package carrying an `AGENTS.md` or a `docs/` in any `node_modules/@lotics` from the current directory UPWARD (nearest wins, so a hoisted root copy never shadows the one a project's own imports resolve to — checking only cwd made an agent editing `src/App.tsx`, or anyone on a workspaces/pnpm layout, see app-sdk and ui vanish while the `@lotics/cli` fallback kept the index looking complete) (six do today — app-sdk, ui, cli, docx, ooxml, xlsx), and within each, every area it actually ships. Titles come from each file's own `# heading` and the version from the installed `package.json`, so a doc OR a whole package added upstream appears with no change to this CLI, and a skewed install is visible rather than reassuring. A package's index is named after the package (`lotics docs ui`), never `index` — every package has one, so a shared name would make the most obvious query the one guaranteed to be ambiguous. `@lotics/app-sdk`, `@lotics/ui` and `@lotics/cli` sort first as a reading ORDER, not a filter. Bundling them here instead would print the contract of whichever kit version this CLI was built against — the kit publishes several times a day — and serving them from an endpoint would answer `latest` to a project on a pin: both are current-and-wrong, which is the one failure npm already prevents. Both the index and `<area>` print to **stdout** — the index is the payload of a bare `lotics docs`, so `lotics docs | grep -i excel` works — with only the provenance line on stderr, so `lotics docs ai > ai.md` is the doc alone; a name two packages share is refused with both qualified forms (`lotics docs ui/templates`) rather than resolved silently. Needs no auth — the references describe the product, not a workspace, so a key that will not resolve is exactly when someone reaches for them. Outside a project only `@lotics/cli`'s own resolve, and it says so. |
|
|
39
39
|
| `lotics report '<json>'` \| `lotics report @report.json` | File a report with the Lotics team about what got in your way. **Covers the classes telemetry structurally cannot see**: a capability that does not exist (no command ran, so nothing was recorded), a command that exited 0 having done the wrong thing, an error whose message did not name the remedy, and anything that made authoring slower than it should be. **A frame, not a paragraph** — `{goal, actual, expected?, tried?, wanted?}`, `goal` and `actual` required, unknown keys dropped rather than refused. The frame exists because a session join reconstructs what was RUN and can never reconstruct what was WANTED: "the deploy warned twice" is unactionable, the same line under a goal names the defect. Each field becomes its own log attribute, so `goal` clusters across sessions instead of being grepped out of prose. **No severity or category** — those are judgements rather than facts, derivable at analysis time (a report followed by more commands on the same task shows a workaround; one followed by nothing shows a dead end), and a taxonomy shipped in a released binary cannot be corrected without a release. Ingest is inline JSON, `@file`, or `-` for stdin. The explicit `-` is the one place this departs from `run`, and it has to: `run` reaches stdin only after a tool name, while a report's single positional IS the payload, so bare and piped are the same argv. `isTTY` cannot separate them — it is falsy for a pipe a supervisor holds open and never writes to — so guessing hangs the bare form forever, and a grace period instead drops the report of any writer slower than the timer. A bare sentence is refused with the frame printed beside it, so the fix is one step; a bare invocation prints the frame BEFORE asking for a credential, since someone whose key will not resolve is exactly who has something to report. **Not spooled**: unlike telemetry it posts inline, prints whether it landed, and exits non-zero if it did not, echoing the report back so a failed send never loses it. Runs regardless of `LOTICS_TELEMETRY` — invoking it IS the consent that passive collection needs an opt-in for — but with telemetry off there are no recorded commands to attach, and it says so rather than implying context it does not have. Requires auth. Never paste records, file contents, or credentials. |
|
|
40
|
-
| `lotics app check` | Every pre-flight `deploy` runs, WITHOUT building or shipping: the manifest's agent schemas against the live app row, every binding a deploy would push, aliases the source calls that nothing bound (queries, workflows AND agents), bindings the app serves that the source names nowhere, capability-gated SDK calls the manifest doesn't declare, a missing icon/theme, a `vite.config.ts` that never defines `global`/`__DEV__` and a `window.open` in the app's own source (both fail ONLY in the deployed app — dev bundles with esbuild and production with rollup, so typecheck, lint, build and `app dev` are all green while react-native-web reads `global.cancelAnimationFrame` as a free variable and the sandboxed iframe drops a popup silently), an agent holding `run_app_query`/`run_app_workflow` with an EMPTY `query_aliases`/`workflow_aliases` (the tool is the capability, the alias list is the reach — empty means every call it makes is refused while the run still COMPLETES, so it surfaces as a model ignoring its prompt; read off the live row, never the manifest, which mirrors those fields but is pushed by no verb), and a notice for any alias the source computes at runtime (invisible to every check here and to the deploy's unbind guard). Adds no rule of its own — each finding is the same helper `deploy` calls, so a green check means a deploy will not complain. **Exits 1 on what a `deploy` would REFUSE or PUSH** — an agent schema that disagrees with the live app, and any binding the project has ahead of the app (an edited workflow body or declaration, edited agent prose, a changed query). Both are things a deploy would act on, so CI gating on a green check means a deploy has nothing left to do; genuine advisories (capabilities, branding, a runtime-computed alias, orphaned bindings) stay advisory and never fail it. The point is the question being ASKABLE: these checks used to cost a build, a tar, an upload and a version row in the audit trail, which is expensive enough that the honest move was to skip them and find out in production. |
|
|
40
|
+
| `lotics app check` | Every pre-flight `deploy` runs, WITHOUT building or shipping: the manifest's agent schemas against the live app row, every binding a deploy would push, aliases the source calls that nothing bound (queries, workflows AND agents), bindings the app serves that the source names nowhere, capability-gated SDK calls the manifest doesn't declare, a missing icon/theme, a missing app `description` (it heads the capability catalog the chat agent reads every turn, and its absence has no other symptom), a `vite.config.ts` that never defines `global`/`__DEV__` and a `window.open` in the app's own source (both fail ONLY in the deployed app — dev bundles with esbuild and production with rollup, so typecheck, lint, build and `app dev` are all green while react-native-web reads `global.cancelAnimationFrame` as a free variable and the sandboxed iframe drops a popup silently), an agent holding `run_app_query`/`run_app_workflow` with an EMPTY `query_aliases`/`workflow_aliases` (the tool is the capability, the alias list is the reach — empty means every call it makes is refused while the run still COMPLETES, so it surfaces as a model ignoring its prompt; read off the live row, never the manifest, which mirrors those fields but is pushed by no verb), and a notice for any alias the source computes at runtime (invisible to every check here and to the deploy's unbind guard). Adds no rule of its own — each finding is the same helper `deploy` calls, so a green check means a deploy will not complain. **Exits 1 on what a `deploy` would REFUSE or PUSH** — an agent schema that disagrees with the live app, and any binding the project has ahead of the app (an edited workflow body or declaration, edited agent prose, a changed query). Both are things a deploy would act on, so CI gating on a green check means a deploy has nothing left to do; genuine advisories (capabilities, branding, a runtime-computed alias, orphaned bindings) stay advisory and never fail it. The point is the question being ASKABLE: these checks used to cost a build, a tar, an upload and a version row in the audit trail, which is expensive enough that the honest move was to skip them and find out in production. |
|
|
41
41
|
| `lotics app workflow run <alias> '<json>'` | Execute a bound app workflow end-to-end via `appWorkflow`. `app_id` comes from the local manifest; the alias must be bound (`set_app_workflow`). Inputs ingest exactly like `lotics run` (inline JSON / `@file` / stdin — bulk inputs bypass `ARG_MAX`). Prints the full `{status,message,data,files,side_effects}` JSON to stdout + a one-line summary to stderr; exits non-zero on `status:"error"` (assertable). `--print-created` (alias `--report-effects`) renders the honest post-run harvest: created records grouped by table, a paste-ready `lotics run delete_records …` per table, then the **mandatory caveat** naming what cannot be auto-undone (external integrations + notifications) and that sub-workflows may have run. `--cleanup` (DEFAULT OFF, implies the report) additionally runs the deletes for harvested records ONLY — never files / external / notifications. Neither is a rollback — a rollback is structurally impossible here. |
|
|
42
42
|
| `lotics app workflow set <alias>` | Push the edited `src/workflows/<alias>.ts` body through `set_app_workflow` (the single author of `apps.workflows`). Reads the body from disk (header + `/// <reference>` + `export {};` marker + the `__workflow` wrapper all stripped) + the typed `inputs`/`outputs` **and the `description`** from `package.json#lotics.workflows.<alias>`; the **server** re-verifies the body and echoes the bound `outputs` (declared, else DERIVED from `return({ data })`). The `description` is the one line an agent reads when choosing between the app's aliases (the workflow counterpart to a query's) — authored in the manifest so it lives beside the body in version control and rides every push; omit it and the workflow keeps whatever description it already has, so a push can never blank one set elsewhere. When the manifest declared NO `outputs`, the DERIVED echo is written back into `package.json#lotics.workflows.<alias>.outputs` (a SURGICAL write — preserves `knowledge`/`config` and every other manifest field) and that alias's types are refreshed in place, so `useWorkflow("<alias>")`'s `result.data` is typed immediately with no hand-copy and no second `lotics app codegen`; an explicitly-declared `outputs` is authoritative and never overwritten. Deploy still never authors workflows — this is a CLI convenience over the existing tool. Clear error + non-zero exit on a missing file, an alias absent from the manifest, or a verify failure. |
|
|
43
43
|
| `lotics app agent set <alias>` | Push `src/agents/<alias>.md` — plus `inputs`/`outputs` when `package.json#lotics.agents.<alias>` declares them — through `set_app_agent`. The agent mirror of `app workflow set`, and the deploy-free authoring path for an agent's prose and its typed edges. **It sends only those fields.** Everything else is absent, and absent means unchanged, so a declaration this CLI does not model cannot be reverted by a push from a checkout that predates it — the chat authoring agent's `knowledge_doc_ids`, another operator's `query_aliases` grant. To change one of those, call `set_app_agent` with just that field (`lotics run set_app_agent '{"app_id":…,"alias":…,"tool_names":[…]}'` — it merges), then `app pull` to bring the manifest back in step. **CREATES the alias when the app has not bound one yet**, so a new agent is authored the same way a new workflow is: write the prose, declare the typed half, push. A create needs the prose file (an agent without instructions is not an agent); it is gated on nothing else, because what keeps a binding alive is a `useAppAgentRun("<alias>")` call site in the shipped bundle — a deploy prunes an agent the bundle never names, manifest entry or not. The prose push is a conditional write against the fingerprint this project last saw, so it is refused rather than allowed to overwrite prose someone else changed. Clear error + non-zero exit when there is no prose file and nothing declared to push instead, when a create has no prose to create from, or when the file is empty once the header is stripped. |
|