@ai-matrx/design-system 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/data-table/copy-helpers.d.cts +3 -2
  3. package/dist/data-table/copy-helpers.d.ts +3 -2
  4. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  5. package/dist/data-table/data-row-inspector.js.map +1 -1
  6. package/dist/data-table/data-row-window.cjs.map +1 -1
  7. package/dist/data-table/data-row-window.js.map +1 -1
  8. package/dist/data-table/filter-engine.d.cts +3 -2
  9. package/dist/data-table/filter-engine.d.ts +3 -2
  10. package/dist/data-table/host.cjs.map +1 -1
  11. package/dist/data-table/host.d.cts +19 -5
  12. package/dist/data-table/host.d.ts +19 -5
  13. package/dist/data-table/host.js.map +1 -1
  14. package/dist/data-table/index.cjs +2080 -1655
  15. package/dist/data-table/index.cjs.map +1 -1
  16. package/dist/data-table/index.d.cts +5 -5
  17. package/dist/data-table/index.d.ts +5 -5
  18. package/dist/data-table/index.js +2080 -1655
  19. package/dist/data-table/index.js.map +1 -1
  20. package/dist/data-table/infer-filter.d.cts +3 -2
  21. package/dist/data-table/infer-filter.d.ts +3 -2
  22. package/dist/data-table/layered-filters.d.cts +3 -2
  23. package/dist/data-table/layered-filters.d.ts +3 -2
  24. package/dist/data-table/menu-targets.cjs +27 -0
  25. package/dist/data-table/menu-targets.cjs.map +1 -1
  26. package/dist/data-table/menu-targets.d.cts +49 -1
  27. package/dist/data-table/menu-targets.d.ts +49 -1
  28. package/dist/data-table/menu-targets.js +27 -0
  29. package/dist/data-table/menu-targets.js.map +1 -1
  30. package/dist/data-table/pagination.cjs.map +1 -1
  31. package/dist/data-table/pagination.js.map +1 -1
  32. package/dist/data-table/query-control.d.cts +3 -2
  33. package/dist/data-table/query-control.d.ts +3 -2
  34. package/dist/data-table/spreadsheet.cjs +24 -8
  35. package/dist/data-table/spreadsheet.cjs.map +1 -1
  36. package/dist/data-table/spreadsheet.d.cts +20 -3
  37. package/dist/data-table/spreadsheet.d.ts +20 -3
  38. package/dist/data-table/spreadsheet.js +24 -8
  39. package/dist/data-table/spreadsheet.js.map +1 -1
  40. package/dist/data-table/types.cjs.map +1 -1
  41. package/dist/data-table/types.d.cts +3 -2
  42. package/dist/data-table/types.d.ts +3 -2
  43. package/dist/data-table/url-state.d.cts +3 -2
  44. package/dist/data-table/url-state.d.ts +3 -2
  45. package/dist/data-table/uuid-cell.cjs.map +1 -1
  46. package/dist/data-table/uuid-cell.js.map +1 -1
  47. package/dist/data-table/validation.d.cts +1 -1
  48. package/dist/data-table/validation.d.ts +1 -1
  49. package/dist/data-table/xlsx.d.cts +3 -2
  50. package/dist/data-table/xlsx.d.ts +3 -2
  51. package/dist/field-formats/index.cjs +269 -5
  52. package/dist/field-formats/index.cjs.map +1 -1
  53. package/dist/field-formats/index.d.cts +2 -2
  54. package/dist/field-formats/index.d.ts +2 -2
  55. package/dist/field-formats/index.js +269 -5
  56. package/dist/field-formats/index.js.map +1 -1
  57. package/dist/formulas/index.cjs +38 -5
  58. package/dist/formulas/index.cjs.map +1 -1
  59. package/dist/formulas/index.d.cts +39 -2
  60. package/dist/formulas/index.d.ts +39 -2
  61. package/dist/formulas/index.js +38 -5
  62. package/dist/formulas/index.js.map +1 -1
  63. package/dist/{types-CJWU1Gae.d.cts → types-JnZD8wM0.d.cts} +27 -2
  64. package/dist/{types-CJWU1Gae.d.ts → types-JnZD8wM0.d.ts} +27 -2
  65. package/dist/{types-Z32pxexx.d.ts → types-afUz93xo.d.ts} +34 -2
  66. package/dist/{types-oVyZbfwn.d.cts → types-q5ficDSo.d.cts} +34 -2
  67. package/package.json +1 -1
@@ -24,9 +24,12 @@ __export(formulas_exports, {
24
24
  evaluateFormula: () => evaluateFormula,
25
25
  formulaColumnsOf: () => formulaColumnsOf,
26
26
  formulaResultType: () => formulaResultType,
27
+ isAutonumberColumn: () => isAutonumberColumn,
28
+ isComputedColumn: () => isComputedColumn,
27
29
  isFormulaColumn: () => isFormulaColumn,
28
30
  parseFormula: () => parseFormula,
29
31
  rewriteFormulaReferences: () => rewriteFormulaReferences,
32
+ systemColumnKindOf: () => systemColumnKindOf,
30
33
  withComputedColumns: () => withComputedColumns
31
34
  });
32
35
  module.exports = __toCommonJS(formulas_exports);
@@ -952,6 +955,23 @@ function formulaExpressionOf(field) {
952
955
  function isFormulaColumn(field) {
953
956
  return formulaExpressionOf(field) !== null;
954
957
  }
958
+ function systemColumnKindOf(field) {
959
+ const metadata = field.metadata;
960
+ if (!metadata || typeof metadata !== "object") return null;
961
+ const format = metadata.format;
962
+ if (!format || typeof format !== "object") return null;
963
+ const id = format.id;
964
+ return id === "created_time" || id === "modified_time" ? id : null;
965
+ }
966
+ function isComputedColumn(field) {
967
+ return isFormulaColumn(field) || systemColumnKindOf(field) !== null || isAutonumberColumn(field);
968
+ }
969
+ function isAutonumberColumn(field) {
970
+ const metadata = field.metadata;
971
+ if (!metadata || typeof metadata !== "object") return false;
972
+ const format = metadata.format;
973
+ return !!format && typeof format === "object" && format.id === "autonumber";
974
+ }
955
975
  function formulaColumnsOf(fields) {
956
976
  return fields.flatMap((field) => {
957
977
  const expression = formulaExpressionOf(field);
@@ -959,11 +979,20 @@ function formulaColumnsOf(fields) {
959
979
  return [{ field, parsed: parseFormula(expression) }];
960
980
  });
961
981
  }
962
- function withComputedColumns(rows, fields) {
982
+ function withComputedColumns(rows, fields, displayValueOf) {
963
983
  const columns = formulaColumnsOf(fields);
964
- const formulaFieldNames = new Set(columns.map((c) => c.field.field_name));
984
+ const systemColumns = fields.flatMap((field) => {
985
+ const kind = systemColumnKindOf(field);
986
+ return kind ? [{ field, kind }] : [];
987
+ });
988
+ const formulaFieldNames = /* @__PURE__ */ new Set([
989
+ ...columns.map((c) => c.field.field_name),
990
+ ...systemColumns.map((c) => c.field.field_name),
991
+ // Stored, not injected — but every write path must skip it all the same.
992
+ ...fields.filter(isAutonumberColumn).map((f) => f.field_name)
993
+ ]);
965
994
  const errors = /* @__PURE__ */ new Map();
966
- if (columns.length === 0) {
995
+ if (columns.length === 0 && systemColumns.length === 0) {
967
996
  return { rows, errors, formulaFieldNames };
968
997
  }
969
998
  const fieldNameByReference = /* @__PURE__ */ new Map();
@@ -975,12 +1004,16 @@ function withComputedColumns(rows, fields) {
975
1004
  }
976
1005
  const computed = rows.map((row) => {
977
1006
  const data = { ...row.data ?? {} };
1007
+ const shown = (fieldName, raw) => displayValueOf ? displayValueOf(fieldName, raw) : raw;
978
1008
  const resolve = (name) => {
979
- if (name in data) return data[name];
1009
+ if (name in data) return shown(name, data[name]);
980
1010
  const fieldName = fieldNameByReference.get(name.toLowerCase());
981
1011
  if (fieldName === void 0) return void 0;
982
- return data[fieldName] ?? null;
1012
+ return shown(fieldName, data[fieldName] ?? null);
983
1013
  };
1014
+ for (const { field, kind } of systemColumns) {
1015
+ data[field.field_name] = (kind === "created_time" ? row.created_at : row.updated_at) ?? null;
1016
+ }
984
1017
  for (const { field, parsed } of columns) {
985
1018
  if (!parsed.ok) {
986
1019
  data[field.field_name] = null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/formulas/index.ts","../../src/formulas/formulas.ts"],"sourcesContent":["export * from \"./formulas\";\n","/**\n * @ai-matrx/design-system/formulas — THE formula language for computed columns.\n *\n * This module moved here from the app so every table in the product shares\n * one formula language. It stays pure: no React, no DOM, no network.\n *\n * A **formula column** holds nothing. Its cell is always `null` in the\n * database; the value the user sees is computed, on read, from the OTHER\n * columns of the SAME row. Champion: Airtable's formula field.\n *\n * THE THREE RULES THIS FILE OBEYS\n *\n * 1. **No `eval`, no `new Function`, no dynamic code.** A formula is user\n * content; it is tokenized, parsed into an AST, and interpreted. Nothing a\n * user types ever becomes JavaScript.\n * 2. **Nothing throws.** `parseFormula` and `evaluateFormula` both return a\n * discriminated result. A syntax error, a missing column, a division by\n * zero and a type mismatch are all `ok: false` with a plain-English\n * sentence a non-technical person can act on — never an exception, never a\n * silent blank cell (THE FALLBACK LAW, `lib/field-formats/FEATURE.md`).\n * 3. **The engine is pure.** It reads cells through a caller-supplied\n * `resolve(name)`. It never touches Redux, Supabase, or the DOM, which is\n * what makes every rule below testable.\n *\n * THE COERCION RULES (the whole contract, in one place)\n *\n * - **BLANK** is `null`, `undefined`, or `\"\"`. All three are the same value to\n * a formula, because a text cell that was cleared holds `\"\"` and a number\n * cell that was cleared holds `null`, and a user does not distinguish them.\n * - **A missing column is NOT blank.** `resolve()` returns `undefined` for a\n * column that does not exist (as opposed to `null` for one that is empty),\n * and that is an error: `{a} + {Typo}` must say the column is unknown rather\n * than quietly computing with zero.\n * - **Arithmetic treats BLANK as 0.** `{a} + {b}` with `b` empty is `{a}`.\n * That is the tolerant reading every spreadsheet uses, and the alternative\n * (the whole cell erroring because one input is empty) makes a formula\n * column useless on real, partly-filled data.\n * - **Numeric strings coerce.** `\"1,234\"`, `\" 42 \"`, `\"$5\"` and `\"3.5\"` are\n * numbers in arithmetic; `\"n/a\"` is a type error naming the value.\n * - **Booleans are 1 and 0 in arithmetic**, so SUM over a checkbox column\n * counts the ticks.\n * - **Aggregates SKIP blanks.** `SUM`/`MIN`/`MAX`/`AVERAGE` ignore empty\n * inputs entirely — `AVERAGE` divides by the count of the values that were\n * actually there, and returns BLANK when there were none.\n * - **Text conversion**: BLANK becomes `\"\"`, a number becomes its shortest\n * exact decimal, a boolean becomes `\"true\"` / `\"false\"`.\n * - **Comparison** is numeric when both sides read as numbers, otherwise a\n * plain string comparison. BLANK takes the shape of what it is compared\n * against — `0` beside a number, `\"\"` beside text — so `{a} = BLANK()`,\n * `{a} = 0` and `{a} = ''` all hold for an empty cell.\n * - **Truthiness** (for `IF` / `AND` / `OR` / `NOT`): BLANK is false, a\n * boolean is itself, a number is true unless `0`, a string is true unless\n * empty.\n * - **Dates are ISO strings, read and written in UTC.** A date function\n * accepts an ISO string (or a `Date`) and returns an ISO string — date-only\n * (`2026-09-14`) when the input was date-only, full (`2026-09-14T…Z`)\n * otherwise. UTC throughout on purpose: reading `2026-09-14` with local\n * getters turns it into the 13th for half the planet.\n * - **Division (and `%`) by zero is an error**, never `Infinity` and never\n * `NaN`. A cell may not lie about arithmetic that did not happen.\n */\n\n// ─── AST ─────────────────────────────────────────────────────────────────────\n\nexport type FormulaValue = string | number | boolean | null;\n\nexport type BinaryOperator =\n | \"+\"\n | \"-\"\n | \"*\"\n | \"/\"\n | \"%\"\n | \"&\"\n | \"=\"\n | \"!=\"\n | \"<>\"\n | \"<\"\n | \"<=\"\n | \">\"\n | \">=\";\n\nexport type FormulaAst =\n | { kind: \"number\"; value: number; position: number }\n | { kind: \"string\"; value: string; position: number }\n | { kind: \"boolean\"; value: boolean; position: number }\n | { kind: \"reference\"; name: string; position: number }\n | { kind: \"unary\"; operator: \"-\"; operand: FormulaAst; position: number }\n | {\n kind: \"binary\";\n operator: BinaryOperator;\n left: FormulaAst;\n right: FormulaAst;\n position: number;\n }\n | {\n kind: \"call\";\n name: FormulaFunctionName;\n args: FormulaAst[];\n position: number;\n };\n\nexport type ParseResult =\n | { ok: true; ast: FormulaAst; references: string[] }\n | { ok: false; error: string; position: number };\n\nexport type FormulaResult =\n | { ok: true; value: FormulaValue }\n | { ok: false; error: string };\n\nexport type FormulaResultType = \"number\" | \"text\" | \"boolean\" | \"date\" | \"unknown\";\n\n/** What a cell lookup may answer: a value, BLANK, or `undefined` = no such column. */\nexport type ResolveCell = (name: string) => unknown;\n\n/** Best-effort declared type of a column, for static result typing. */\nexport type ResolveColumnType = (\n name: string,\n) => \"number\" | \"text\" | \"boolean\" | \"date\" | undefined;\n\n// ─── the function table ──────────────────────────────────────────────────────\n\nconst FUNCTION_SPECS = {\n SUM: {\n signature: \"SUM(number, …)\",\n description: \"Adds every value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n MIN: {\n signature: \"MIN(number, …)\",\n description: \"The smallest value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n MAX: {\n signature: \"MAX(number, …)\",\n description: \"The largest value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n AVERAGE: {\n signature: \"AVERAGE(number, …)\",\n description:\n \"The average of the values that are filled in. Empty values are not counted.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n ROUND: {\n signature: \"ROUND(number, places?)\",\n description:\n \"Rounds to the given number of decimal places (0 when omitted). Halves round away from zero.\",\n min: 1,\n max: 2,\n result: \"number\",\n },\n ABS: {\n signature: \"ABS(number)\",\n description: \"The value without its minus sign.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n LEN: {\n signature: \"LEN(text)\",\n description: \"How many characters the text has.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n UPPER: {\n signature: \"UPPER(text)\",\n description: \"The text in capitals.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n LOWER: {\n signature: \"LOWER(text)\",\n description: \"The text in lower case.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n TRIM: {\n signature: \"TRIM(text)\",\n description: \"The text without leading or trailing spaces.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n CONCATENATE: {\n signature: \"CONCATENATE(text, …)\",\n description: \"Joins every value into one piece of text.\",\n min: 1,\n max: Infinity,\n result: \"text\",\n },\n LEFT: {\n signature: \"LEFT(text, count)\",\n description: \"The first few characters of the text.\",\n min: 2,\n max: 2,\n result: \"text\",\n },\n RIGHT: {\n signature: \"RIGHT(text, count)\",\n description: \"The last few characters of the text.\",\n min: 2,\n max: 2,\n result: \"text\",\n },\n CONTAINS: {\n signature: \"CONTAINS(text, part)\",\n description:\n \"Yes when the text contains that part. Upper and lower case are treated the same.\",\n min: 2,\n max: 2,\n result: \"boolean\",\n },\n IF: {\n signature: \"IF(condition, then, otherwise?)\",\n description:\n \"The second value when the condition holds, otherwise the third (empty when omitted).\",\n min: 2,\n max: 3,\n result: \"unknown\",\n },\n AND: {\n signature: \"AND(condition, …)\",\n description: \"Yes when every condition holds.\",\n min: 1,\n max: Infinity,\n result: \"boolean\",\n },\n OR: {\n signature: \"OR(condition, …)\",\n description: \"Yes when at least one condition holds.\",\n min: 1,\n max: Infinity,\n result: \"boolean\",\n },\n NOT: {\n signature: \"NOT(condition)\",\n description: \"Turns yes into no and no into yes.\",\n min: 1,\n max: 1,\n result: \"boolean\",\n },\n BLANK: {\n signature: \"BLANK()\",\n description: \"An empty value, for comparing against or returning.\",\n min: 0,\n max: 0,\n result: \"unknown\",\n },\n ISBLANK: {\n signature: \"ISBLANK(value)\",\n description: \"Yes when the value is empty.\",\n min: 1,\n max: 1,\n result: \"boolean\",\n },\n TODAY: {\n signature: \"TODAY()\",\n description: \"Today's date.\",\n min: 0,\n max: 0,\n result: \"date\",\n },\n NOW: {\n signature: \"NOW()\",\n description: \"The current date and time.\",\n min: 0,\n max: 0,\n result: \"date\",\n },\n DATEDIFF: {\n signature: \"DATEDIFF(from, to, 'days' | 'hours' | 'minutes')\",\n description:\n \"How far the second date is after the first. Negative when it is earlier.\",\n min: 3,\n max: 3,\n result: \"number\",\n },\n YEAR: {\n signature: \"YEAR(date)\",\n description: \"The four-digit year of the date.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n MONTH: {\n signature: \"MONTH(date)\",\n description: \"The month of the date, 1 to 12.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n DAY: {\n signature: \"DAY(date)\",\n description: \"The day of the month, 1 to 31.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n DATEADD: {\n signature: \"DATEADD(date, count, 'days' | 'months' | 'years')\",\n description:\n \"The date moved forward by that many units. Use a negative count to go back.\",\n min: 3,\n max: 3,\n result: \"date\",\n },\n} as const satisfies Record<\n string,\n {\n signature: string;\n description: string;\n min: number;\n max: number;\n result: FormulaResultType;\n }\n>;\n\nexport type FormulaFunctionName = keyof typeof FUNCTION_SPECS;\n\nconst FUNCTION_NAMES = Object.keys(FUNCTION_SPECS) as FormulaFunctionName[];\n\n/** The help list the formula editor shows. Order is the order it renders in. */\nexport const FORMULA_FUNCTIONS: readonly {\n name: FormulaFunctionName;\n signature: string;\n description: string;\n}[] = Object.freeze(\n FUNCTION_NAMES.map((name) => ({\n name,\n signature: FUNCTION_SPECS[name].signature,\n description: FUNCTION_SPECS[name].description,\n })),\n);\n\n// ─── tokenizer ───────────────────────────────────────────────────────────────\n\ntype TokenType =\n | \"number\"\n | \"string\"\n | \"identifier\"\n | \"reference\"\n | \"operator\"\n | \"paren-open\"\n | \"paren-close\"\n | \"comma\"\n | \"end\";\n\ntype Token = { type: TokenType; text: string; position: number };\n\n/** Thrown only INSIDE this module; every public entry point catches it. */\nclass FormulaError extends Error {\n readonly position: number;\n constructor(message: string, position = 0) {\n super(message);\n this.name = \"FormulaError\";\n this.position = position;\n }\n}\n\nconst TWO_CHAR_OPERATORS = [\"!=\", \"<>\", \"<=\", \">=\"];\nconst ONE_CHAR_OPERATORS = [\"+\", \"-\", \"*\", \"/\", \"%\", \"&\", \"=\", \"<\", \">\"];\n\nfunction isDigit(ch: string): boolean {\n return ch >= \"0\" && ch <= \"9\";\n}\n\nfunction isIdentifierStart(ch: string): boolean {\n return /[A-Za-z_]/.test(ch);\n}\n\nfunction isIdentifierPart(ch: string): boolean {\n return /[A-Za-z0-9_]/.test(ch);\n}\n\nfunction tokenize(source: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < source.length) {\n const ch = source[i];\n // `i < source.length` already guarantees a character here; the guard only\n // makes that knowledge explicit for the compiler.\n if (ch === undefined) break;\n\n if (/\\s/.test(ch)) {\n i += 1;\n continue;\n }\n\n if (ch === \"{\") {\n const close = source.indexOf(\"}\", i + 1);\n if (close === -1) {\n throw new FormulaError(\n \"This column reference is missing its closing brace `}`.\",\n i,\n );\n }\n const name = source.slice(i + 1, close).trim();\n if (name === \"\") {\n throw new FormulaError(\"This column reference has no name inside `{}`.\", i);\n }\n tokens.push({ type: \"reference\", text: name, position: i });\n i = close + 1;\n continue;\n }\n\n if (ch === '\"' || ch === \"'\") {\n const quote = ch;\n let j = i + 1;\n let text = \"\";\n let closed = false;\n while (j < source.length) {\n if (source[j] === \"\\\\\" && j + 1 < source.length) {\n text += source[j + 1];\n j += 2;\n continue;\n }\n if (source[j] === quote) {\n closed = true;\n j += 1;\n break;\n }\n text += source[j];\n j += 1;\n }\n if (!closed) {\n throw new FormulaError(\n `This text is missing its closing ${quote === '\"' ? \"double\" : \"single\"} quote.`,\n i,\n );\n }\n tokens.push({ type: \"string\", text, position: i });\n i = j;\n continue;\n }\n\n if (isDigit(ch) || (ch === \".\" && isDigit(source[i + 1] ?? \"\"))) {\n let j = i;\n let seenDot = false;\n while (j < source.length) {\n // The `j < source.length` above guarantees a character; `?? \"\"` is\n // never taken and reads as \"not a digit\", which ends the number.\n if (isDigit(source[j] ?? \"\")) {\n j += 1;\n continue;\n }\n if (source[j] === \".\" && !seenDot) {\n seenDot = true;\n j += 1;\n continue;\n }\n break;\n }\n tokens.push({ type: \"number\", text: source.slice(i, j), position: i });\n i = j;\n continue;\n }\n\n if (isIdentifierStart(ch)) {\n let j = i;\n // Guarded by `j < source.length`; `?? \"\"` is never taken and reads as\n // \"not an identifier character\", which ends the identifier.\n while (j < source.length && isIdentifierPart(source[j] ?? \"\")) j += 1;\n tokens.push({ type: \"identifier\", text: source.slice(i, j), position: i });\n i = j;\n continue;\n }\n\n if (ch === \"(\") {\n tokens.push({ type: \"paren-open\", text: ch, position: i });\n i += 1;\n continue;\n }\n if (ch === \")\") {\n tokens.push({ type: \"paren-close\", text: ch, position: i });\n i += 1;\n continue;\n }\n if (ch === \",\") {\n tokens.push({ type: \"comma\", text: ch, position: i });\n i += 1;\n continue;\n }\n\n const two = source.slice(i, i + 2);\n if (TWO_CHAR_OPERATORS.includes(two)) {\n tokens.push({ type: \"operator\", text: two, position: i });\n i += 2;\n continue;\n }\n if (ONE_CHAR_OPERATORS.includes(ch)) {\n tokens.push({ type: \"operator\", text: ch, position: i });\n i += 1;\n continue;\n }\n\n throw new FormulaError(`\\`${ch}\\` does not mean anything in a formula.`, i);\n }\n\n tokens.push({ type: \"end\", text: \"\", position: source.length });\n return tokens;\n}\n\n// ─── parser ──────────────────────────────────────────────────────────────────\n//\n// Precedence, loosest first:\n// comparison = != <> < <= > >=\n// concat &\n// additive + -\n// multiplicative * / %\n// unary -\n// primary literal · {reference} · FUNC(…) · ( … )\n\nconst COMPARISON_OPERATORS = [\"=\", \"!=\", \"<>\", \"<\", \"<=\", \">\", \">=\"];\n\nclass Parser {\n private readonly tokens: Token[];\n private index = 0;\n readonly references: string[] = [];\n\n /**\n * `tokenize` always terminates the list with an `end` token and the grammar\n * never advances past it, so neither accessor below can actually run off the\n * end. This sentinel is what they would return if one ever did, which keeps\n * both accessors total without an assertion.\n */\n private readonly endToken: Token;\n\n constructor(tokens: Token[]) {\n this.tokens = tokens;\n this.endToken = tokens[tokens.length - 1] ?? {\n type: \"end\",\n text: \"\",\n position: 0,\n };\n }\n\n private peek(): Token {\n return this.tokens[this.index] ?? this.endToken;\n }\n\n private next(): Token {\n return this.tokens[this.index++] ?? this.endToken;\n }\n\n private matchOperator(candidates: readonly string[]): Token | null {\n const token = this.peek();\n if (token.type === \"operator\" && candidates.includes(token.text)) {\n this.index += 1;\n return token;\n }\n return null;\n }\n\n parseProgram(): FormulaAst {\n const expression = this.parseComparison();\n const trailing = this.peek();\n if (trailing.type !== \"end\") {\n throw new FormulaError(\n `Nothing should follow the formula here — remove \\`${trailing.text}\\`.`,\n trailing.position,\n );\n }\n return expression;\n }\n\n private parseComparison(): FormulaAst {\n let left = this.parseConcat();\n let operator = this.matchOperator(COMPARISON_OPERATORS);\n while (operator) {\n const right = this.parseConcat();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator(COMPARISON_OPERATORS);\n }\n return left;\n }\n\n private parseConcat(): FormulaAst {\n let left = this.parseAdditive();\n let operator = this.matchOperator([\"&\"]);\n while (operator) {\n const right = this.parseAdditive();\n left = {\n kind: \"binary\",\n operator: \"&\",\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"&\"]);\n }\n return left;\n }\n\n private parseAdditive(): FormulaAst {\n let left = this.parseMultiplicative();\n let operator = this.matchOperator([\"+\", \"-\"]);\n while (operator) {\n const right = this.parseMultiplicative();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"+\", \"-\"]);\n }\n return left;\n }\n\n private parseMultiplicative(): FormulaAst {\n let left = this.parseUnary();\n let operator = this.matchOperator([\"*\", \"/\", \"%\"]);\n while (operator) {\n const right = this.parseUnary();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"*\", \"/\", \"%\"]);\n }\n return left;\n }\n\n private parseUnary(): FormulaAst {\n const minus = this.matchOperator([\"-\"]);\n if (minus) {\n return {\n kind: \"unary\",\n operator: \"-\",\n operand: this.parseUnary(),\n position: minus.position,\n };\n }\n // A leading `+` is harmless and people type it; accept and ignore it.\n const plus = this.matchOperator([\"+\"]);\n if (plus) return this.parseUnary();\n return this.parsePrimary();\n }\n\n private parsePrimary(): FormulaAst {\n const token = this.next();\n\n if (token.type === \"number\") {\n return { kind: \"number\", value: Number(token.text), position: token.position };\n }\n\n if (token.type === \"string\") {\n return { kind: \"string\", value: token.text, position: token.position };\n }\n\n if (token.type === \"reference\") {\n if (!this.references.includes(token.text)) this.references.push(token.text);\n return { kind: \"reference\", name: token.text, position: token.position };\n }\n\n if (token.type === \"paren-open\") {\n const inner = this.parseComparison();\n const close = this.next();\n if (close.type !== \"paren-close\") {\n throw new FormulaError(\n \"This opening bracket `(` never closes.\",\n token.position,\n );\n }\n return inner;\n }\n\n if (token.type === \"identifier\") {\n const upper = token.text.toUpperCase();\n if (upper === \"TRUE\" || upper === \"FALSE\") {\n return {\n kind: \"boolean\",\n value: upper === \"TRUE\",\n position: token.position,\n };\n }\n if (!(FUNCTION_NAMES as string[]).includes(upper)) {\n throw new FormulaError(\n `There is no function called \\`${token.text}\\`. A column goes in braces, like {${token.text}}.`,\n token.position,\n );\n }\n const name = upper as FormulaFunctionName;\n const open = this.next();\n if (open.type !== \"paren-open\") {\n throw new FormulaError(\n `\\`${name}\\` needs brackets after it, like ${FUNCTION_SPECS[name].signature}.`,\n token.position,\n );\n }\n const args: FormulaAst[] = [];\n if (this.peek().type === \"paren-close\") {\n this.index += 1;\n } else {\n for (;;) {\n args.push(this.parseComparison());\n const separator = this.next();\n if (separator.type === \"comma\") continue;\n if (separator.type === \"paren-close\") break;\n throw new FormulaError(\n `\\`${name}\\` is missing a comma or its closing bracket.`,\n separator.position,\n );\n }\n }\n const spec = FUNCTION_SPECS[name];\n if (args.length < spec.min || args.length > spec.max) {\n throw new FormulaError(\n `\\`${name}\\` was given ${args.length} value${args.length === 1 ? \"\" : \"s\"}. Use ${spec.signature}.`,\n token.position,\n );\n }\n return { kind: \"call\", name, args, position: token.position };\n }\n\n if (token.type === \"end\") {\n throw new FormulaError(\"The formula stops before it is finished.\", token.position);\n }\n\n throw new FormulaError(\n `\\`${token.text}\\` cannot start a value here.`,\n token.position,\n );\n }\n}\n\n/**\n * Turn formula text into an AST plus the list of columns it reads.\n *\n * `references` holds every `{…}` name exactly as typed, in first-use order —\n * the caller resolves them (display name OR machine field name) through its own\n * lookup, so this module never needs to know the table.\n */\nexport function parseFormula(source: string): ParseResult {\n try {\n const text = source ?? \"\";\n if (text.trim() === \"\") {\n return { ok: false, error: \"This formula is empty.\", position: 0 };\n }\n const parser = new Parser(tokenize(text));\n const ast = parser.parseProgram();\n return { ok: true, ast, references: parser.references };\n } catch (error) {\n if (error instanceof FormulaError) {\n return { ok: false, error: error.message, position: error.position };\n }\n return {\n ok: false,\n error: \"This formula could not be read.\",\n position: 0,\n };\n }\n}\n\n// ─── value coercion ──────────────────────────────────────────────────────────\n\nconst MISSING = Symbol(\"missing-column\");\n\nfunction isBlank(value: FormulaValue): boolean {\n return value === null || value === \"\";\n}\n\n/** Shortest exact decimal for a number — keeps `0.1 + 0.2` out of the cell. */\nfunction numberToText(n: number): string {\n if (Number.isInteger(n)) return String(n);\n const trimmed = Number(n.toPrecision(15));\n return String(Number.isFinite(trimmed) ? trimmed : n);\n}\n\nfunction toText(value: FormulaValue): string {\n if (value === null) return \"\";\n if (typeof value === \"number\") return numberToText(value);\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n return value;\n}\n\n/** A number, or `null` when the value simply is not one. BLANK reads as 0. */\nfunction looseNumber(value: FormulaValue): number | null {\n if (value === null || value === \"\") return 0;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"boolean\") return value ? 1 : 0;\n const cleaned = value.replace(/[,\\s$€£¥%]/g, \"\");\n if (cleaned === \"\") return 0;\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction requireNumber(value: FormulaValue, what: string): number {\n const n = looseNumber(value);\n if (n === null) {\n throw new FormulaError(`${what} needs a number, but got \"${toText(value)}\".`);\n }\n return n;\n}\n\nfunction truthy(value: FormulaValue): boolean {\n if (value === null || value === \"\") return false;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return value !== 0;\n return true;\n}\n\n/** Both sides numeric? compare as numbers. Otherwise compare as text. */\nfunction compareValues(left: FormulaValue, right: FormulaValue): number {\n const looksNumeric = (v: FormulaValue) =>\n typeof v === \"number\" ||\n (typeof v === \"string\" && v.trim() !== \"\" && looseNumber(v) !== null);\n // BLANK takes the shape of whatever it is compared against: 0 beside a\n // number, \"\" beside text. `{Empty} = 0` and `{Empty} = ''` are both true.\n if (isBlank(left) && isBlank(right)) return 0;\n const blankBesideNumber =\n (isBlank(left) && looksNumeric(right)) ||\n (isBlank(right) && looksNumeric(left));\n if (blankBesideNumber || (looksNumeric(left) && looksNumeric(right))) {\n const a = looseNumber(left) ?? 0;\n const b = looseNumber(right) ?? 0;\n return a < b ? -1 : a > b ? 1 : 0;\n }\n if (typeof left === \"boolean\" || typeof right === \"boolean\") {\n const a = truthy(left);\n const b = truthy(right);\n return a === b ? 0 : a ? 1 : -1;\n }\n const a = toText(left);\n const b = toText(right);\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n// ─── dates ───────────────────────────────────────────────────────────────────\n\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\ntype ParsedDate = { ms: number; dateOnly: boolean };\n\nfunction requireDate(value: FormulaValue, what: string): ParsedDate {\n if (value === null || value === \"\") {\n throw new FormulaError(`${what} needs a date, but that value is empty.`);\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n const ms = Date.parse(trimmed);\n if (!Number.isNaN(ms)) return { ms, dateOnly: DATE_ONLY_RE.test(trimmed) };\n }\n throw new FormulaError(`${what} needs a date, but got \"${toText(value)}\".`);\n}\n\nfunction isoFrom(ms: number, dateOnly: boolean): string {\n const iso = new Date(ms).toISOString();\n return dateOnly ? iso.slice(0, 10) : iso;\n}\n\nconst DATEDIFF_UNITS: Record<string, number> = {\n days: 86_400_000,\n hours: 3_600_000,\n minutes: 60_000,\n};\n\nfunction requireUnit(\n value: FormulaValue,\n allowed: string[],\n fn: string,\n): string {\n const unit = toText(value).trim().toLowerCase();\n if (!allowed.includes(unit)) {\n throw new FormulaError(\n `\\`${fn}\\` needs one of ${allowed.map((u) => `'${u}'`).join(\", \")} — got \"${toText(value)}\".`,\n );\n }\n return unit;\n}\n\n/** Add months/years in UTC, clamping to the end of a shorter month. */\nfunction addMonths(ms: number, count: number): number {\n const d = new Date(ms);\n const day = d.getUTCDate();\n const target = new Date(ms);\n target.setUTCDate(1);\n target.setUTCMonth(target.getUTCMonth() + count);\n const lastDay = new Date(\n Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0),\n ).getUTCDate();\n target.setUTCDate(Math.min(day, lastDay));\n return target.getTime();\n}\n\n// ─── evaluator ───────────────────────────────────────────────────────────────\n\n/** SUM-style: flatten args, drop blanks, demand numbers of the rest. */\nfunction numericArgs(values: FormulaValue[], fn: string): number[] {\n const out: number[] = [];\n for (const value of values) {\n if (isBlank(value)) continue;\n out.push(requireNumber(value, `\\`${fn}\\``));\n }\n return out;\n}\n\nfunction roundHalfAway(n: number, places: number): number {\n const factor = 10 ** places;\n const scaled = n * factor;\n // Nudge past the float representation of an exact half (1.005 * 100 = 100.49999…).\n const corrected = Number(scaled.toPrecision(15));\n const rounded =\n corrected < 0 ? -Math.round(-corrected) : Math.round(corrected);\n return rounded / factor;\n}\n\n/**\n * Positional argument of a call. The parser has already checked every call\n * against its `FUNCTION_SPECS` min/max, so each index read below is present;\n * this accessor only makes that knowledge explicit. An index that somehow was\n * not supplied reads as BLANK — the value this language already uses for \"not\n * given\" — so nothing here can throw on a missing argument.\n */\nfunction argAt(args: readonly FormulaValue[], index: number): FormulaValue {\n return args[index] ?? null;\n}\n\nfunction callFunction(\n name: FormulaFunctionName,\n args: FormulaValue[],\n): FormulaValue {\n switch (name) {\n case \"SUM\":\n return numericArgs(args, \"SUM\").reduce((a, b) => a + b, 0);\n case \"MIN\": {\n const nums = numericArgs(args, \"MIN\");\n return nums.length === 0 ? null : Math.min(...nums);\n }\n case \"MAX\": {\n const nums = numericArgs(args, \"MAX\");\n return nums.length === 0 ? null : Math.max(...nums);\n }\n case \"AVERAGE\": {\n const nums = numericArgs(args, \"AVERAGE\");\n if (nums.length === 0) return null;\n return nums.reduce((a, b) => a + b, 0) / nums.length;\n }\n case \"ROUND\": {\n const n = requireNumber(argAt(args, 0), \"`ROUND`\");\n const places =\n args.length > 1 ? Math.trunc(requireNumber(argAt(args, 1), \"`ROUND`\")) : 0;\n return roundHalfAway(n, places);\n }\n case \"ABS\":\n return Math.abs(requireNumber(argAt(args, 0), \"`ABS`\"));\n case \"LEN\":\n return toText(argAt(args, 0)).length;\n case \"UPPER\":\n return toText(argAt(args, 0)).toUpperCase();\n case \"LOWER\":\n return toText(argAt(args, 0)).toLowerCase();\n case \"TRIM\":\n return toText(argAt(args, 0)).trim();\n case \"CONCATENATE\":\n return args.map(toText).join(\"\");\n case \"LEFT\": {\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`LEFT`\"));\n return toText(argAt(args, 0)).slice(0, Math.max(0, count));\n }\n case \"RIGHT\": {\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`RIGHT`\"));\n if (count <= 0) return \"\";\n return toText(argAt(args, 0)).slice(-count);\n }\n case \"CONTAINS\":\n return toText(argAt(args, 0))\n .toLowerCase()\n .includes(toText(argAt(args, 1)).toLowerCase());\n case \"NOT\":\n return !truthy(argAt(args, 0));\n case \"BLANK\":\n return null;\n case \"ISBLANK\":\n return isBlank(argAt(args, 0));\n case \"TODAY\":\n return new Date().toISOString().slice(0, 10);\n case \"NOW\":\n return new Date().toISOString();\n case \"DATEDIFF\": {\n const from = requireDate(argAt(args, 0), \"`DATEDIFF`\");\n const to = requireDate(argAt(args, 1), \"`DATEDIFF`\");\n const unit = requireUnit(argAt(args, 2), Object.keys(DATEDIFF_UNITS), \"DATEDIFF\");\n // `requireUnit` was handed this map's own key list, so it can only return\n // a key that is present; the guard makes that explicit.\n const perUnit = DATEDIFF_UNITS[unit];\n if (perUnit === undefined) return null;\n return Math.trunc((to.ms - from.ms) / perUnit);\n }\n case \"YEAR\":\n return new Date(requireDate(argAt(args, 0), \"`YEAR`\").ms).getUTCFullYear();\n case \"MONTH\":\n return new Date(requireDate(argAt(args, 0), \"`MONTH`\").ms).getUTCMonth() + 1;\n case \"DAY\":\n return new Date(requireDate(argAt(args, 0), \"`DAY`\").ms).getUTCDate();\n case \"DATEADD\": {\n const base = requireDate(argAt(args, 0), \"`DATEADD`\");\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`DATEADD`\"));\n const unit = requireUnit(argAt(args, 2), [\"days\", \"months\", \"years\"], \"DATEADD\");\n const ms =\n unit === \"days\"\n ? base.ms + count * 86_400_000\n : addMonths(base.ms, unit === \"months\" ? count : count * 12);\n return isoFrom(ms, base.dateOnly);\n }\n // IF / AND / OR short-circuit in `evaluateNode` and never reach here.\n case \"IF\":\n case \"AND\":\n case \"OR\":\n throw new FormulaError(`\\`${name}\\` could not be evaluated.`);\n }\n}\n\nfunction applyBinary(\n operator: BinaryOperator,\n left: FormulaValue,\n right: FormulaValue,\n): FormulaValue {\n switch (operator) {\n case \"&\":\n return toText(left) + toText(right);\n case \"+\":\n return (\n requireNumber(left, \"`+`\") + requireNumber(right, \"`+`\")\n );\n case \"-\":\n return requireNumber(left, \"`-`\") - requireNumber(right, \"`-`\");\n case \"*\":\n return requireNumber(left, \"`*`\") * requireNumber(right, \"`*`\");\n case \"/\": {\n const divisor = requireNumber(right, \"`/`\");\n if (divisor === 0) {\n throw new FormulaError(\"This formula divides by zero.\");\n }\n return requireNumber(left, \"`/`\") / divisor;\n }\n case \"%\": {\n const divisor = requireNumber(right, \"`%`\");\n if (divisor === 0) {\n throw new FormulaError(\"This formula divides by zero.\");\n }\n return requireNumber(left, \"`%`\") % divisor;\n }\n case \"=\":\n return compareValues(left, right) === 0;\n case \"!=\":\n case \"<>\":\n return compareValues(left, right) !== 0;\n case \"<\":\n return compareValues(left, right) < 0;\n case \"<=\":\n return compareValues(left, right) <= 0;\n case \">\":\n return compareValues(left, right) > 0;\n case \">=\":\n return compareValues(left, right) >= 0;\n }\n}\n\nfunction normalizeCell(raw: unknown, name: string): FormulaValue {\n if (raw === MISSING) {\n throw new FormulaError(`There is no column called {${name}}.`);\n }\n if (raw === null || raw === undefined) return null;\n if (typeof raw === \"string\" || typeof raw === \"boolean\") return raw;\n if (typeof raw === \"number\") {\n if (!Number.isFinite(raw)) {\n throw new FormulaError(`The value in {${name}} is not a usable number.`);\n }\n return raw;\n }\n if (raw instanceof Date) {\n return Number.isNaN(raw.getTime()) ? null : raw.toISOString();\n }\n // Arrays and objects have no scalar reading; JSON keeps them legible.\n try {\n return JSON.stringify(raw);\n } catch {\n return String(raw);\n }\n}\n\nfunction evaluateNode(node: FormulaAst, resolve: ResolveCell): FormulaValue {\n switch (node.kind) {\n case \"number\":\n case \"string\":\n case \"boolean\":\n return node.value;\n case \"reference\": {\n const raw = resolve(node.name);\n // `undefined` means the column does not exist; a blank cell is null/\"\".\n return normalizeCell(raw === undefined ? MISSING : raw, node.name);\n }\n case \"unary\":\n return -requireNumber(evaluateNode(node.operand, resolve), \"`-`\");\n case \"binary\":\n return applyBinary(\n node.operator,\n evaluateNode(node.left, resolve),\n evaluateNode(node.right, resolve),\n );\n case \"call\": {\n if (node.name === \"IF\") {\n // The parser enforces IF's 2–3 arity, so both of these are present; an\n // absent one would read as BLANK rather than throw.\n const test = node.args[0];\n const whenTrue = node.args[1];\n const condition = truthy(test ? evaluateNode(test, resolve) : null);\n if (condition) return whenTrue ? evaluateNode(whenTrue, resolve) : null;\n const whenFalse = node.args[2];\n return node.args.length > 2 && whenFalse\n ? evaluateNode(whenFalse, resolve)\n : null;\n }\n if (node.name === \"AND\") {\n for (const arg of node.args) {\n if (!truthy(evaluateNode(arg, resolve))) return false;\n }\n return true;\n }\n if (node.name === \"OR\") {\n for (const arg of node.args) {\n if (truthy(evaluateNode(arg, resolve))) return true;\n }\n return false;\n }\n return callFunction(\n node.name,\n node.args.map((arg) => evaluateNode(arg, resolve)),\n );\n }\n }\n}\n\n/**\n * Compute one cell. NEVER throws.\n *\n * `resolve(name)` is given the name exactly as it was typed between the braces\n * and must return the cell's value, or `undefined` when no such column exists —\n * the difference between \"empty\" and \"misspelt\", which is the difference\n * between a working formula and a wrong answer.\n */\nexport function evaluateFormula(\n ast: FormulaAst,\n resolve: ResolveCell,\n): FormulaResult {\n try {\n const value = evaluateNode(ast, resolve);\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n return { ok: false, error: \"This formula produced a number too large to show.\" };\n }\n return { ok: true, value };\n } catch (error) {\n if (error instanceof FormulaError) return { ok: false, error: error.message };\n return { ok: false, error: \"This formula could not be calculated.\" };\n }\n}\n\n// ─── static result type ──────────────────────────────────────────────────────\n\n/**\n * Best-effort: what kind of value will this formula produce? The UI uses it to\n * pick a sensible display format for the computed column. `unknown` is always a\n * legal answer — never guess a format on a shaky reading.\n */\nexport function formulaResultType(\n ast: FormulaAst,\n columnType: ResolveColumnType,\n): FormulaResultType {\n switch (ast.kind) {\n case \"number\":\n return \"number\";\n case \"string\":\n return \"text\";\n case \"boolean\":\n return \"boolean\";\n case \"unary\":\n return \"number\";\n case \"reference\":\n return columnType(ast.name) ?? \"unknown\";\n case \"binary\": {\n if (ast.operator === \"&\") return \"text\";\n if (COMPARISON_OPERATORS.includes(ast.operator)) return \"boolean\";\n return \"number\";\n }\n case \"call\": {\n if (ast.name === \"IF\") {\n // Arity is parser-enforced: IF always carries its \"then\" branch. An\n // absent branch types as \"unknown\", the same neutral this function\n // already returns for anything it cannot pin down.\n const thenArg = ast.args[1];\n const then = thenArg ? formulaResultType(thenArg, columnType) : \"unknown\";\n const otherwiseArg = ast.args[2];\n if (ast.args.length < 3 || !otherwiseArg) return then;\n const otherwise = formulaResultType(otherwiseArg, columnType);\n if (then === otherwise) return then;\n if (then === \"unknown\") return otherwise;\n if (otherwise === \"unknown\") return then;\n return \"unknown\";\n }\n if (ast.name === \"MIN\" || ast.name === \"MAX\") {\n // MIN/MAX over dates is still dates; over anything else, a number.\n const types = ast.args.map((a) => formulaResultType(a, columnType));\n if (types.length > 0 && types.every((t) => t === \"date\")) return \"date\";\n return \"number\";\n }\n return FUNCTION_SPECS[ast.name].result;\n }\n }\n}\n\n// ─── computed columns over rows ─────────────────────────────────────────────\n//\n// THE ONE PLACE a formula column's value is injected into a row. The grid,\n// every copy/export reader (`getCompleteTable` consumers), the client-side\n// sort and the agent scope all call this, so no reader can ever hand out a\n// formula column as the empty cell the database actually stores.\n\n/** The slice of a field row this helper needs to find and evaluate formulas. */\nexport interface ComputedColumnField {\n field_name: string;\n display_name: string;\n data_type: string;\n metadata?: unknown;\n}\n\nexport interface ComputedColumn<F extends ComputedColumnField = ComputedColumnField> {\n field: F;\n parsed: ParseResult;\n}\n\n/**\n * Read the field's declared format WITHOUT importing `lib/field-formats`\n * (which imports nothing from `features/`, and the reverse direction is the\n * one this module keeps clean too). The shape is `metadata.format = { id,\n * options }`, exactly what `readFieldFormatConfig` reads.\n */\nfunction formulaExpressionOf(field: ComputedColumnField): string | null {\n const metadata = field.metadata;\n if (!metadata || typeof metadata !== \"object\") return null;\n const format = (metadata as { format?: unknown }).format;\n if (!format || typeof format !== \"object\") return null;\n if ((format as { id?: unknown }).id !== \"formula\") return null;\n const options = (format as { options?: unknown }).options;\n const formula =\n options && typeof options === \"object\"\n ? (options as { formula?: unknown }).formula\n : undefined;\n const expression =\n formula && typeof formula === \"object\"\n ? (formula as { expression?: unknown }).expression\n : undefined;\n return typeof expression === \"string\" ? expression : \"\";\n}\n\n/** True when the column's declared format is `formula`. */\nexport function isFormulaColumn(field: ComputedColumnField): boolean {\n return formulaExpressionOf(field) !== null;\n}\n\n/** Every formula column among `fields`, each with its expression parsed once. */\nexport function formulaColumnsOf<F extends ComputedColumnField>(\n fields: readonly F[],\n): ComputedColumn<F>[] {\n return fields.flatMap((field) => {\n const expression = formulaExpressionOf(field);\n if (expression === null) return [];\n return [{ field, parsed: parseFormula(expression) }];\n });\n}\n\nexport interface ComputedRowsResult<R> {\n rows: R[];\n /** `${rowId}::${fieldName}` → the plain-English reason that cell shows #ERROR. */\n errors: Map<string, string>;\n /** Machine names of the formula columns, for callers that must skip them. */\n formulaFieldNames: Set<string>;\n}\n\n/**\n * Return `rows` with every formula column's value computed from the row's\n * other cells. Pure; the input rows are never mutated. When `fields` holds no\n * formula column the SAME array is returned, so callers pay nothing.\n *\n * Formula columns are evaluated in `fields` order, and each one is written into\n * the working copy of the row before the next runs — so a later formula may\n * reference an earlier one. References resolve by machine name first, then by\n * display name (case-insensitive), matching the grid.\n */\nexport function withComputedColumns<\n R extends { id: string; data: Record<string, unknown> },\n F extends ComputedColumnField,\n>(rows: readonly R[], fields: readonly F[]): ComputedRowsResult<R> {\n const columns = formulaColumnsOf(fields);\n const formulaFieldNames = new Set(columns.map((c) => c.field.field_name));\n const errors = new Map<string, string>();\n if (columns.length === 0) {\n return { rows: rows as R[], errors, formulaFieldNames };\n }\n // A reference resolves against the table's COLUMNS, never against the keys a\n // particular row happens to carry: a row saved with only some of its cells\n // (every new row, every sparse import) has no key for the rest, and that is\n // BLANK — not \"no such column\", which is the #ERROR the engine reserves for\n // a misspelt reference.\n const fieldNameByReference = new Map<string, string>();\n for (const f of fields) {\n fieldNameByReference.set(f.display_name.toLowerCase(), f.field_name);\n }\n for (const f of fields) {\n // Machine names win over a display name that happens to collide.\n fieldNameByReference.set(f.field_name.toLowerCase(), f.field_name);\n }\n const computed = rows.map((row) => {\n const data: Record<string, unknown> = { ...(row.data ?? {}) };\n const resolve: ResolveCell = (name) => {\n if (name in data) return data[name];\n const fieldName = fieldNameByReference.get(name.toLowerCase());\n if (fieldName === undefined) return undefined; // truly no such column\n return data[fieldName] ?? null;\n };\n for (const { field, parsed } of columns) {\n if (!parsed.ok) {\n data[field.field_name] = null;\n errors.set(`${row.id}::${field.field_name}`, parsed.error);\n continue;\n }\n const result = evaluateFormula(parsed.ast, resolve);\n if (result.ok) data[field.field_name] = result.value;\n else {\n data[field.field_name] = null;\n errors.set(`${row.id}::${field.field_name}`, result.error);\n }\n }\n return { ...row, data };\n });\n return { rows: computed, errors, formulaFieldNames };\n}\n\n// ─── renaming a column a formula refers to ──────────────────────────────────\n\n/**\n * Rewrite every `{from}` reference in `expression` to `{to}` — what keeps a\n * formula working when the column it names is renamed (Airtable and Sheets do\n * this silently; a rename that turned a working column into #ERROR would be\n * the platform punishing the user for tidying up).\n *\n * Walks the source with the tokenizer's own rules for the two things that\n * matter: a `{…}` inside a quoted string is TEXT and is left alone, and `\\`\n * escapes the next character inside a string. The match is on the trimmed\n * name, case-insensitive — the same comparison `withComputedColumns` makes.\n * Returns the SAME string when nothing referenced `from`.\n */\nexport function rewriteFormulaReferences(\n expression: string,\n from: string,\n to: string,\n): string {\n const target = from.trim().toLowerCase();\n if (!target) return expression;\n let out = \"\";\n let i = 0;\n while (i < expression.length) {\n const ch = expression[i];\n if (ch === '\"' || ch === \"'\") {\n let j = i + 1;\n while (j < expression.length) {\n if (expression[j] === \"\\\\\" && j + 1 < expression.length) {\n j += 2;\n continue;\n }\n if (expression[j] === ch) {\n j += 1;\n break;\n }\n j += 1;\n }\n out += expression.slice(i, j);\n i = j;\n continue;\n }\n if (ch === \"{\") {\n const close = expression.indexOf(\"}\", i + 1);\n if (close === -1) {\n out += expression.slice(i);\n break;\n }\n const name = expression.slice(i + 1, close);\n out += name.trim().toLowerCase() === target ? `{${to}}` : expression.slice(i, close + 1);\n i = close + 1;\n continue;\n }\n out += ch;\n i += 1;\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyHA,IAAM,iBAAiB;AAAA,EACrB,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,aACE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aACE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,aAAa;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,UAAU;AAAA,IACR,WAAW;AAAA,IACX,aACE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,WAAW;AAAA,IACX,aACE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,UAAU;AAAA,IACR,WAAW;AAAA,IACX,aACE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,WAAW;AAAA,IACX,aAAa;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,aACE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,EACV;AACF;AAaA,IAAM,iBAAiB,OAAO,KAAK,cAAc;AAG1C,IAAM,oBAIP,OAAO;AAAA,EACX,eAAe,IAAI,CAAC,UAAU;AAAA,IAC5B;AAAA,IACA,WAAW,eAAe,IAAI,EAAE;AAAA,IAChC,aAAa,eAAe,IAAI,EAAE;AAAA,EACpC,EAAE;AACJ;AAkBA,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtB;AAAA,EACT,YAAY,SAAiB,WAAW,GAAG;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,IAAM,qBAAqB,CAAC,MAAM,MAAM,MAAM,IAAI;AAClD,IAAM,qBAAqB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAEvE,SAAS,QAAQ,IAAqB;AACpC,SAAO,MAAM,OAAO,MAAM;AAC5B;AAEA,SAAS,kBAAkB,IAAqB;AAC9C,SAAO,YAAY,KAAK,EAAE;AAC5B;AAEA,SAAS,iBAAiB,IAAqB;AAC7C,SAAO,eAAe,KAAK,EAAE;AAC/B;AAEA,SAAS,SAAS,QAAyB;AACzC,QAAM,SAAkB,CAAC;AACzB,MAAI,IAAI;AAER,SAAO,IAAI,OAAO,QAAQ;AACxB,UAAM,KAAK,OAAO,CAAC;AAGnB,QAAI,OAAO,OAAW;AAEtB,QAAI,KAAK,KAAK,EAAE,GAAG;AACjB,WAAK;AACL;AAAA,IACF;AAEA,QAAI,OAAO,KAAK;AACd,YAAM,QAAQ,OAAO,QAAQ,KAAK,IAAI,CAAC;AACvC,UAAI,UAAU,IAAI;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,YAAM,OAAO,OAAO,MAAM,IAAI,GAAG,KAAK,EAAE,KAAK;AAC7C,UAAI,SAAS,IAAI;AACf,cAAM,IAAI,aAAa,kDAAkD,CAAC;AAAA,MAC5E;AACA,aAAO,KAAK,EAAE,MAAM,aAAa,MAAM,MAAM,UAAU,EAAE,CAAC;AAC1D,UAAI,QAAQ;AACZ;AAAA,IACF;AAEA,QAAI,OAAO,OAAO,OAAO,KAAK;AAC5B,YAAM,QAAQ;AACd,UAAI,IAAI,IAAI;AACZ,UAAI,OAAO;AACX,UAAI,SAAS;AACb,aAAO,IAAI,OAAO,QAAQ;AACxB,YAAI,OAAO,CAAC,MAAM,QAAQ,IAAI,IAAI,OAAO,QAAQ;AAC/C,kBAAQ,OAAO,IAAI,CAAC;AACpB,eAAK;AACL;AAAA,QACF;AACA,YAAI,OAAO,CAAC,MAAM,OAAO;AACvB,mBAAS;AACT,eAAK;AACL;AAAA,QACF;AACA,gBAAQ,OAAO,CAAC;AAChB,aAAK;AAAA,MACP;AACA,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR,oCAAoC,UAAU,MAAM,WAAW,QAAQ;AAAA,UACvE;AAAA,QACF;AAAA,MACF;AACA,aAAO,KAAK,EAAE,MAAM,UAAU,MAAM,UAAU,EAAE,CAAC;AACjD,UAAI;AACJ;AAAA,IACF;AAEA,QAAI,QAAQ,EAAE,KAAM,OAAO,OAAO,QAAQ,OAAO,IAAI,CAAC,KAAK,EAAE,GAAI;AAC/D,UAAI,IAAI;AACR,UAAI,UAAU;AACd,aAAO,IAAI,OAAO,QAAQ;AAGxB,YAAI,QAAQ,OAAO,CAAC,KAAK,EAAE,GAAG;AAC5B,eAAK;AACL;AAAA,QACF;AACA,YAAI,OAAO,CAAC,MAAM,OAAO,CAAC,SAAS;AACjC,oBAAU;AACV,eAAK;AACL;AAAA,QACF;AACA;AAAA,MACF;AACA,aAAO,KAAK,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC;AACrE,UAAI;AACJ;AAAA,IACF;AAEA,QAAI,kBAAkB,EAAE,GAAG;AACzB,UAAI,IAAI;AAGR,aAAO,IAAI,OAAO,UAAU,iBAAiB,OAAO,CAAC,KAAK,EAAE,EAAG,MAAK;AACpE,aAAO,KAAK,EAAE,MAAM,cAAc,MAAM,OAAO,MAAM,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC;AACzE,UAAI;AACJ;AAAA,IACF;AAEA,QAAI,OAAO,KAAK;AACd,aAAO,KAAK,EAAE,MAAM,cAAc,MAAM,IAAI,UAAU,EAAE,CAAC;AACzD,WAAK;AACL;AAAA,IACF;AACA,QAAI,OAAO,KAAK;AACd,aAAO,KAAK,EAAE,MAAM,eAAe,MAAM,IAAI,UAAU,EAAE,CAAC;AAC1D,WAAK;AACL;AAAA,IACF;AACA,QAAI,OAAO,KAAK;AACd,aAAO,KAAK,EAAE,MAAM,SAAS,MAAM,IAAI,UAAU,EAAE,CAAC;AACpD,WAAK;AACL;AAAA,IACF;AAEA,UAAM,MAAM,OAAO,MAAM,GAAG,IAAI,CAAC;AACjC,QAAI,mBAAmB,SAAS,GAAG,GAAG;AACpC,aAAO,KAAK,EAAE,MAAM,YAAY,MAAM,KAAK,UAAU,EAAE,CAAC;AACxD,WAAK;AACL;AAAA,IACF;AACA,QAAI,mBAAmB,SAAS,EAAE,GAAG;AACnC,aAAO,KAAK,EAAE,MAAM,YAAY,MAAM,IAAI,UAAU,EAAE,CAAC;AACvD,WAAK;AACL;AAAA,IACF;AAEA,UAAM,IAAI,aAAa,KAAK,EAAE,2CAA2C,CAAC;AAAA,EAC5E;AAEA,SAAO,KAAK,EAAE,MAAM,OAAO,MAAM,IAAI,UAAU,OAAO,OAAO,CAAC;AAC9D,SAAO;AACT;AAYA,IAAM,uBAAuB,CAAC,KAAK,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI;AAEnE,IAAM,SAAN,MAAa;AAAA,EACM;AAAA,EACT,QAAQ;AAAA,EACP,aAAuB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhB;AAAA,EAEjB,YAAY,QAAiB;AAC3B,SAAK,SAAS;AACd,SAAK,WAAW,OAAO,OAAO,SAAS,CAAC,KAAK;AAAA,MAC3C,MAAM;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EAEQ,OAAc;AACpB,WAAO,KAAK,OAAO,KAAK,KAAK,KAAK,KAAK;AAAA,EACzC;AAAA,EAEQ,OAAc;AACpB,WAAO,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK;AAAA,EAC3C;AAAA,EAEQ,cAAc,YAA6C;AACjE,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,MAAM,SAAS,cAAc,WAAW,SAAS,MAAM,IAAI,GAAG;AAChE,WAAK,SAAS;AACd,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,eAA2B;AACzB,UAAM,aAAa,KAAK,gBAAgB;AACxC,UAAM,WAAW,KAAK,KAAK;AAC3B,QAAI,SAAS,SAAS,OAAO;AAC3B,YAAM,IAAI;AAAA,QACR,0DAAqD,SAAS,IAAI;AAAA,QAClE,SAAS;AAAA,MACX;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAA8B;AACpC,QAAI,OAAO,KAAK,YAAY;AAC5B,QAAI,WAAW,KAAK,cAAc,oBAAoB;AACtD,WAAO,UAAU;AACf,YAAM,QAAQ,KAAK,YAAY;AAC/B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,SAAS;AAAA,QACnB;AAAA,QACA;AAAA,QACA,UAAU,SAAS;AAAA,MACrB;AACA,iBAAW,KAAK,cAAc,oBAAoB;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cAA0B;AAChC,QAAI,OAAO,KAAK,cAAc;AAC9B,QAAI,WAAW,KAAK,cAAc,CAAC,GAAG,CAAC;AACvC,WAAO,UAAU;AACf,YAAM,QAAQ,KAAK,cAAc;AACjC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA,UAAU,SAAS;AAAA,MACrB;AACA,iBAAW,KAAK,cAAc,CAAC,GAAG,CAAC;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAA4B;AAClC,QAAI,OAAO,KAAK,oBAAoB;AACpC,QAAI,WAAW,KAAK,cAAc,CAAC,KAAK,GAAG,CAAC;AAC5C,WAAO,UAAU;AACf,YAAM,QAAQ,KAAK,oBAAoB;AACvC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,SAAS;AAAA,QACnB;AAAA,QACA;AAAA,QACA,UAAU,SAAS;AAAA,MACrB;AACA,iBAAW,KAAK,cAAc,CAAC,KAAK,GAAG,CAAC;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,sBAAkC;AACxC,QAAI,OAAO,KAAK,WAAW;AAC3B,QAAI,WAAW,KAAK,cAAc,CAAC,KAAK,KAAK,GAAG,CAAC;AACjD,WAAO,UAAU;AACf,YAAM,QAAQ,KAAK,WAAW;AAC9B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,SAAS;AAAA,QACnB;AAAA,QACA;AAAA,QACA,UAAU,SAAS;AAAA,MACrB;AACA,iBAAW,KAAK,cAAc,CAAC,KAAK,KAAK,GAAG,CAAC;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAyB;AAC/B,UAAM,QAAQ,KAAK,cAAc,CAAC,GAAG,CAAC;AACtC,QAAI,OAAO;AACT,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS,KAAK,WAAW;AAAA,QACzB,UAAU,MAAM;AAAA,MAClB;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,cAAc,CAAC,GAAG,CAAC;AACrC,QAAI,KAAM,QAAO,KAAK,WAAW;AACjC,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA,EAEQ,eAA2B;AACjC,UAAM,QAAQ,KAAK,KAAK;AAExB,QAAI,MAAM,SAAS,UAAU;AAC3B,aAAO,EAAE,MAAM,UAAU,OAAO,OAAO,MAAM,IAAI,GAAG,UAAU,MAAM,SAAS;AAAA,IAC/E;AAEA,QAAI,MAAM,SAAS,UAAU;AAC3B,aAAO,EAAE,MAAM,UAAU,OAAO,MAAM,MAAM,UAAU,MAAM,SAAS;AAAA,IACvE;AAEA,QAAI,MAAM,SAAS,aAAa;AAC9B,UAAI,CAAC,KAAK,WAAW,SAAS,MAAM,IAAI,EAAG,MAAK,WAAW,KAAK,MAAM,IAAI;AAC1E,aAAO,EAAE,MAAM,aAAa,MAAM,MAAM,MAAM,UAAU,MAAM,SAAS;AAAA,IACzE;AAEA,QAAI,MAAM,SAAS,cAAc;AAC/B,YAAM,QAAQ,KAAK,gBAAgB;AACnC,YAAM,QAAQ,KAAK,KAAK;AACxB,UAAI,MAAM,SAAS,eAAe;AAChC,cAAM,IAAI;AAAA,UACR;AAAA,UACA,MAAM;AAAA,QACR;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,SAAS,cAAc;AAC/B,YAAM,QAAQ,MAAM,KAAK,YAAY;AACrC,UAAI,UAAU,UAAU,UAAU,SAAS;AACzC,eAAO;AAAA,UACL,MAAM;AAAA,UACN,OAAO,UAAU;AAAA,UACjB,UAAU,MAAM;AAAA,QAClB;AAAA,MACF;AACA,UAAI,CAAE,eAA4B,SAAS,KAAK,GAAG;AACjD,cAAM,IAAI;AAAA,UACR,iCAAiC,MAAM,IAAI,sCAAsC,MAAM,IAAI;AAAA,UAC3F,MAAM;AAAA,QACR;AAAA,MACF;AACA,YAAM,OAAO;AACb,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,KAAK,SAAS,cAAc;AAC9B,cAAM,IAAI;AAAA,UACR,KAAK,IAAI,oCAAoC,eAAe,IAAI,EAAE,SAAS;AAAA,UAC3E,MAAM;AAAA,QACR;AAAA,MACF;AACA,YAAM,OAAqB,CAAC;AAC5B,UAAI,KAAK,KAAK,EAAE,SAAS,eAAe;AACtC,aAAK,SAAS;AAAA,MAChB,OAAO;AACL,mBAAS;AACP,eAAK,KAAK,KAAK,gBAAgB,CAAC;AAChC,gBAAM,YAAY,KAAK,KAAK;AAC5B,cAAI,UAAU,SAAS,QAAS;AAChC,cAAI,UAAU,SAAS,cAAe;AACtC,gBAAM,IAAI;AAAA,YACR,KAAK,IAAI;AAAA,YACT,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AACA,YAAM,OAAO,eAAe,IAAI;AAChC,UAAI,KAAK,SAAS,KAAK,OAAO,KAAK,SAAS,KAAK,KAAK;AACpD,cAAM,IAAI;AAAA,UACR,KAAK,IAAI,gBAAgB,KAAK,MAAM,SAAS,KAAK,WAAW,IAAI,KAAK,GAAG,SAAS,KAAK,SAAS;AAAA,UAChG,MAAM;AAAA,QACR;AAAA,MACF;AACA,aAAO,EAAE,MAAM,QAAQ,MAAM,MAAM,UAAU,MAAM,SAAS;AAAA,IAC9D;AAEA,QAAI,MAAM,SAAS,OAAO;AACxB,YAAM,IAAI,aAAa,4CAA4C,MAAM,QAAQ;AAAA,IACnF;AAEA,UAAM,IAAI;AAAA,MACR,KAAK,MAAM,IAAI;AAAA,MACf,MAAM;AAAA,IACR;AAAA,EACF;AACF;AASO,SAAS,aAAa,QAA6B;AACxD,MAAI;AACF,UAAM,OAAO,UAAU;AACvB,QAAI,KAAK,KAAK,MAAM,IAAI;AACtB,aAAO,EAAE,IAAI,OAAO,OAAO,0BAA0B,UAAU,EAAE;AAAA,IACnE;AACA,UAAM,SAAS,IAAI,OAAO,SAAS,IAAI,CAAC;AACxC,UAAM,MAAM,OAAO,aAAa;AAChC,WAAO,EAAE,IAAI,MAAM,KAAK,YAAY,OAAO,WAAW;AAAA,EACxD,SAAS,OAAO;AACd,QAAI,iBAAiB,cAAc;AACjC,aAAO,EAAE,IAAI,OAAO,OAAO,MAAM,SAAS,UAAU,MAAM,SAAS;AAAA,IACrE;AACA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAIA,IAAM,UAAU,uBAAO,gBAAgB;AAEvC,SAAS,QAAQ,OAA8B;AAC7C,SAAO,UAAU,QAAQ,UAAU;AACrC;AAGA,SAAS,aAAa,GAAmB;AACvC,MAAI,OAAO,UAAU,CAAC,EAAG,QAAO,OAAO,CAAC;AACxC,QAAM,UAAU,OAAO,EAAE,YAAY,EAAE,CAAC;AACxC,SAAO,OAAO,OAAO,SAAS,OAAO,IAAI,UAAU,CAAC;AACtD;AAEA,SAAS,OAAO,OAA6B;AAC3C,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,OAAO,UAAU,SAAU,QAAO,aAAa,KAAK;AACxD,MAAI,OAAO,UAAU,UAAW,QAAO,QAAQ,SAAS;AACxD,SAAO;AACT;AAGA,SAAS,YAAY,OAAoC;AACvD,MAAI,UAAU,QAAQ,UAAU,GAAI,QAAO;AAC3C,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,UAAW,QAAO,QAAQ,IAAI;AACnD,QAAM,UAAU,MAAM,QAAQ,eAAe,EAAE;AAC/C,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,IAAI,OAAO,OAAO;AACxB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAS,cAAc,OAAqB,MAAsB;AAChE,QAAM,IAAI,YAAY,KAAK;AAC3B,MAAI,MAAM,MAAM;AACd,UAAM,IAAI,aAAa,GAAG,IAAI,6BAA6B,OAAO,KAAK,CAAC,IAAI;AAAA,EAC9E;AACA,SAAO;AACT;AAEA,SAAS,OAAO,OAA8B;AAC5C,MAAI,UAAU,QAAQ,UAAU,GAAI,QAAO;AAC3C,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO,UAAU;AAChD,SAAO;AACT;AAGA,SAAS,cAAc,MAAoB,OAA6B;AACtE,QAAM,eAAe,CAAC,MACpB,OAAO,MAAM,YACZ,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,MAAM,YAAY,CAAC,MAAM;AAGlE,MAAI,QAAQ,IAAI,KAAK,QAAQ,KAAK,EAAG,QAAO;AAC5C,QAAM,oBACH,QAAQ,IAAI,KAAK,aAAa,KAAK,KACnC,QAAQ,KAAK,KAAK,aAAa,IAAI;AACtC,MAAI,qBAAsB,aAAa,IAAI,KAAK,aAAa,KAAK,GAAI;AACpE,UAAMA,KAAI,YAAY,IAAI,KAAK;AAC/B,UAAMC,KAAI,YAAY,KAAK,KAAK;AAChC,WAAOD,KAAIC,KAAI,KAAKD,KAAIC,KAAI,IAAI;AAAA,EAClC;AACA,MAAI,OAAO,SAAS,aAAa,OAAO,UAAU,WAAW;AAC3D,UAAMD,KAAI,OAAO,IAAI;AACrB,UAAMC,KAAI,OAAO,KAAK;AACtB,WAAOD,OAAMC,KAAI,IAAID,KAAI,IAAI;AAAA,EAC/B;AACA,QAAM,IAAI,OAAO,IAAI;AACrB,QAAM,IAAI,OAAO,KAAK;AACtB,SAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAClC;AAIA,IAAM,eAAe;AAIrB,SAAS,YAAY,OAAqB,MAA0B;AAClE,MAAI,UAAU,QAAQ,UAAU,IAAI;AAClC,UAAM,IAAI,aAAa,GAAG,IAAI,yCAAyC;AAAA,EACzE;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,UAAM,KAAK,KAAK,MAAM,OAAO;AAC7B,QAAI,CAAC,OAAO,MAAM,EAAE,EAAG,QAAO,EAAE,IAAI,UAAU,aAAa,KAAK,OAAO,EAAE;AAAA,EAC3E;AACA,QAAM,IAAI,aAAa,GAAG,IAAI,2BAA2B,OAAO,KAAK,CAAC,IAAI;AAC5E;AAEA,SAAS,QAAQ,IAAY,UAA2B;AACtD,QAAM,MAAM,IAAI,KAAK,EAAE,EAAE,YAAY;AACrC,SAAO,WAAW,IAAI,MAAM,GAAG,EAAE,IAAI;AACvC;AAEA,IAAM,iBAAyC;AAAA,EAC7C,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AACX;AAEA,SAAS,YACP,OACA,SACA,IACQ;AACR,QAAM,OAAO,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY;AAC9C,MAAI,CAAC,QAAQ,SAAS,IAAI,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR,KAAK,EAAE,mBAAmB,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,gBAAW,OAAO,KAAK,CAAC;AAAA,IAC3F;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,UAAU,IAAY,OAAuB;AACpD,QAAM,IAAI,IAAI,KAAK,EAAE;AACrB,QAAM,MAAM,EAAE,WAAW;AACzB,QAAM,SAAS,IAAI,KAAK,EAAE;AAC1B,SAAO,WAAW,CAAC;AACnB,SAAO,YAAY,OAAO,YAAY,IAAI,KAAK;AAC/C,QAAM,UAAU,IAAI;AAAA,IAClB,KAAK,IAAI,OAAO,eAAe,GAAG,OAAO,YAAY,IAAI,GAAG,CAAC;AAAA,EAC/D,EAAE,WAAW;AACb,SAAO,WAAW,KAAK,IAAI,KAAK,OAAO,CAAC;AACxC,SAAO,OAAO,QAAQ;AACxB;AAKA,SAAS,YAAY,QAAwB,IAAsB;AACjE,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,QAAQ;AAC1B,QAAI,QAAQ,KAAK,EAAG;AACpB,QAAI,KAAK,cAAc,OAAO,KAAK,EAAE,IAAI,CAAC;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,SAAS,cAAc,GAAW,QAAwB;AACxD,QAAM,SAAS,MAAM;AACrB,QAAM,SAAS,IAAI;AAEnB,QAAM,YAAY,OAAO,OAAO,YAAY,EAAE,CAAC;AAC/C,QAAM,UACJ,YAAY,IAAI,CAAC,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,MAAM,SAAS;AAChE,SAAO,UAAU;AACnB;AASA,SAAS,MAAM,MAA+B,OAA6B;AACzE,SAAO,KAAK,KAAK,KAAK;AACxB;AAEA,SAAS,aACP,MACA,MACc;AACd,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,YAAY,MAAM,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAAA,IAC3D,KAAK,OAAO;AACV,YAAM,OAAO,YAAY,MAAM,KAAK;AACpC,aAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI;AAAA,IACpD;AAAA,IACA,KAAK,OAAO;AACV,YAAM,OAAO,YAAY,MAAM,KAAK;AACpC,aAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI;AAAA,IACpD;AAAA,IACA,KAAK,WAAW;AACd,YAAM,OAAO,YAAY,MAAM,SAAS;AACxC,UAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,aAAO,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK;AAAA,IAChD;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,IAAI,cAAc,MAAM,MAAM,CAAC,GAAG,SAAS;AACjD,YAAM,SACJ,KAAK,SAAS,IAAI,KAAK,MAAM,cAAc,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI;AAC3E,aAAO,cAAc,GAAG,MAAM;AAAA,IAChC;AAAA,IACA,KAAK;AACH,aAAO,KAAK,IAAI,cAAc,MAAM,MAAM,CAAC,GAAG,OAAO,CAAC;AAAA,IACxD,KAAK;AACH,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE;AAAA,IAChC,KAAK;AACH,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,IAC5C,KAAK;AACH,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,IAC5C,KAAK;AACH,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK;AAAA,IACrC,KAAK;AACH,aAAO,KAAK,IAAI,MAAM,EAAE,KAAK,EAAE;AAAA,IACjC,KAAK,QAAQ;AACX,YAAM,QAAQ,KAAK,MAAM,cAAc,MAAM,MAAM,CAAC,GAAG,QAAQ,CAAC;AAChE,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,IAC3D;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,QAAQ,KAAK,MAAM,cAAc,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;AACjE,UAAI,SAAS,EAAG,QAAO;AACvB,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK;AAAA,IAC5C;AAAA,IACA,KAAK;AACH,aAAO,OAAO,MAAM,MAAM,CAAC,CAAC,EACzB,YAAY,EACZ,SAAS,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC;AAAA,IAClD,KAAK;AACH,aAAO,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;AAAA,IAC/B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,QAAQ,MAAM,MAAM,CAAC,CAAC;AAAA,IAC/B,KAAK;AACH,cAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,IAC7C,KAAK;AACH,cAAO,oBAAI,KAAK,GAAE,YAAY;AAAA,IAChC,KAAK,YAAY;AACf,YAAM,OAAO,YAAY,MAAM,MAAM,CAAC,GAAG,YAAY;AACrD,YAAM,KAAK,YAAY,MAAM,MAAM,CAAC,GAAG,YAAY;AACnD,YAAM,OAAO,YAAY,MAAM,MAAM,CAAC,GAAG,OAAO,KAAK,cAAc,GAAG,UAAU;AAGhF,YAAM,UAAU,eAAe,IAAI;AACnC,UAAI,YAAY,OAAW,QAAO;AAClC,aAAO,KAAK,OAAO,GAAG,KAAK,KAAK,MAAM,OAAO;AAAA,IAC/C;AAAA,IACA,KAAK;AACH,aAAO,IAAI,KAAK,YAAY,MAAM,MAAM,CAAC,GAAG,QAAQ,EAAE,EAAE,EAAE,eAAe;AAAA,IAC3E,KAAK;AACH,aAAO,IAAI,KAAK,YAAY,MAAM,MAAM,CAAC,GAAG,SAAS,EAAE,EAAE,EAAE,YAAY,IAAI;AAAA,IAC7E,KAAK;AACH,aAAO,IAAI,KAAK,YAAY,MAAM,MAAM,CAAC,GAAG,OAAO,EAAE,EAAE,EAAE,WAAW;AAAA,IACtE,KAAK,WAAW;AACd,YAAM,OAAO,YAAY,MAAM,MAAM,CAAC,GAAG,WAAW;AACpD,YAAM,QAAQ,KAAK,MAAM,cAAc,MAAM,MAAM,CAAC,GAAG,WAAW,CAAC;AACnE,YAAM,OAAO,YAAY,MAAM,MAAM,CAAC,GAAG,CAAC,QAAQ,UAAU,OAAO,GAAG,SAAS;AAC/E,YAAM,KACJ,SAAS,SACL,KAAK,KAAK,QAAQ,QAClB,UAAU,KAAK,IAAI,SAAS,WAAW,QAAQ,QAAQ,EAAE;AAC/D,aAAO,QAAQ,IAAI,KAAK,QAAQ;AAAA,IAClC;AAAA;AAAA,IAEA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,YAAM,IAAI,aAAa,KAAK,IAAI,4BAA4B;AAAA,EAChE;AACF;AAEA,SAAS,YACP,UACA,MACA,OACc;AACd,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,OAAO,IAAI,IAAI,OAAO,KAAK;AAAA,IACpC,KAAK;AACH,aACE,cAAc,MAAM,KAAK,IAAI,cAAc,OAAO,KAAK;AAAA,IAE3D,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,IAAI,cAAc,OAAO,KAAK;AAAA,IAChE,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,IAAI,cAAc,OAAO,KAAK;AAAA,IAChE,KAAK,KAAK;AACR,YAAM,UAAU,cAAc,OAAO,KAAK;AAC1C,UAAI,YAAY,GAAG;AACjB,cAAM,IAAI,aAAa,+BAA+B;AAAA,MACxD;AACA,aAAO,cAAc,MAAM,KAAK,IAAI;AAAA,IACtC;AAAA,IACA,KAAK,KAAK;AACR,YAAM,UAAU,cAAc,OAAO,KAAK;AAC1C,UAAI,YAAY,GAAG;AACjB,cAAM,IAAI,aAAa,+BAA+B;AAAA,MACxD;AACA,aAAO,cAAc,MAAM,KAAK,IAAI;AAAA,IACtC;AAAA,IACA,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,MAAM;AAAA,IACxC,KAAK;AAAA,IACL,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,MAAM;AAAA,IACxC,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,IAAI;AAAA,IACtC,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,KAAK;AAAA,IACvC,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,IAAI;AAAA,IACtC,KAAK;AACH,aAAO,cAAc,MAAM,KAAK,KAAK;AAAA,EACzC;AACF;AAEA,SAAS,cAAc,KAAc,MAA4B;AAC/D,MAAI,QAAQ,SAAS;AACnB,UAAM,IAAI,aAAa,8BAA8B,IAAI,IAAI;AAAA,EAC/D;AACA,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAW,QAAO;AAChE,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI,CAAC,OAAO,SAAS,GAAG,GAAG;AACzB,YAAM,IAAI,aAAa,iBAAiB,IAAI,2BAA2B;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AACA,MAAI,eAAe,MAAM;AACvB,WAAO,OAAO,MAAM,IAAI,QAAQ,CAAC,IAAI,OAAO,IAAI,YAAY;AAAA,EAC9D;AAEA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,GAAG;AAAA,EACnB;AACF;AAEA,SAAS,aAAa,MAAkB,SAAoC;AAC1E,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK;AAAA,IACd,KAAK,aAAa;AAChB,YAAM,MAAM,QAAQ,KAAK,IAAI;AAE7B,aAAO,cAAc,QAAQ,SAAY,UAAU,KAAK,KAAK,IAAI;AAAA,IACnE;AAAA,IACA,KAAK;AACH,aAAO,CAAC,cAAc,aAAa,KAAK,SAAS,OAAO,GAAG,KAAK;AAAA,IAClE,KAAK;AACH,aAAO;AAAA,QACL,KAAK;AAAA,QACL,aAAa,KAAK,MAAM,OAAO;AAAA,QAC/B,aAAa,KAAK,OAAO,OAAO;AAAA,MAClC;AAAA,IACF,KAAK,QAAQ;AACX,UAAI,KAAK,SAAS,MAAM;AAGtB,cAAM,OAAO,KAAK,KAAK,CAAC;AACxB,cAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,cAAM,YAAY,OAAO,OAAO,aAAa,MAAM,OAAO,IAAI,IAAI;AAClE,YAAI,UAAW,QAAO,WAAW,aAAa,UAAU,OAAO,IAAI;AACnE,cAAM,YAAY,KAAK,KAAK,CAAC;AAC7B,eAAO,KAAK,KAAK,SAAS,KAAK,YAC3B,aAAa,WAAW,OAAO,IAC/B;AAAA,MACN;AACA,UAAI,KAAK,SAAS,OAAO;AACvB,mBAAW,OAAO,KAAK,MAAM;AAC3B,cAAI,CAAC,OAAO,aAAa,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,QAClD;AACA,eAAO;AAAA,MACT;AACA,UAAI,KAAK,SAAS,MAAM;AACtB,mBAAW,OAAO,KAAK,MAAM;AAC3B,cAAI,OAAO,aAAa,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,QACjD;AACA,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,KAAK;AAAA,QACL,KAAK,KAAK,IAAI,CAAC,QAAQ,aAAa,KAAK,OAAO,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACF;AAUO,SAAS,gBACd,KACA,SACe;AACf,MAAI;AACF,UAAM,QAAQ,aAAa,KAAK,OAAO;AACvC,QAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;AACxD,aAAO,EAAE,IAAI,OAAO,OAAO,oDAAoD;AAAA,IACjF;AACA,WAAO,EAAE,IAAI,MAAM,MAAM;AAAA,EAC3B,SAAS,OAAO;AACd,QAAI,iBAAiB,aAAc,QAAO,EAAE,IAAI,OAAO,OAAO,MAAM,QAAQ;AAC5E,WAAO,EAAE,IAAI,OAAO,OAAO,wCAAwC;AAAA,EACrE;AACF;AASO,SAAS,kBACd,KACA,YACmB;AACnB,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,WAAW,IAAI,IAAI,KAAK;AAAA,IACjC,KAAK,UAAU;AACb,UAAI,IAAI,aAAa,IAAK,QAAO;AACjC,UAAI,qBAAqB,SAAS,IAAI,QAAQ,EAAG,QAAO;AACxD,aAAO;AAAA,IACT;AAAA,IACA,KAAK,QAAQ;AACX,UAAI,IAAI,SAAS,MAAM;AAIrB,cAAM,UAAU,IAAI,KAAK,CAAC;AAC1B,cAAM,OAAO,UAAU,kBAAkB,SAAS,UAAU,IAAI;AAChE,cAAM,eAAe,IAAI,KAAK,CAAC;AAC/B,YAAI,IAAI,KAAK,SAAS,KAAK,CAAC,aAAc,QAAO;AACjD,cAAM,YAAY,kBAAkB,cAAc,UAAU;AAC5D,YAAI,SAAS,UAAW,QAAO;AAC/B,YAAI,SAAS,UAAW,QAAO;AAC/B,YAAI,cAAc,UAAW,QAAO;AACpC,eAAO;AAAA,MACT;AACA,UAAI,IAAI,SAAS,SAAS,IAAI,SAAS,OAAO;AAE5C,cAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;AAClE,YAAI,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,MAAM,MAAM,MAAM,EAAG,QAAO;AACjE,eAAO;AAAA,MACT;AACA,aAAO,eAAe,IAAI,IAAI,EAAE;AAAA,IAClC;AAAA,EACF;AACF;AA4BA,SAAS,oBAAoB,OAA2C;AACtE,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,SAAU,SAAkC;AAClD,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,MAAK,OAA4B,OAAO,UAAW,QAAO;AAC1D,QAAM,UAAW,OAAiC;AAClD,QAAM,UACJ,WAAW,OAAO,YAAY,WACzB,QAAkC,UACnC;AACN,QAAM,aACJ,WAAW,OAAO,YAAY,WACzB,QAAqC,aACtC;AACN,SAAO,OAAO,eAAe,WAAW,aAAa;AACvD;AAGO,SAAS,gBAAgB,OAAqC;AACnE,SAAO,oBAAoB,KAAK,MAAM;AACxC;AAGO,SAAS,iBACd,QACqB;AACrB,SAAO,OAAO,QAAQ,CAAC,UAAU;AAC/B,UAAM,aAAa,oBAAoB,KAAK;AAC5C,QAAI,eAAe,KAAM,QAAO,CAAC;AACjC,WAAO,CAAC,EAAE,OAAO,QAAQ,aAAa,UAAU,EAAE,CAAC;AAAA,EACrD,CAAC;AACH;AAoBO,SAAS,oBAGd,MAAoB,QAA6C;AACjE,QAAM,UAAU,iBAAiB,MAAM;AACvC,QAAM,oBAAoB,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,UAAU,CAAC;AACxE,QAAM,SAAS,oBAAI,IAAoB;AACvC,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,MAAmB,QAAQ,kBAAkB;AAAA,EACxD;AAMA,QAAM,uBAAuB,oBAAI,IAAoB;AACrD,aAAW,KAAK,QAAQ;AACtB,yBAAqB,IAAI,EAAE,aAAa,YAAY,GAAG,EAAE,UAAU;AAAA,EACrE;AACA,aAAW,KAAK,QAAQ;AAEtB,yBAAqB,IAAI,EAAE,WAAW,YAAY,GAAG,EAAE,UAAU;AAAA,EACnE;AACA,QAAM,WAAW,KAAK,IAAI,CAAC,QAAQ;AACjC,UAAM,OAAgC,EAAE,GAAI,IAAI,QAAQ,CAAC,EAAG;AAC5D,UAAM,UAAuB,CAAC,SAAS;AACrC,UAAI,QAAQ,KAAM,QAAO,KAAK,IAAI;AAClC,YAAM,YAAY,qBAAqB,IAAI,KAAK,YAAY,CAAC;AAC7D,UAAI,cAAc,OAAW,QAAO;AACpC,aAAO,KAAK,SAAS,KAAK;AAAA,IAC5B;AACA,eAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,UAAI,CAAC,OAAO,IAAI;AACd,aAAK,MAAM,UAAU,IAAI;AACzB,eAAO,IAAI,GAAG,IAAI,EAAE,KAAK,MAAM,UAAU,IAAI,OAAO,KAAK;AACzD;AAAA,MACF;AACA,YAAM,SAAS,gBAAgB,OAAO,KAAK,OAAO;AAClD,UAAI,OAAO,GAAI,MAAK,MAAM,UAAU,IAAI,OAAO;AAAA,WAC1C;AACH,aAAK,MAAM,UAAU,IAAI;AACzB,eAAO,IAAI,GAAG,IAAI,EAAE,KAAK,MAAM,UAAU,IAAI,OAAO,KAAK;AAAA,MAC3D;AAAA,IACF;AACA,WAAO,EAAE,GAAG,KAAK,KAAK;AAAA,EACxB,CAAC;AACD,SAAO,EAAE,MAAM,UAAU,QAAQ,kBAAkB;AACrD;AAgBO,SAAS,yBACd,YACA,MACA,IACQ;AACR,QAAM,SAAS,KAAK,KAAK,EAAE,YAAY;AACvC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,MAAM;AACV,MAAI,IAAI;AACR,SAAO,IAAI,WAAW,QAAQ;AAC5B,UAAM,KAAK,WAAW,CAAC;AACvB,QAAI,OAAO,OAAO,OAAO,KAAK;AAC5B,UAAI,IAAI,IAAI;AACZ,aAAO,IAAI,WAAW,QAAQ;AAC5B,YAAI,WAAW,CAAC,MAAM,QAAQ,IAAI,IAAI,WAAW,QAAQ;AACvD,eAAK;AACL;AAAA,QACF;AACA,YAAI,WAAW,CAAC,MAAM,IAAI;AACxB,eAAK;AACL;AAAA,QACF;AACA,aAAK;AAAA,MACP;AACA,aAAO,WAAW,MAAM,GAAG,CAAC;AAC5B,UAAI;AACJ;AAAA,IACF;AACA,QAAI,OAAO,KAAK;AACd,YAAM,QAAQ,WAAW,QAAQ,KAAK,IAAI,CAAC;AAC3C,UAAI,UAAU,IAAI;AAChB,eAAO,WAAW,MAAM,CAAC;AACzB;AAAA,MACF;AACA,YAAM,OAAO,WAAW,MAAM,IAAI,GAAG,KAAK;AAC1C,aAAO,KAAK,KAAK,EAAE,YAAY,MAAM,SAAS,IAAI,EAAE,MAAM,WAAW,MAAM,GAAG,QAAQ,CAAC;AACvF,UAAI,QAAQ;AACZ;AAAA,IACF;AACA,WAAO;AACP,SAAK;AAAA,EACP;AACA,SAAO;AACT;","names":["a","b"]}
1
+ {"version":3,"sources":["../../src/formulas/index.ts","../../src/formulas/formulas.ts"],"sourcesContent":["export * from \"./formulas\";\n","/**\n * @ai-matrx/design-system/formulas — THE formula language for computed columns.\n *\n * This module moved here from the app so every table in the product shares\n * one formula language. It stays pure: no React, no DOM, no network.\n *\n * A **formula column** holds nothing. Its cell is always `null` in the\n * database; the value the user sees is computed, on read, from the OTHER\n * columns of the SAME row. Champion: Airtable's formula field.\n *\n * THE THREE RULES THIS FILE OBEYS\n *\n * 1. **No `eval`, no `new Function`, no dynamic code.** A formula is user\n * content; it is tokenized, parsed into an AST, and interpreted. Nothing a\n * user types ever becomes JavaScript.\n * 2. **Nothing throws.** `parseFormula` and `evaluateFormula` both return a\n * discriminated result. A syntax error, a missing column, a division by\n * zero and a type mismatch are all `ok: false` with a plain-English\n * sentence a non-technical person can act on — never an exception, never a\n * silent blank cell (THE FALLBACK LAW, `lib/field-formats/FEATURE.md`).\n * 3. **The engine is pure.** It reads cells through a caller-supplied\n * `resolve(name)`. It never touches Redux, Supabase, or the DOM, which is\n * what makes every rule below testable.\n *\n * THE COERCION RULES (the whole contract, in one place)\n *\n * - **BLANK** is `null`, `undefined`, or `\"\"`. All three are the same value to\n * a formula, because a text cell that was cleared holds `\"\"` and a number\n * cell that was cleared holds `null`, and a user does not distinguish them.\n * - **A missing column is NOT blank.** `resolve()` returns `undefined` for a\n * column that does not exist (as opposed to `null` for one that is empty),\n * and that is an error: `{a} + {Typo}` must say the column is unknown rather\n * than quietly computing with zero.\n * - **Arithmetic treats BLANK as 0.** `{a} + {b}` with `b` empty is `{a}`.\n * That is the tolerant reading every spreadsheet uses, and the alternative\n * (the whole cell erroring because one input is empty) makes a formula\n * column useless on real, partly-filled data.\n * - **Numeric strings coerce.** `\"1,234\"`, `\" 42 \"`, `\"$5\"` and `\"3.5\"` are\n * numbers in arithmetic; `\"n/a\"` is a type error naming the value.\n * - **Booleans are 1 and 0 in arithmetic**, so SUM over a checkbox column\n * counts the ticks.\n * - **Aggregates SKIP blanks.** `SUM`/`MIN`/`MAX`/`AVERAGE` ignore empty\n * inputs entirely — `AVERAGE` divides by the count of the values that were\n * actually there, and returns BLANK when there were none.\n * - **Text conversion**: BLANK becomes `\"\"`, a number becomes its shortest\n * exact decimal, a boolean becomes `\"true\"` / `\"false\"`.\n * - **Comparison** is numeric when both sides read as numbers, otherwise a\n * plain string comparison. BLANK takes the shape of what it is compared\n * against — `0` beside a number, `\"\"` beside text — so `{a} = BLANK()`,\n * `{a} = 0` and `{a} = ''` all hold for an empty cell.\n * - **Truthiness** (for `IF` / `AND` / `OR` / `NOT`): BLANK is false, a\n * boolean is itself, a number is true unless `0`, a string is true unless\n * empty.\n * - **Dates are ISO strings, read and written in UTC.** A date function\n * accepts an ISO string (or a `Date`) and returns an ISO string — date-only\n * (`2026-09-14`) when the input was date-only, full (`2026-09-14T…Z`)\n * otherwise. UTC throughout on purpose: reading `2026-09-14` with local\n * getters turns it into the 13th for half the planet.\n * - **Division (and `%`) by zero is an error**, never `Infinity` and never\n * `NaN`. A cell may not lie about arithmetic that did not happen.\n */\n\n// ─── AST ─────────────────────────────────────────────────────────────────────\n\nexport type FormulaValue = string | number | boolean | null;\n\nexport type BinaryOperator =\n | \"+\"\n | \"-\"\n | \"*\"\n | \"/\"\n | \"%\"\n | \"&\"\n | \"=\"\n | \"!=\"\n | \"<>\"\n | \"<\"\n | \"<=\"\n | \">\"\n | \">=\";\n\nexport type FormulaAst =\n | { kind: \"number\"; value: number; position: number }\n | { kind: \"string\"; value: string; position: number }\n | { kind: \"boolean\"; value: boolean; position: number }\n | { kind: \"reference\"; name: string; position: number }\n | { kind: \"unary\"; operator: \"-\"; operand: FormulaAst; position: number }\n | {\n kind: \"binary\";\n operator: BinaryOperator;\n left: FormulaAst;\n right: FormulaAst;\n position: number;\n }\n | {\n kind: \"call\";\n name: FormulaFunctionName;\n args: FormulaAst[];\n position: number;\n };\n\nexport type ParseResult =\n | { ok: true; ast: FormulaAst; references: string[] }\n | { ok: false; error: string; position: number };\n\nexport type FormulaResult =\n | { ok: true; value: FormulaValue }\n | { ok: false; error: string };\n\nexport type FormulaResultType = \"number\" | \"text\" | \"boolean\" | \"date\" | \"unknown\";\n\n/** What a cell lookup may answer: a value, BLANK, or `undefined` = no such column. */\nexport type ResolveCell = (name: string) => unknown;\n\n/** Best-effort declared type of a column, for static result typing. */\nexport type ResolveColumnType = (\n name: string,\n) => \"number\" | \"text\" | \"boolean\" | \"date\" | undefined;\n\n// ─── the function table ──────────────────────────────────────────────────────\n\nconst FUNCTION_SPECS = {\n SUM: {\n signature: \"SUM(number, …)\",\n description: \"Adds every value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n MIN: {\n signature: \"MIN(number, …)\",\n description: \"The smallest value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n MAX: {\n signature: \"MAX(number, …)\",\n description: \"The largest value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n AVERAGE: {\n signature: \"AVERAGE(number, …)\",\n description:\n \"The average of the values that are filled in. Empty values are not counted.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n ROUND: {\n signature: \"ROUND(number, places?)\",\n description:\n \"Rounds to the given number of decimal places (0 when omitted). Halves round away from zero.\",\n min: 1,\n max: 2,\n result: \"number\",\n },\n ABS: {\n signature: \"ABS(number)\",\n description: \"The value without its minus sign.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n LEN: {\n signature: \"LEN(text)\",\n description: \"How many characters the text has.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n UPPER: {\n signature: \"UPPER(text)\",\n description: \"The text in capitals.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n LOWER: {\n signature: \"LOWER(text)\",\n description: \"The text in lower case.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n TRIM: {\n signature: \"TRIM(text)\",\n description: \"The text without leading or trailing spaces.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n CONCATENATE: {\n signature: \"CONCATENATE(text, …)\",\n description: \"Joins every value into one piece of text.\",\n min: 1,\n max: Infinity,\n result: \"text\",\n },\n LEFT: {\n signature: \"LEFT(text, count)\",\n description: \"The first few characters of the text.\",\n min: 2,\n max: 2,\n result: \"text\",\n },\n RIGHT: {\n signature: \"RIGHT(text, count)\",\n description: \"The last few characters of the text.\",\n min: 2,\n max: 2,\n result: \"text\",\n },\n CONTAINS: {\n signature: \"CONTAINS(text, part)\",\n description:\n \"Yes when the text contains that part. Upper and lower case are treated the same.\",\n min: 2,\n max: 2,\n result: \"boolean\",\n },\n IF: {\n signature: \"IF(condition, then, otherwise?)\",\n description:\n \"The second value when the condition holds, otherwise the third (empty when omitted).\",\n min: 2,\n max: 3,\n result: \"unknown\",\n },\n AND: {\n signature: \"AND(condition, …)\",\n description: \"Yes when every condition holds.\",\n min: 1,\n max: Infinity,\n result: \"boolean\",\n },\n OR: {\n signature: \"OR(condition, …)\",\n description: \"Yes when at least one condition holds.\",\n min: 1,\n max: Infinity,\n result: \"boolean\",\n },\n NOT: {\n signature: \"NOT(condition)\",\n description: \"Turns yes into no and no into yes.\",\n min: 1,\n max: 1,\n result: \"boolean\",\n },\n BLANK: {\n signature: \"BLANK()\",\n description: \"An empty value, for comparing against or returning.\",\n min: 0,\n max: 0,\n result: \"unknown\",\n },\n ISBLANK: {\n signature: \"ISBLANK(value)\",\n description: \"Yes when the value is empty.\",\n min: 1,\n max: 1,\n result: \"boolean\",\n },\n TODAY: {\n signature: \"TODAY()\",\n description: \"Today's date.\",\n min: 0,\n max: 0,\n result: \"date\",\n },\n NOW: {\n signature: \"NOW()\",\n description: \"The current date and time.\",\n min: 0,\n max: 0,\n result: \"date\",\n },\n DATEDIFF: {\n signature: \"DATEDIFF(from, to, 'days' | 'hours' | 'minutes')\",\n description:\n \"How far the second date is after the first. Negative when it is earlier.\",\n min: 3,\n max: 3,\n result: \"number\",\n },\n YEAR: {\n signature: \"YEAR(date)\",\n description: \"The four-digit year of the date.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n MONTH: {\n signature: \"MONTH(date)\",\n description: \"The month of the date, 1 to 12.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n DAY: {\n signature: \"DAY(date)\",\n description: \"The day of the month, 1 to 31.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n DATEADD: {\n signature: \"DATEADD(date, count, 'days' | 'months' | 'years')\",\n description:\n \"The date moved forward by that many units. Use a negative count to go back.\",\n min: 3,\n max: 3,\n result: \"date\",\n },\n} as const satisfies Record<\n string,\n {\n signature: string;\n description: string;\n min: number;\n max: number;\n result: FormulaResultType;\n }\n>;\n\nexport type FormulaFunctionName = keyof typeof FUNCTION_SPECS;\n\nconst FUNCTION_NAMES = Object.keys(FUNCTION_SPECS) as FormulaFunctionName[];\n\n/** The help list the formula editor shows. Order is the order it renders in. */\nexport const FORMULA_FUNCTIONS: readonly {\n name: FormulaFunctionName;\n signature: string;\n description: string;\n}[] = Object.freeze(\n FUNCTION_NAMES.map((name) => ({\n name,\n signature: FUNCTION_SPECS[name].signature,\n description: FUNCTION_SPECS[name].description,\n })),\n);\n\n// ─── tokenizer ───────────────────────────────────────────────────────────────\n\ntype TokenType =\n | \"number\"\n | \"string\"\n | \"identifier\"\n | \"reference\"\n | \"operator\"\n | \"paren-open\"\n | \"paren-close\"\n | \"comma\"\n | \"end\";\n\ntype Token = { type: TokenType; text: string; position: number };\n\n/** Thrown only INSIDE this module; every public entry point catches it. */\nclass FormulaError extends Error {\n readonly position: number;\n constructor(message: string, position = 0) {\n super(message);\n this.name = \"FormulaError\";\n this.position = position;\n }\n}\n\nconst TWO_CHAR_OPERATORS = [\"!=\", \"<>\", \"<=\", \">=\"];\nconst ONE_CHAR_OPERATORS = [\"+\", \"-\", \"*\", \"/\", \"%\", \"&\", \"=\", \"<\", \">\"];\n\nfunction isDigit(ch: string): boolean {\n return ch >= \"0\" && ch <= \"9\";\n}\n\nfunction isIdentifierStart(ch: string): boolean {\n return /[A-Za-z_]/.test(ch);\n}\n\nfunction isIdentifierPart(ch: string): boolean {\n return /[A-Za-z0-9_]/.test(ch);\n}\n\nfunction tokenize(source: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < source.length) {\n const ch = source[i];\n // `i < source.length` already guarantees a character here; the guard only\n // makes that knowledge explicit for the compiler.\n if (ch === undefined) break;\n\n if (/\\s/.test(ch)) {\n i += 1;\n continue;\n }\n\n if (ch === \"{\") {\n const close = source.indexOf(\"}\", i + 1);\n if (close === -1) {\n throw new FormulaError(\n \"This column reference is missing its closing brace `}`.\",\n i,\n );\n }\n const name = source.slice(i + 1, close).trim();\n if (name === \"\") {\n throw new FormulaError(\"This column reference has no name inside `{}`.\", i);\n }\n tokens.push({ type: \"reference\", text: name, position: i });\n i = close + 1;\n continue;\n }\n\n if (ch === '\"' || ch === \"'\") {\n const quote = ch;\n let j = i + 1;\n let text = \"\";\n let closed = false;\n while (j < source.length) {\n if (source[j] === \"\\\\\" && j + 1 < source.length) {\n text += source[j + 1];\n j += 2;\n continue;\n }\n if (source[j] === quote) {\n closed = true;\n j += 1;\n break;\n }\n text += source[j];\n j += 1;\n }\n if (!closed) {\n throw new FormulaError(\n `This text is missing its closing ${quote === '\"' ? \"double\" : \"single\"} quote.`,\n i,\n );\n }\n tokens.push({ type: \"string\", text, position: i });\n i = j;\n continue;\n }\n\n if (isDigit(ch) || (ch === \".\" && isDigit(source[i + 1] ?? \"\"))) {\n let j = i;\n let seenDot = false;\n while (j < source.length) {\n // The `j < source.length` above guarantees a character; `?? \"\"` is\n // never taken and reads as \"not a digit\", which ends the number.\n if (isDigit(source[j] ?? \"\")) {\n j += 1;\n continue;\n }\n if (source[j] === \".\" && !seenDot) {\n seenDot = true;\n j += 1;\n continue;\n }\n break;\n }\n tokens.push({ type: \"number\", text: source.slice(i, j), position: i });\n i = j;\n continue;\n }\n\n if (isIdentifierStart(ch)) {\n let j = i;\n // Guarded by `j < source.length`; `?? \"\"` is never taken and reads as\n // \"not an identifier character\", which ends the identifier.\n while (j < source.length && isIdentifierPart(source[j] ?? \"\")) j += 1;\n tokens.push({ type: \"identifier\", text: source.slice(i, j), position: i });\n i = j;\n continue;\n }\n\n if (ch === \"(\") {\n tokens.push({ type: \"paren-open\", text: ch, position: i });\n i += 1;\n continue;\n }\n if (ch === \")\") {\n tokens.push({ type: \"paren-close\", text: ch, position: i });\n i += 1;\n continue;\n }\n if (ch === \",\") {\n tokens.push({ type: \"comma\", text: ch, position: i });\n i += 1;\n continue;\n }\n\n const two = source.slice(i, i + 2);\n if (TWO_CHAR_OPERATORS.includes(two)) {\n tokens.push({ type: \"operator\", text: two, position: i });\n i += 2;\n continue;\n }\n if (ONE_CHAR_OPERATORS.includes(ch)) {\n tokens.push({ type: \"operator\", text: ch, position: i });\n i += 1;\n continue;\n }\n\n throw new FormulaError(`\\`${ch}\\` does not mean anything in a formula.`, i);\n }\n\n tokens.push({ type: \"end\", text: \"\", position: source.length });\n return tokens;\n}\n\n// ─── parser ──────────────────────────────────────────────────────────────────\n//\n// Precedence, loosest first:\n// comparison = != <> < <= > >=\n// concat &\n// additive + -\n// multiplicative * / %\n// unary -\n// primary literal · {reference} · FUNC(…) · ( … )\n\nconst COMPARISON_OPERATORS = [\"=\", \"!=\", \"<>\", \"<\", \"<=\", \">\", \">=\"];\n\nclass Parser {\n private readonly tokens: Token[];\n private index = 0;\n readonly references: string[] = [];\n\n /**\n * `tokenize` always terminates the list with an `end` token and the grammar\n * never advances past it, so neither accessor below can actually run off the\n * end. This sentinel is what they would return if one ever did, which keeps\n * both accessors total without an assertion.\n */\n private readonly endToken: Token;\n\n constructor(tokens: Token[]) {\n this.tokens = tokens;\n this.endToken = tokens[tokens.length - 1] ?? {\n type: \"end\",\n text: \"\",\n position: 0,\n };\n }\n\n private peek(): Token {\n return this.tokens[this.index] ?? this.endToken;\n }\n\n private next(): Token {\n return this.tokens[this.index++] ?? this.endToken;\n }\n\n private matchOperator(candidates: readonly string[]): Token | null {\n const token = this.peek();\n if (token.type === \"operator\" && candidates.includes(token.text)) {\n this.index += 1;\n return token;\n }\n return null;\n }\n\n parseProgram(): FormulaAst {\n const expression = this.parseComparison();\n const trailing = this.peek();\n if (trailing.type !== \"end\") {\n throw new FormulaError(\n `Nothing should follow the formula here — remove \\`${trailing.text}\\`.`,\n trailing.position,\n );\n }\n return expression;\n }\n\n private parseComparison(): FormulaAst {\n let left = this.parseConcat();\n let operator = this.matchOperator(COMPARISON_OPERATORS);\n while (operator) {\n const right = this.parseConcat();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator(COMPARISON_OPERATORS);\n }\n return left;\n }\n\n private parseConcat(): FormulaAst {\n let left = this.parseAdditive();\n let operator = this.matchOperator([\"&\"]);\n while (operator) {\n const right = this.parseAdditive();\n left = {\n kind: \"binary\",\n operator: \"&\",\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"&\"]);\n }\n return left;\n }\n\n private parseAdditive(): FormulaAst {\n let left = this.parseMultiplicative();\n let operator = this.matchOperator([\"+\", \"-\"]);\n while (operator) {\n const right = this.parseMultiplicative();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"+\", \"-\"]);\n }\n return left;\n }\n\n private parseMultiplicative(): FormulaAst {\n let left = this.parseUnary();\n let operator = this.matchOperator([\"*\", \"/\", \"%\"]);\n while (operator) {\n const right = this.parseUnary();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"*\", \"/\", \"%\"]);\n }\n return left;\n }\n\n private parseUnary(): FormulaAst {\n const minus = this.matchOperator([\"-\"]);\n if (minus) {\n return {\n kind: \"unary\",\n operator: \"-\",\n operand: this.parseUnary(),\n position: minus.position,\n };\n }\n // A leading `+` is harmless and people type it; accept and ignore it.\n const plus = this.matchOperator([\"+\"]);\n if (plus) return this.parseUnary();\n return this.parsePrimary();\n }\n\n private parsePrimary(): FormulaAst {\n const token = this.next();\n\n if (token.type === \"number\") {\n return { kind: \"number\", value: Number(token.text), position: token.position };\n }\n\n if (token.type === \"string\") {\n return { kind: \"string\", value: token.text, position: token.position };\n }\n\n if (token.type === \"reference\") {\n if (!this.references.includes(token.text)) this.references.push(token.text);\n return { kind: \"reference\", name: token.text, position: token.position };\n }\n\n if (token.type === \"paren-open\") {\n const inner = this.parseComparison();\n const close = this.next();\n if (close.type !== \"paren-close\") {\n throw new FormulaError(\n \"This opening bracket `(` never closes.\",\n token.position,\n );\n }\n return inner;\n }\n\n if (token.type === \"identifier\") {\n const upper = token.text.toUpperCase();\n if (upper === \"TRUE\" || upper === \"FALSE\") {\n return {\n kind: \"boolean\",\n value: upper === \"TRUE\",\n position: token.position,\n };\n }\n if (!(FUNCTION_NAMES as string[]).includes(upper)) {\n throw new FormulaError(\n `There is no function called \\`${token.text}\\`. A column goes in braces, like {${token.text}}.`,\n token.position,\n );\n }\n const name = upper as FormulaFunctionName;\n const open = this.next();\n if (open.type !== \"paren-open\") {\n throw new FormulaError(\n `\\`${name}\\` needs brackets after it, like ${FUNCTION_SPECS[name].signature}.`,\n token.position,\n );\n }\n const args: FormulaAst[] = [];\n if (this.peek().type === \"paren-close\") {\n this.index += 1;\n } else {\n for (;;) {\n args.push(this.parseComparison());\n const separator = this.next();\n if (separator.type === \"comma\") continue;\n if (separator.type === \"paren-close\") break;\n throw new FormulaError(\n `\\`${name}\\` is missing a comma or its closing bracket.`,\n separator.position,\n );\n }\n }\n const spec = FUNCTION_SPECS[name];\n if (args.length < spec.min || args.length > spec.max) {\n throw new FormulaError(\n `\\`${name}\\` was given ${args.length} value${args.length === 1 ? \"\" : \"s\"}. Use ${spec.signature}.`,\n token.position,\n );\n }\n return { kind: \"call\", name, args, position: token.position };\n }\n\n if (token.type === \"end\") {\n throw new FormulaError(\"The formula stops before it is finished.\", token.position);\n }\n\n throw new FormulaError(\n `\\`${token.text}\\` cannot start a value here.`,\n token.position,\n );\n }\n}\n\n/**\n * Turn formula text into an AST plus the list of columns it reads.\n *\n * `references` holds every `{…}` name exactly as typed, in first-use order —\n * the caller resolves them (display name OR machine field name) through its own\n * lookup, so this module never needs to know the table.\n */\nexport function parseFormula(source: string): ParseResult {\n try {\n const text = source ?? \"\";\n if (text.trim() === \"\") {\n return { ok: false, error: \"This formula is empty.\", position: 0 };\n }\n const parser = new Parser(tokenize(text));\n const ast = parser.parseProgram();\n return { ok: true, ast, references: parser.references };\n } catch (error) {\n if (error instanceof FormulaError) {\n return { ok: false, error: error.message, position: error.position };\n }\n return {\n ok: false,\n error: \"This formula could not be read.\",\n position: 0,\n };\n }\n}\n\n// ─── value coercion ──────────────────────────────────────────────────────────\n\nconst MISSING = Symbol(\"missing-column\");\n\nfunction isBlank(value: FormulaValue): boolean {\n return value === null || value === \"\";\n}\n\n/** Shortest exact decimal for a number — keeps `0.1 + 0.2` out of the cell. */\nfunction numberToText(n: number): string {\n if (Number.isInteger(n)) return String(n);\n const trimmed = Number(n.toPrecision(15));\n return String(Number.isFinite(trimmed) ? trimmed : n);\n}\n\nfunction toText(value: FormulaValue): string {\n if (value === null) return \"\";\n if (typeof value === \"number\") return numberToText(value);\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n return value;\n}\n\n/** A number, or `null` when the value simply is not one. BLANK reads as 0. */\nfunction looseNumber(value: FormulaValue): number | null {\n if (value === null || value === \"\") return 0;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"boolean\") return value ? 1 : 0;\n const cleaned = value.replace(/[,\\s$€£¥%]/g, \"\");\n if (cleaned === \"\") return 0;\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction requireNumber(value: FormulaValue, what: string): number {\n const n = looseNumber(value);\n if (n === null) {\n throw new FormulaError(`${what} needs a number, but got \"${toText(value)}\".`);\n }\n return n;\n}\n\nfunction truthy(value: FormulaValue): boolean {\n if (value === null || value === \"\") return false;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return value !== 0;\n return true;\n}\n\n/** Both sides numeric? compare as numbers. Otherwise compare as text. */\nfunction compareValues(left: FormulaValue, right: FormulaValue): number {\n const looksNumeric = (v: FormulaValue) =>\n typeof v === \"number\" ||\n (typeof v === \"string\" && v.trim() !== \"\" && looseNumber(v) !== null);\n // BLANK takes the shape of whatever it is compared against: 0 beside a\n // number, \"\" beside text. `{Empty} = 0` and `{Empty} = ''` are both true.\n if (isBlank(left) && isBlank(right)) return 0;\n const blankBesideNumber =\n (isBlank(left) && looksNumeric(right)) ||\n (isBlank(right) && looksNumeric(left));\n if (blankBesideNumber || (looksNumeric(left) && looksNumeric(right))) {\n const a = looseNumber(left) ?? 0;\n const b = looseNumber(right) ?? 0;\n return a < b ? -1 : a > b ? 1 : 0;\n }\n if (typeof left === \"boolean\" || typeof right === \"boolean\") {\n const a = truthy(left);\n const b = truthy(right);\n return a === b ? 0 : a ? 1 : -1;\n }\n const a = toText(left);\n const b = toText(right);\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n// ─── dates ───────────────────────────────────────────────────────────────────\n\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\ntype ParsedDate = { ms: number; dateOnly: boolean };\n\nfunction requireDate(value: FormulaValue, what: string): ParsedDate {\n if (value === null || value === \"\") {\n throw new FormulaError(`${what} needs a date, but that value is empty.`);\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n const ms = Date.parse(trimmed);\n if (!Number.isNaN(ms)) return { ms, dateOnly: DATE_ONLY_RE.test(trimmed) };\n }\n throw new FormulaError(`${what} needs a date, but got \"${toText(value)}\".`);\n}\n\nfunction isoFrom(ms: number, dateOnly: boolean): string {\n const iso = new Date(ms).toISOString();\n return dateOnly ? iso.slice(0, 10) : iso;\n}\n\nconst DATEDIFF_UNITS: Record<string, number> = {\n days: 86_400_000,\n hours: 3_600_000,\n minutes: 60_000,\n};\n\nfunction requireUnit(\n value: FormulaValue,\n allowed: string[],\n fn: string,\n): string {\n const unit = toText(value).trim().toLowerCase();\n if (!allowed.includes(unit)) {\n throw new FormulaError(\n `\\`${fn}\\` needs one of ${allowed.map((u) => `'${u}'`).join(\", \")} — got \"${toText(value)}\".`,\n );\n }\n return unit;\n}\n\n/** Add months/years in UTC, clamping to the end of a shorter month. */\nfunction addMonths(ms: number, count: number): number {\n const d = new Date(ms);\n const day = d.getUTCDate();\n const target = new Date(ms);\n target.setUTCDate(1);\n target.setUTCMonth(target.getUTCMonth() + count);\n const lastDay = new Date(\n Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0),\n ).getUTCDate();\n target.setUTCDate(Math.min(day, lastDay));\n return target.getTime();\n}\n\n// ─── evaluator ───────────────────────────────────────────────────────────────\n\n/** SUM-style: flatten args, drop blanks, demand numbers of the rest. */\nfunction numericArgs(values: FormulaValue[], fn: string): number[] {\n const out: number[] = [];\n for (const value of values) {\n if (isBlank(value)) continue;\n out.push(requireNumber(value, `\\`${fn}\\``));\n }\n return out;\n}\n\nfunction roundHalfAway(n: number, places: number): number {\n const factor = 10 ** places;\n const scaled = n * factor;\n // Nudge past the float representation of an exact half (1.005 * 100 = 100.49999…).\n const corrected = Number(scaled.toPrecision(15));\n const rounded =\n corrected < 0 ? -Math.round(-corrected) : Math.round(corrected);\n return rounded / factor;\n}\n\n/**\n * Positional argument of a call. The parser has already checked every call\n * against its `FUNCTION_SPECS` min/max, so each index read below is present;\n * this accessor only makes that knowledge explicit. An index that somehow was\n * not supplied reads as BLANK — the value this language already uses for \"not\n * given\" — so nothing here can throw on a missing argument.\n */\nfunction argAt(args: readonly FormulaValue[], index: number): FormulaValue {\n return args[index] ?? null;\n}\n\nfunction callFunction(\n name: FormulaFunctionName,\n args: FormulaValue[],\n): FormulaValue {\n switch (name) {\n case \"SUM\":\n return numericArgs(args, \"SUM\").reduce((a, b) => a + b, 0);\n case \"MIN\": {\n const nums = numericArgs(args, \"MIN\");\n return nums.length === 0 ? null : Math.min(...nums);\n }\n case \"MAX\": {\n const nums = numericArgs(args, \"MAX\");\n return nums.length === 0 ? null : Math.max(...nums);\n }\n case \"AVERAGE\": {\n const nums = numericArgs(args, \"AVERAGE\");\n if (nums.length === 0) return null;\n return nums.reduce((a, b) => a + b, 0) / nums.length;\n }\n case \"ROUND\": {\n const n = requireNumber(argAt(args, 0), \"`ROUND`\");\n const places =\n args.length > 1 ? Math.trunc(requireNumber(argAt(args, 1), \"`ROUND`\")) : 0;\n return roundHalfAway(n, places);\n }\n case \"ABS\":\n return Math.abs(requireNumber(argAt(args, 0), \"`ABS`\"));\n case \"LEN\":\n return toText(argAt(args, 0)).length;\n case \"UPPER\":\n return toText(argAt(args, 0)).toUpperCase();\n case \"LOWER\":\n return toText(argAt(args, 0)).toLowerCase();\n case \"TRIM\":\n return toText(argAt(args, 0)).trim();\n case \"CONCATENATE\":\n return args.map(toText).join(\"\");\n case \"LEFT\": {\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`LEFT`\"));\n return toText(argAt(args, 0)).slice(0, Math.max(0, count));\n }\n case \"RIGHT\": {\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`RIGHT`\"));\n if (count <= 0) return \"\";\n return toText(argAt(args, 0)).slice(-count);\n }\n case \"CONTAINS\":\n return toText(argAt(args, 0))\n .toLowerCase()\n .includes(toText(argAt(args, 1)).toLowerCase());\n case \"NOT\":\n return !truthy(argAt(args, 0));\n case \"BLANK\":\n return null;\n case \"ISBLANK\":\n return isBlank(argAt(args, 0));\n case \"TODAY\":\n return new Date().toISOString().slice(0, 10);\n case \"NOW\":\n return new Date().toISOString();\n case \"DATEDIFF\": {\n const from = requireDate(argAt(args, 0), \"`DATEDIFF`\");\n const to = requireDate(argAt(args, 1), \"`DATEDIFF`\");\n const unit = requireUnit(argAt(args, 2), Object.keys(DATEDIFF_UNITS), \"DATEDIFF\");\n // `requireUnit` was handed this map's own key list, so it can only return\n // a key that is present; the guard makes that explicit.\n const perUnit = DATEDIFF_UNITS[unit];\n if (perUnit === undefined) return null;\n return Math.trunc((to.ms - from.ms) / perUnit);\n }\n case \"YEAR\":\n return new Date(requireDate(argAt(args, 0), \"`YEAR`\").ms).getUTCFullYear();\n case \"MONTH\":\n return new Date(requireDate(argAt(args, 0), \"`MONTH`\").ms).getUTCMonth() + 1;\n case \"DAY\":\n return new Date(requireDate(argAt(args, 0), \"`DAY`\").ms).getUTCDate();\n case \"DATEADD\": {\n const base = requireDate(argAt(args, 0), \"`DATEADD`\");\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`DATEADD`\"));\n const unit = requireUnit(argAt(args, 2), [\"days\", \"months\", \"years\"], \"DATEADD\");\n const ms =\n unit === \"days\"\n ? base.ms + count * 86_400_000\n : addMonths(base.ms, unit === \"months\" ? count : count * 12);\n return isoFrom(ms, base.dateOnly);\n }\n // IF / AND / OR short-circuit in `evaluateNode` and never reach here.\n case \"IF\":\n case \"AND\":\n case \"OR\":\n throw new FormulaError(`\\`${name}\\` could not be evaluated.`);\n }\n}\n\nfunction applyBinary(\n operator: BinaryOperator,\n left: FormulaValue,\n right: FormulaValue,\n): FormulaValue {\n switch (operator) {\n case \"&\":\n return toText(left) + toText(right);\n case \"+\":\n return (\n requireNumber(left, \"`+`\") + requireNumber(right, \"`+`\")\n );\n case \"-\":\n return requireNumber(left, \"`-`\") - requireNumber(right, \"`-`\");\n case \"*\":\n return requireNumber(left, \"`*`\") * requireNumber(right, \"`*`\");\n case \"/\": {\n const divisor = requireNumber(right, \"`/`\");\n if (divisor === 0) {\n throw new FormulaError(\"This formula divides by zero.\");\n }\n return requireNumber(left, \"`/`\") / divisor;\n }\n case \"%\": {\n const divisor = requireNumber(right, \"`%`\");\n if (divisor === 0) {\n throw new FormulaError(\"This formula divides by zero.\");\n }\n return requireNumber(left, \"`%`\") % divisor;\n }\n case \"=\":\n return compareValues(left, right) === 0;\n case \"!=\":\n case \"<>\":\n return compareValues(left, right) !== 0;\n case \"<\":\n return compareValues(left, right) < 0;\n case \"<=\":\n return compareValues(left, right) <= 0;\n case \">\":\n return compareValues(left, right) > 0;\n case \">=\":\n return compareValues(left, right) >= 0;\n }\n}\n\nfunction normalizeCell(raw: unknown, name: string): FormulaValue {\n if (raw === MISSING) {\n throw new FormulaError(`There is no column called {${name}}.`);\n }\n if (raw === null || raw === undefined) return null;\n if (typeof raw === \"string\" || typeof raw === \"boolean\") return raw;\n if (typeof raw === \"number\") {\n if (!Number.isFinite(raw)) {\n throw new FormulaError(`The value in {${name}} is not a usable number.`);\n }\n return raw;\n }\n if (raw instanceof Date) {\n return Number.isNaN(raw.getTime()) ? null : raw.toISOString();\n }\n // Arrays and objects have no scalar reading; JSON keeps them legible.\n try {\n return JSON.stringify(raw);\n } catch {\n return String(raw);\n }\n}\n\nfunction evaluateNode(node: FormulaAst, resolve: ResolveCell): FormulaValue {\n switch (node.kind) {\n case \"number\":\n case \"string\":\n case \"boolean\":\n return node.value;\n case \"reference\": {\n const raw = resolve(node.name);\n // `undefined` means the column does not exist; a blank cell is null/\"\".\n return normalizeCell(raw === undefined ? MISSING : raw, node.name);\n }\n case \"unary\":\n return -requireNumber(evaluateNode(node.operand, resolve), \"`-`\");\n case \"binary\":\n return applyBinary(\n node.operator,\n evaluateNode(node.left, resolve),\n evaluateNode(node.right, resolve),\n );\n case \"call\": {\n if (node.name === \"IF\") {\n // The parser enforces IF's 2–3 arity, so both of these are present; an\n // absent one would read as BLANK rather than throw.\n const test = node.args[0];\n const whenTrue = node.args[1];\n const condition = truthy(test ? evaluateNode(test, resolve) : null);\n if (condition) return whenTrue ? evaluateNode(whenTrue, resolve) : null;\n const whenFalse = node.args[2];\n return node.args.length > 2 && whenFalse\n ? evaluateNode(whenFalse, resolve)\n : null;\n }\n if (node.name === \"AND\") {\n for (const arg of node.args) {\n if (!truthy(evaluateNode(arg, resolve))) return false;\n }\n return true;\n }\n if (node.name === \"OR\") {\n for (const arg of node.args) {\n if (truthy(evaluateNode(arg, resolve))) return true;\n }\n return false;\n }\n return callFunction(\n node.name,\n node.args.map((arg) => evaluateNode(arg, resolve)),\n );\n }\n }\n}\n\n/**\n * Compute one cell. NEVER throws.\n *\n * `resolve(name)` is given the name exactly as it was typed between the braces\n * and must return the cell's value, or `undefined` when no such column exists —\n * the difference between \"empty\" and \"misspelt\", which is the difference\n * between a working formula and a wrong answer.\n */\nexport function evaluateFormula(\n ast: FormulaAst,\n resolve: ResolveCell,\n): FormulaResult {\n try {\n const value = evaluateNode(ast, resolve);\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n return { ok: false, error: \"This formula produced a number too large to show.\" };\n }\n return { ok: true, value };\n } catch (error) {\n if (error instanceof FormulaError) return { ok: false, error: error.message };\n return { ok: false, error: \"This formula could not be calculated.\" };\n }\n}\n\n// ─── static result type ──────────────────────────────────────────────────────\n\n/**\n * Best-effort: what kind of value will this formula produce? The UI uses it to\n * pick a sensible display format for the computed column. `unknown` is always a\n * legal answer — never guess a format on a shaky reading.\n */\nexport function formulaResultType(\n ast: FormulaAst,\n columnType: ResolveColumnType,\n): FormulaResultType {\n switch (ast.kind) {\n case \"number\":\n return \"number\";\n case \"string\":\n return \"text\";\n case \"boolean\":\n return \"boolean\";\n case \"unary\":\n return \"number\";\n case \"reference\":\n return columnType(ast.name) ?? \"unknown\";\n case \"binary\": {\n if (ast.operator === \"&\") return \"text\";\n if (COMPARISON_OPERATORS.includes(ast.operator)) return \"boolean\";\n return \"number\";\n }\n case \"call\": {\n if (ast.name === \"IF\") {\n // Arity is parser-enforced: IF always carries its \"then\" branch. An\n // absent branch types as \"unknown\", the same neutral this function\n // already returns for anything it cannot pin down.\n const thenArg = ast.args[1];\n const then = thenArg ? formulaResultType(thenArg, columnType) : \"unknown\";\n const otherwiseArg = ast.args[2];\n if (ast.args.length < 3 || !otherwiseArg) return then;\n const otherwise = formulaResultType(otherwiseArg, columnType);\n if (then === otherwise) return then;\n if (then === \"unknown\") return otherwise;\n if (otherwise === \"unknown\") return then;\n return \"unknown\";\n }\n if (ast.name === \"MIN\" || ast.name === \"MAX\") {\n // MIN/MAX over dates is still dates; over anything else, a number.\n const types = ast.args.map((a) => formulaResultType(a, columnType));\n if (types.length > 0 && types.every((t) => t === \"date\")) return \"date\";\n return \"number\";\n }\n return FUNCTION_SPECS[ast.name].result;\n }\n }\n}\n\n// ─── computed columns over rows ─────────────────────────────────────────────\n//\n// THE ONE PLACE a formula column's value is injected into a row. The grid,\n// every copy/export reader (`getCompleteTable` consumers), the client-side\n// sort and the agent scope all call this, so no reader can ever hand out a\n// formula column as the empty cell the database actually stores.\n\n/** The slice of a field row this helper needs to find and evaluate formulas. */\nexport interface ComputedColumnField {\n field_name: string;\n display_name: string;\n data_type: string;\n metadata?: unknown;\n}\n\nexport interface ComputedColumn<F extends ComputedColumnField = ComputedColumnField> {\n field: F;\n parsed: ParseResult;\n}\n\n/**\n * Read the field's declared format WITHOUT importing `lib/field-formats`\n * (which imports nothing from `features/`, and the reverse direction is the\n * one this module keeps clean too). The shape is `metadata.format = { id,\n * options }`, exactly what `readFieldFormatConfig` reads.\n */\nfunction formulaExpressionOf(field: ComputedColumnField): string | null {\n const metadata = field.metadata;\n if (!metadata || typeof metadata !== \"object\") return null;\n const format = (metadata as { format?: unknown }).format;\n if (!format || typeof format !== \"object\") return null;\n if ((format as { id?: unknown }).id !== \"formula\") return null;\n const options = (format as { options?: unknown }).options;\n const formula =\n options && typeof options === \"object\"\n ? (options as { formula?: unknown }).formula\n : undefined;\n const expression =\n formula && typeof formula === \"object\"\n ? (formula as { expression?: unknown }).expression\n : undefined;\n return typeof expression === \"string\" ? expression : \"\";\n}\n\n/** True when the column's declared format is `formula`. */\nexport function isFormulaColumn(field: ComputedColumnField): boolean {\n return formulaExpressionOf(field) !== null;\n}\n\n/**\n * SYSTEM columns: filled from the row's own record, never typed and never\n * stored in `data` — \"Created time\" and \"Last modified time\" (Airtable's\n * names). Both row readers already return `created_at` / `updated_at`.\n */\nexport type SystemColumnKind = \"created_time\" | \"modified_time\";\n\nexport function systemColumnKindOf(field: ComputedColumnField): SystemColumnKind | null {\n const metadata = field.metadata;\n if (!metadata || typeof metadata !== \"object\") return null;\n const format = (metadata as { format?: unknown }).format;\n if (!format || typeof format !== \"object\") return null;\n const id = (format as { id?: unknown }).id;\n return id === \"created_time\" || id === \"modified_time\" ? id : null;\n}\n\n/**\n * Any column whose value the platform supplies on read — a formula or a\n * system column. THE check every write path uses to refuse: the grid cell,\n * paste / clear / fill, the row forms, and the agent `cell_value` target.\n */\nexport function isComputedColumn(field: ComputedColumnField): boolean {\n return (\n isFormulaColumn(field) ||\n systemColumnKindOf(field) !== null ||\n isAutonumberColumn(field)\n );\n}\n\n/**\n * An Autonumber column: the DATABASE assigns the value at insert (trigger\n * `_udt_autonumber`), so unlike a formula it IS stored — nothing is injected\n * on read — but like one it must never be typed, pasted over, or cleared.\n */\nexport function isAutonumberColumn(field: ComputedColumnField): boolean {\n const metadata = field.metadata;\n if (!metadata || typeof metadata !== \"object\") return false;\n const format = (metadata as { format?: unknown }).format;\n return (\n !!format &&\n typeof format === \"object\" &&\n (format as { id?: unknown }).id === \"autonumber\"\n );\n}\n\n/** Every formula column among `fields`, each with its expression parsed once. */\nexport function formulaColumnsOf<F extends ComputedColumnField>(\n fields: readonly F[],\n): ComputedColumn<F>[] {\n return fields.flatMap((field) => {\n const expression = formulaExpressionOf(field);\n if (expression === null) return [];\n return [{ field, parsed: parseFormula(expression) }];\n });\n}\n\nexport interface ComputedRowsResult<R> {\n rows: R[];\n /** `${rowId}::${fieldName}` → the plain-English reason that cell shows #ERROR. */\n errors: Map<string, string>;\n /** Machine names of the formula columns, for callers that must skip them. */\n formulaFieldNames: Set<string>;\n}\n\n/**\n * Return `rows` with every formula column's value computed from the row's\n * other cells. Pure; the input rows are never mutated. When `fields` holds no\n * formula column the SAME array is returned, so callers pay nothing.\n *\n * Formula columns are evaluated in `fields` order, and each one is written into\n * the working copy of the row before the next runs — so a later formula may\n * reference an earlier one. References resolve by machine name first, then by\n * display name (case-insensitive), matching the grid.\n */\nexport function withComputedColumns<\n R extends {\n id: string;\n data: Record<string, unknown>;\n created_at?: string;\n updated_at?: string;\n },\n F extends ComputedColumnField,\n>(\n rows: readonly R[],\n fields: readonly F[],\n /**\n * THE FORMULA SEAM (OLD-TABLES-CUTOVER rev 2 §3.3, reader 2).\n *\n * `compareValues` compares whatever it is handed, and a formula handed two\n * `relation` cells compares RECORD IDS as strings — `=` works by accident and\n * `<` is nonsense. That is not fixed inside the comparison: it is fixed at the\n * one place a cell BECOMES a formula value, so `=`, `<`, `&`, IF and every\n * function this language will ever gain see the words at once, and there is\n * one arm to get right rather than one per operator.\n *\n * It is a FUNCTION and not a words map on purpose: this module deliberately\n * imports nothing from the field-format registry, and the caller already holds both\n * the formats and the resolved words. Omitted, every value is its raw self —\n * exactly today's behaviour.\n */\n displayValueOf?: (fieldName: string, raw: unknown) => unknown,\n): ComputedRowsResult<R> {\n const columns = formulaColumnsOf(fields);\n const systemColumns = fields.flatMap((field) => {\n const kind = systemColumnKindOf(field);\n return kind ? [{ field, kind }] : [];\n });\n // Named for its first tenant; it holds EVERY computed column (formula and\n // system) — the set write paths must skip.\n const formulaFieldNames = new Set([\n ...columns.map((c) => c.field.field_name),\n ...systemColumns.map((c) => c.field.field_name),\n // Stored, not injected — but every write path must skip it all the same.\n ...fields.filter(isAutonumberColumn).map((f) => f.field_name),\n ]);\n const errors = new Map<string, string>();\n if (columns.length === 0 && systemColumns.length === 0) {\n return { rows: rows as R[], errors, formulaFieldNames };\n }\n // A reference resolves against the table's COLUMNS, never against the keys a\n // particular row happens to carry: a row saved with only some of its cells\n // (every new row, every sparse import) has no key for the rest, and that is\n // BLANK — not \"no such column\", which is the #ERROR the engine reserves for\n // a misspelt reference.\n const fieldNameByReference = new Map<string, string>();\n for (const f of fields) {\n fieldNameByReference.set(f.display_name.toLowerCase(), f.field_name);\n }\n for (const f of fields) {\n // Machine names win over a display name that happens to collide.\n fieldNameByReference.set(f.field_name.toLowerCase(), f.field_name);\n }\n const computed = rows.map((row) => {\n const data: Record<string, unknown> = { ...(row.data ?? {}) };\n const shown = (fieldName: string, raw: unknown): unknown =>\n displayValueOf ? displayValueOf(fieldName, raw) : raw;\n const resolve: ResolveCell = (name) => {\n if (name in data) return shown(name, data[name]);\n const fieldName = fieldNameByReference.get(name.toLowerCase());\n if (fieldName === undefined) return undefined; // truly no such column\n return shown(fieldName, data[fieldName] ?? null);\n };\n // System columns first, so a formula may reference them\n // (`DATEDIF({Created}, TODAY(), \"D\")`). A reader that did not return the\n // timestamp leaves the cell blank rather than inventing one.\n for (const { field, kind } of systemColumns) {\n data[field.field_name] =\n (kind === \"created_time\" ? row.created_at : row.updated_at) ?? null;\n }\n for (const { field, parsed } of columns) {\n if (!parsed.ok) {\n data[field.field_name] = null;\n errors.set(`${row.id}::${field.field_name}`, parsed.error);\n continue;\n }\n const result = evaluateFormula(parsed.ast, resolve);\n if (result.ok) data[field.field_name] = result.value;\n else {\n data[field.field_name] = null;\n errors.set(`${row.id}::${field.field_name}`, result.error);\n }\n }\n return { ...row, data };\n });\n return { rows: computed, errors, formulaFieldNames };\n}\n\n// ─── renaming a column a formula refers to ──────────────────────────────────\n\n/**\n * Rewrite every `{from}` reference in `expression` to `{to}` — what keeps a\n * formula working when the column it names is renamed (Airtable and Sheets do\n * this silently; a rename that turned a working column into #ERROR would be\n * the platform punishing the user for tidying up).\n *\n * Walks the source with the tokenizer's own rules for the two things that\n * matter: a `{…}` inside a quoted string is TEXT and is left alone, and `\\`\n * escapes the next character inside a string. The match is on the trimmed\n * name, case-insensitive — the same comparison `withComputedColumns` makes.\n * Returns the SAME string when nothing referenced `from`.\n */\nexport function rewriteFormulaReferences(\n expression: string,\n from: string,\n to: string,\n): string {\n const target = from.trim().toLowerCase();\n if (!target) return expression;\n let out = \"\";\n let i = 0;\n while (i < expression.length) {\n const ch = expression[i];\n if (ch === '\"' || ch === \"'\") {\n let j = i + 1;\n while (j < expression.length) {\n if (expression[j] === \"\\\\\" && j + 1 < expression.length) {\n j += 2;\n continue;\n }\n if (expression[j] === ch) {\n j += 1;\n break;\n }\n j += 1;\n }\n out += expression.slice(i, j);\n i = j;\n continue;\n }\n if (ch === \"{\") {\n const close = expression.indexOf(\"}\", i + 1);\n if (close === -1) {\n out += expression.slice(i);\n break;\n }\n const name = expression.slice(i + 1, close);\n out += name.trim().toLowerCase() === target ? `{${to}}` : expression.slice(i, close + 1);\n i = close + 1;\n continue;\n }\n out += ch;\n i += 1;\n }\n return out;\n}\n"],"mappings":"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","names":["a","b"]}
@@ -349,6 +349,25 @@ interface ComputedColumn<F extends ComputedColumnField = ComputedColumnField> {
349
349
  }
350
350
  /** True when the column's declared format is `formula`. */
351
351
  declare function isFormulaColumn(field: ComputedColumnField): boolean;
352
+ /**
353
+ * SYSTEM columns: filled from the row's own record, never typed and never
354
+ * stored in `data` — "Created time" and "Last modified time" (Airtable's
355
+ * names). Both row readers already return `created_at` / `updated_at`.
356
+ */
357
+ type SystemColumnKind = "created_time" | "modified_time";
358
+ declare function systemColumnKindOf(field: ComputedColumnField): SystemColumnKind | null;
359
+ /**
360
+ * Any column whose value the platform supplies on read — a formula or a
361
+ * system column. THE check every write path uses to refuse: the grid cell,
362
+ * paste / clear / fill, the row forms, and the agent `cell_value` target.
363
+ */
364
+ declare function isComputedColumn(field: ComputedColumnField): boolean;
365
+ /**
366
+ * An Autonumber column: the DATABASE assigns the value at insert (trigger
367
+ * `_udt_autonumber`), so unlike a formula it IS stored — nothing is injected
368
+ * on read — but like one it must never be typed, pasted over, or cleared.
369
+ */
370
+ declare function isAutonumberColumn(field: ComputedColumnField): boolean;
352
371
  /** Every formula column among `fields`, each with its expression parsed once. */
353
372
  declare function formulaColumnsOf<F extends ComputedColumnField>(fields: readonly F[]): ComputedColumn<F>[];
354
373
  interface ComputedRowsResult<R> {
@@ -371,7 +390,25 @@ interface ComputedRowsResult<R> {
371
390
  declare function withComputedColumns<R extends {
372
391
  id: string;
373
392
  data: Record<string, unknown>;
374
- }, F extends ComputedColumnField>(rows: readonly R[], fields: readonly F[]): ComputedRowsResult<R>;
393
+ created_at?: string;
394
+ updated_at?: string;
395
+ }, F extends ComputedColumnField>(rows: readonly R[], fields: readonly F[],
396
+ /**
397
+ * THE FORMULA SEAM (OLD-TABLES-CUTOVER rev 2 §3.3, reader 2).
398
+ *
399
+ * `compareValues` compares whatever it is handed, and a formula handed two
400
+ * `relation` cells compares RECORD IDS as strings — `=` works by accident and
401
+ * `<` is nonsense. That is not fixed inside the comparison: it is fixed at the
402
+ * one place a cell BECOMES a formula value, so `=`, `<`, `&`, IF and every
403
+ * function this language will ever gain see the words at once, and there is
404
+ * one arm to get right rather than one per operator.
405
+ *
406
+ * It is a FUNCTION and not a words map on purpose: this module deliberately
407
+ * imports nothing from the field-format registry, and the caller already holds both
408
+ * the formats and the resolved words. Omitted, every value is its raw self —
409
+ * exactly today's behaviour.
410
+ */
411
+ displayValueOf?: (fieldName: string, raw: unknown) => unknown): ComputedRowsResult<R>;
375
412
  /**
376
413
  * Rewrite every `{from}` reference in `expression` to `{to}` — what keeps a
377
414
  * formula working when the column it names is renamed (Airtable and Sheets do
@@ -386,4 +423,4 @@ declare function withComputedColumns<R extends {
386
423
  */
387
424
  declare function rewriteFormulaReferences(expression: string, from: string, to: string): string;
388
425
 
389
- export { type BinaryOperator, type ComputedColumn, type ComputedColumnField, type ComputedRowsResult, FORMULA_FUNCTIONS, type FormulaAst, type FormulaFunctionName, type FormulaResult, type FormulaResultType, type FormulaValue, type ParseResult, type ResolveCell, type ResolveColumnType, evaluateFormula, formulaColumnsOf, formulaResultType, isFormulaColumn, parseFormula, rewriteFormulaReferences, withComputedColumns };
426
+ export { type BinaryOperator, type ComputedColumn, type ComputedColumnField, type ComputedRowsResult, FORMULA_FUNCTIONS, type FormulaAst, type FormulaFunctionName, type FormulaResult, type FormulaResultType, type FormulaValue, type ParseResult, type ResolveCell, type ResolveColumnType, type SystemColumnKind, evaluateFormula, formulaColumnsOf, formulaResultType, isAutonumberColumn, isComputedColumn, isFormulaColumn, parseFormula, rewriteFormulaReferences, systemColumnKindOf, withComputedColumns };
@@ -349,6 +349,25 @@ interface ComputedColumn<F extends ComputedColumnField = ComputedColumnField> {
349
349
  }
350
350
  /** True when the column's declared format is `formula`. */
351
351
  declare function isFormulaColumn(field: ComputedColumnField): boolean;
352
+ /**
353
+ * SYSTEM columns: filled from the row's own record, never typed and never
354
+ * stored in `data` — "Created time" and "Last modified time" (Airtable's
355
+ * names). Both row readers already return `created_at` / `updated_at`.
356
+ */
357
+ type SystemColumnKind = "created_time" | "modified_time";
358
+ declare function systemColumnKindOf(field: ComputedColumnField): SystemColumnKind | null;
359
+ /**
360
+ * Any column whose value the platform supplies on read — a formula or a
361
+ * system column. THE check every write path uses to refuse: the grid cell,
362
+ * paste / clear / fill, the row forms, and the agent `cell_value` target.
363
+ */
364
+ declare function isComputedColumn(field: ComputedColumnField): boolean;
365
+ /**
366
+ * An Autonumber column: the DATABASE assigns the value at insert (trigger
367
+ * `_udt_autonumber`), so unlike a formula it IS stored — nothing is injected
368
+ * on read — but like one it must never be typed, pasted over, or cleared.
369
+ */
370
+ declare function isAutonumberColumn(field: ComputedColumnField): boolean;
352
371
  /** Every formula column among `fields`, each with its expression parsed once. */
353
372
  declare function formulaColumnsOf<F extends ComputedColumnField>(fields: readonly F[]): ComputedColumn<F>[];
354
373
  interface ComputedRowsResult<R> {
@@ -371,7 +390,25 @@ interface ComputedRowsResult<R> {
371
390
  declare function withComputedColumns<R extends {
372
391
  id: string;
373
392
  data: Record<string, unknown>;
374
- }, F extends ComputedColumnField>(rows: readonly R[], fields: readonly F[]): ComputedRowsResult<R>;
393
+ created_at?: string;
394
+ updated_at?: string;
395
+ }, F extends ComputedColumnField>(rows: readonly R[], fields: readonly F[],
396
+ /**
397
+ * THE FORMULA SEAM (OLD-TABLES-CUTOVER rev 2 §3.3, reader 2).
398
+ *
399
+ * `compareValues` compares whatever it is handed, and a formula handed two
400
+ * `relation` cells compares RECORD IDS as strings — `=` works by accident and
401
+ * `<` is nonsense. That is not fixed inside the comparison: it is fixed at the
402
+ * one place a cell BECOMES a formula value, so `=`, `<`, `&`, IF and every
403
+ * function this language will ever gain see the words at once, and there is
404
+ * one arm to get right rather than one per operator.
405
+ *
406
+ * It is a FUNCTION and not a words map on purpose: this module deliberately
407
+ * imports nothing from the field-format registry, and the caller already holds both
408
+ * the formats and the resolved words. Omitted, every value is its raw self —
409
+ * exactly today's behaviour.
410
+ */
411
+ displayValueOf?: (fieldName: string, raw: unknown) => unknown): ComputedRowsResult<R>;
375
412
  /**
376
413
  * Rewrite every `{from}` reference in `expression` to `{to}` — what keeps a
377
414
  * formula working when the column it names is renamed (Airtable and Sheets do
@@ -386,4 +423,4 @@ declare function withComputedColumns<R extends {
386
423
  */
387
424
  declare function rewriteFormulaReferences(expression: string, from: string, to: string): string;
388
425
 
389
- export { type BinaryOperator, type ComputedColumn, type ComputedColumnField, type ComputedRowsResult, FORMULA_FUNCTIONS, type FormulaAst, type FormulaFunctionName, type FormulaResult, type FormulaResultType, type FormulaValue, type ParseResult, type ResolveCell, type ResolveColumnType, evaluateFormula, formulaColumnsOf, formulaResultType, isFormulaColumn, parseFormula, rewriteFormulaReferences, withComputedColumns };
426
+ export { type BinaryOperator, type ComputedColumn, type ComputedColumnField, type ComputedRowsResult, FORMULA_FUNCTIONS, type FormulaAst, type FormulaFunctionName, type FormulaResult, type FormulaResultType, type FormulaValue, type ParseResult, type ResolveCell, type ResolveColumnType, type SystemColumnKind, evaluateFormula, formulaColumnsOf, formulaResultType, isAutonumberColumn, isComputedColumn, isFormulaColumn, parseFormula, rewriteFormulaReferences, systemColumnKindOf, withComputedColumns };