@ryuzaki13/react-foundation-lib 2.28.0 → 2.30.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 (38) hide show
  1. package/README.md +1 -0
  2. package/dist/analytical-table/index.js +2 -2
  3. package/dist/chunks/{formatters-B_NcT2pe.js → formatters-CZnnbdud.js} +3 -3
  4. package/dist/chunks/{formatters-B_NcT2pe.js.map → formatters-CZnnbdud.js.map} +1 -1
  5. package/dist/chunks/formulas-BXT_XBWI.js +236 -0
  6. package/dist/chunks/formulas-BXT_XBWI.js.map +1 -0
  7. package/dist/chunks/{validate-C8cQm_9m.js → validate-CEQHqevt.js} +2 -2
  8. package/dist/chunks/{validate-C8cQm_9m.js.map → validate-CEQHqevt.js.map} +1 -1
  9. package/dist/error-report/index.js +1 -1
  10. package/dist/excel/index.js +1 -1
  11. package/dist/formatters/index.js +2 -2
  12. package/dist/formatters/pipeline/types.d.ts +2 -2
  13. package/dist/formatters/pipeline/types.d.ts.map +1 -1
  14. package/dist/formulas/execute.d.ts +4 -4
  15. package/dist/formulas/execute.d.ts.map +1 -1
  16. package/dist/formulas/formatTableFormulaExpression.d.ts +3 -2
  17. package/dist/formulas/formatTableFormulaExpression.d.ts.map +1 -1
  18. package/dist/formulas/index.js +2 -198
  19. package/dist/formulas/isTableFormulaDependency.d.ts +4 -0
  20. package/dist/formulas/isTableFormulaDependency.d.ts.map +1 -0
  21. package/dist/formulas/types.d.ts +11 -0
  22. package/dist/formulas/types.d.ts.map +1 -1
  23. package/dist/formulas/validate.d.ts +3 -2
  24. package/dist/formulas/validate.d.ts.map +1 -1
  25. package/dist/value-transformers/execute.d.ts +16 -0
  26. package/dist/value-transformers/execute.d.ts.map +1 -0
  27. package/dist/value-transformers/index.d.ts +4 -0
  28. package/dist/value-transformers/index.d.ts.map +1 -0
  29. package/dist/value-transformers/index.js +69 -0
  30. package/dist/value-transformers/index.js.map +1 -0
  31. package/dist/value-transformers/registry.d.ts +15 -0
  32. package/dist/value-transformers/registry.d.ts.map +1 -0
  33. package/dist/value-transformers/types.d.ts +32 -0
  34. package/dist/value-transformers/types.d.ts.map +1 -0
  35. package/package.json +5 -1
  36. package/src/formulas/README.mdx +24 -14
  37. package/src/value-transformers/README.md +56 -0
  38. package/dist/formulas/index.js.map +0 -1
@@ -6,7 +6,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
6
6
 
7
7
  Модуль `@ryuzaki13/react-foundation-lib/formulas` предоставляет безопасную инфраструктуру для заранее написанных клиентских формул таблицы.
8
8
 
9
- Формула получает значения других полей текущей строки, выполняет синхронный расчёт и возвращает конечное JavaScript-число. Например, она может вычислить сумму, разницу, отношение или процент для calculated-колонки.
9
+ Формула получает значения полей текущей строки и числовые константы, выполняет синхронный расчёт и возвращает конечное JavaScript-число. Например, она может вычислить сумму, разницу, отношение или процент для calculated-колонки.
10
10
 
11
11
  ```text
12
12
  данные строки + упорядоченные зависимости
@@ -181,12 +181,17 @@ const rowData = {
181
181
 
182
182
  ### Зависимости и keys
183
183
 
184
- Dependencies — упорядоченный массив id полей:
184
+ Dependencies — упорядоченный массив аргументов. Строка обозначает id поля, объект — конечную числовую константу:
185
185
 
186
186
  ```ts
187
- const keys = ["planned", "actual"];
187
+ import { type TableFormulaDependency } from "@ryuzaki13/react-foundation-lib/formulas";
188
+
189
+ const keys: readonly TableFormulaDependency[] = ["planned", "actual"];
190
+ const scaledKeys: readonly TableFormulaDependency[] = ["planned", { kind: "constant", value: 100 }];
188
191
  ```
189
192
 
193
+ `keys: ["planned", { kind: "constant", value: 100 }]` передаёт в `$0` значение поля, а в `$1` число `100`. Поля и константы можно смешивать, повторять и переставлять; константа не становится полем строки. Ноль, отрицательные и дробные числа допустимы. Строковый `"100"` обозначает ID поля, а не число. `NaN` и бесконечности отклоняются validator-ом; при исполнении дают `runtime_error`. Редактор отвечает за разбор пользовательского ввода до передачи числового значения в API.
194
+
190
195
  Позиция является частью контракта:
191
196
 
192
197
  - `0` означает `planned`;
@@ -283,16 +288,18 @@ expressionTemplate: "$0 / $1";
283
288
 
284
289
  ### `formatTableFormulaExpression(expressionTemplate, dependencies)`
285
290
 
286
- Чистая функция подставляет ID зависимостей в позиции `$n` за один проход:
291
+ Чистая функция подставляет ID полей и константы в позиции `$n` за один проход:
287
292
 
288
293
  ```ts
289
294
  import { formatTableFormulaExpression } from "@ryuzaki13/react-foundation-lib/formulas";
290
295
 
291
296
  formatTableFormulaExpression("$0 / $1", ["FIELD_X", "FIELD_Y"]); // "FIELD_X / FIELD_Y"
292
297
  formatTableFormulaExpression("$0 / $1", ["FIELD_X"]); // "FIELD_X / $1"
298
+ formatTableFormulaExpression("$0 / $1", ["FIELD_X", { kind: "constant", value: 100 }]); // "FIELD_X / 100"
299
+ formatTableFormulaExpression("$0 - $1", ["FIELD_X", { kind: "constant", value: -2.5 }]); // "FIELD_X - (-2.5)"
293
300
  ```
294
301
 
295
- Повторные вхождения заменяются, `$1` и `$10` различаются. Незаданный или пустой ID оставляет исходный маркер; пробелы и специальные символы в ID сохраняются. Подставленные ID не обрабатываются повторно, входной массив не изменяется. Операторы, скобки и остальной текст шаблона не анализируются.
302
+ Повторные вхождения заменяются, `$1` и `$10` различаются. Незаданный, пустой или некорректный аргумент оставляет исходный маркер; пробелы и специальные символы в ID сохраняются. Отрицательное число отображается в скобках, чтобы сохранить смысл выражения. Подставленные ID не обрабатываются повторно, входной массив не изменяется. Операторы, скобки и остальной текст шаблона не анализируются.
296
303
 
297
304
  Пустая строка возвращается без изменений. Host UI самостоятельно выбирает сообщение для отсутствующего или пустого шаблона и отображает результат как текст.
298
305
 
@@ -369,7 +376,7 @@ ctx.key(-1); // undefined
369
376
  ctx.key(1.5); // undefined
370
377
  ```
371
378
 
372
- Допустим только целый неотрицательный index в пределах массива.
379
+ Допустим только целый неотрицательный index в пределах массива. Для константы `key(index)` возвращает `undefined`: у неё нет ID поля, хотя сама позиция считается прочитанной instrumentation.
373
380
 
374
381
  Пустая строка технически может вернуться из `key`, если она была передана в `keys`, но `value` и `num` считают пустой key отсутствующим. Используйте только непустые dependency ids.
375
382
 
@@ -382,7 +389,7 @@ ctx.key(1.5); // undefined
382
389
  ctx.value(1); // "25"
383
390
  ```
384
391
 
385
- Для неправильного индекса, пустого key или отсутствующего свойства возвращается `undefined`.
392
+ Для константы возвращается непосредственно её числовое значение. Для неправильного индекса, пустого key или отсутствующего свойства возвращается `undefined`.
386
393
 
387
394
  Используйте `value`, когда нужно отличать отсутствующее значение от нуля или применять собственный parser:
388
395
 
@@ -426,7 +433,7 @@ context.num(0); // 12.5
426
433
  context.num(1); // 4
427
434
  ```
428
435
 
429
- `keys` копируется при создании context. Изменение исходного массива после вызова не меняет mapping. `rowData` не копируется: context читает переданный объект.
436
+ `keys`, включая значения объектов-констант, копируется при создании context. Изменение исходного массива или объекта константы после вызова не меняет mapping. Некорректная константа приводит к `TypeError` при ручном создании context. `rowData` не копируется: context читает переданный объект.
430
437
 
431
438
  ### Instrumentation
432
439
 
@@ -766,11 +773,11 @@ type CompileResult = { ok: true; execute: TableFormulaCompiledExecutor } | { ok:
766
773
  Compiler:
767
774
 
768
775
  - находит formula definition один раз;
769
- - копирует массив `keys`;
776
+ - копирует массив `keys` и значения объектов-констант;
770
777
  - создаёт один context object;
771
778
  - возвращает executor `(rowData) => result`.
772
779
 
773
- Изменение исходного массива keys после compile не влияет на executor. Замена активного registry также не меняет уже найденную formula function.
780
+ Изменение исходного массива keys или объекта константы после compile не влияет на executor. Замена активного registry также не меняет уже найденную formula function. При некорректной константе возвращается executor с прежней ошибкой `runtime_error`; форма compile-result не расширяется.
774
781
 
775
782
  ### Что происходит при каждом execute
776
783
 
@@ -840,7 +847,7 @@ interface TableFormulaDependenciesValidationResult {
840
847
  Validator:
841
848
 
842
849
  1. ищет formula в активном registry;
843
- 2. проверяет, что каждый dependency id входит в `availableColumnIds`;
850
+ 2. проверяет, что каждый dependency id входит в `availableColumnIds`, а константа содержит конечное число;
844
851
  3. запускает formula на положительных synthetic values;
845
852
  4. запускает formula на отрицательных synthetic values;
846
853
  5. через instrumentation собирает прочитанные indexes;
@@ -855,6 +862,7 @@ Validator:
855
862
  | ------------------------------- | ----------------------------------------------------------- |
856
863
  | `formula_not_found` | Formula id отсутствует в registry |
857
864
  | `dependency_not_available` | Dependency id отсутствует среди доступных колонок |
865
+ | `invalid_constant` | Константа не содержит конечное числовое значение |
858
866
  | `dependency_index_out_of_range` | Formula прочитала index, для которого dependency не передан |
859
867
 
860
868
  ### Warnings
@@ -873,7 +881,7 @@ validation.usage.usedDependencyIds; // например ["planned", "actual"]
873
881
  validation.usage.requiredDependencyCount; // 2
874
882
  ```
875
883
 
876
- `requiredDependencyCount` равен `максимальный использованный index + 1`.
884
+ `requiredDependencyCount` равен `максимальный использованный index + 1`. Константы учитываются в indexes и требуемом количестве аргументов, но отсутствуют в `usedDependencyIds`. Synthetic passes меняют только значения полей; значения констант остаются заданными в конфигурации.
877
885
 
878
886
  Это полезно редактору:
879
887
 
@@ -884,7 +892,7 @@ validation.usage.requiredDependencyCount; // 2
884
892
 
885
893
  ### `availableColumnIds` нужно передавать явно
886
894
 
887
- По умолчанию список доступных колонок пуст. Если передать dependencies, но не передать `availableColumnIds`, каждый dependency будет считаться недоступным:
895
+ По умолчанию список доступных колонок пуст. Если передать аргументы-поля, но не передать `availableColumnIds`, каждое поле будет считаться недоступным. Константы не проверяются на присутствие в этом списке:
888
896
 
889
897
  ```ts
890
898
  validateTableFormulaDependencies({
@@ -1217,7 +1225,7 @@ Promise не является конечным number и даст `invalid_resul
1217
1225
 
1218
1226
  ### Хранить function в JSON
1219
1227
 
1220
- Сохраняйте только `formulaId` и dependency ids. Function определяется доверенным application bundle.
1228
+ Сохраняйте только `formulaId` и сериализуемые аргументы: dependency ids и объекты числовых констант. Function определяется доверенным application bundle.
1221
1229
 
1222
1230
  ### Путать table formula и row-based formatter
1223
1231
 
@@ -1244,6 +1252,7 @@ Promise не является конечным number и даст `invalid_resul
1244
1252
  | Тип | Назначение |
1245
1253
  | ------------------------------------------ | ------------------------------------------ |
1246
1254
  | `TableFormulaRowData` | Строка `Record<string, unknown>` |
1255
+ | `TableFormulaDependency` | ID поля либо объект конечной числовой константы |
1247
1256
  | `TableFormulaContext` | Методы `key`, `value`, `num` |
1248
1257
  | `TableFormulaFn` | Синхронная function `context → number` |
1249
1258
  | `TableFormulaDefinition` | Metadata и function одной formula |
@@ -1270,6 +1279,7 @@ Promise не является конечным number и даст `invalid_resul
1270
1279
  | `formula_not_found` | error |
1271
1280
  | `dependency_index_out_of_range` | error |
1272
1281
  | `dependency_not_available` | error |
1282
+ | `invalid_constant` | error |
1273
1283
  | `unused_dependencies` | warning |
1274
1284
  | `formula_does_not_use_dependencies` | warning |
1275
1285
  | `formula_runtime_error` | warning |
@@ -0,0 +1,56 @@
1
+ # Преобразования числовых значений
2
+
3
+ Публичный subpath `@ryuzaki13/react-foundation-lib/value-transformers` предоставляет реестр и синхронное исполнение доверенных функций одного аргумента. Прикладные определения принадлежат host-приложению; встроенного каталога операций или единиц измерения нет.
4
+
5
+ ## Контракт функции
6
+
7
+ ```ts
8
+ import { type ValueTransformerDefinition } from "@ryuzaki13/react-foundation-lib/value-transformers";
9
+
10
+ const definition: ValueTransformerDefinition = {
11
+ id: "double",
12
+ name: "Удвоение",
13
+ description: "Удваивает числовое значение; остальные входы оставляет без результата.",
14
+ fn: (value) => (typeof value === "number" ? value * 2 : undefined)
15
+ };
16
+ ```
17
+
18
+ `ValueTransformerFn` принимает `unknown` и возвращает `number | null | undefined`. Функция сама определяет разбор строк, допустимые типы, конечность числа и поведение при пустых или ошибочных данных. Механизм не вызывает `Number`, не подставляет `0` и не меняет результат функции, включая `null`, `undefined`, `NaN` или `Infinity`. Эти правила не наследуются от табличных формул или display-форматтеров.
19
+
20
+ Функция должна работать синхронно. Вход передаётся буквально и не копируется: определение не должно изменять объекты строки, Query cache или внешнее состояние. Асинхронные функции и строковые результаты не входят в TypeScript-контракт.
21
+
22
+ ## Реестр
23
+
24
+ ```ts
25
+ import { configureValueTransformerRegistry, createValueTransformerRegistry } from "@ryuzaki13/react-foundation-lib/value-transformers";
26
+
27
+ const registry = createValueTransformerRegistry([definition]);
28
+ configureValueTransformerRegistry(registry);
29
+ ```
30
+
31
+ Host устанавливает каталог в composition root до чтения или компиляции. `createValueTransformerRegistry` сохраняет порядок определений, удаляет внешние пробелы в `id`, `name` и `description`, отклоняет пустой и повторный ID. Registry object, список, определения и независимые копии `keywords` заморожены; индекс представлен `ReadonlyMap`.
32
+
33
+ `getValueTransformerList()` возвращает текущий readonly-список. `getValueTransformerById(id)` удаляет внешние пробелы в ID и возвращает `undefined` для пустого или неизвестного ID. Создание каталога не устанавливает его автоматически; `configureValueTransformerRegistry` полностью заменяет активный каталог данного экземпляра модуля.
34
+
35
+ ## Исполнение
36
+
37
+ ```ts
38
+ import { compileValueTransformer, executeValueTransformer } from "@ryuzaki13/react-foundation-lib/value-transformers";
39
+
40
+ executeValueTransformer({ transformerId: "double", value: 3 });
41
+ // { ok: true, value: 6 }
42
+
43
+ const compiled = compileValueTransformer({ transformerId: "double" });
44
+ if (compiled.ok) {
45
+ compiled.execute(3); // { ok: true, value: 6 }
46
+ compiled.execute(null); // { ok: true, value: undefined } — решение fn из примера
47
+ }
48
+ ```
49
+
50
+ Разовый `executeValueTransformer` использует активный каталог. Для повторной обработки строк `compileValueTransformer` фиксирует функцию один раз: замена исходного определения или активного registry не меняет уже созданный executor.
51
+
52
+ Compile-result: `{ ok: true, execute } | { ok: false, reason: "transformer_not_found" }`.
53
+
54
+ Execution-result: `{ ok: true, value: number | null | undefined } | { ok: false, reason: "transformer_not_found" | "runtime_error" }`. Неизвестный ID даёт `transformer_not_found`; исключение функции — `runtime_error`. Ошибка не содержит исходное значение и не превращается в успешный результат по умолчанию. Потребитель отвечает за контекст диагностики и отображение ошибки.
55
+
56
+ Модуль не читает поля строки, не строит граф зависимостей и не материализует колонки. Потребитель выбирает исходное поле, передаёт его значение и записывает успешный результат в принадлежащую ему модель до исполнения зависимых формул. Сериализуемая конфигурация хранит стабильный ID преобразования, а функция остаётся доверенным кодом приложения.
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/formulas/registry.ts","../../src/formulas/execute.ts","../../src/formulas/formatTableFormulaExpression.ts","../../src/formulas/validate.ts"],"sourcesContent":["import type { TableFormulaDefinition } from \"./types\";\n\nexport type TableFormulaRegistry = Readonly<{\n\tlist: readonly TableFormulaDefinition[];\n\tbyId: ReadonlyMap<string, TableFormulaDefinition>;\n}>;\n\nexport function createTableFormulaRegistry(definitions: readonly TableFormulaDefinition[]): TableFormulaRegistry {\n\tconst byId = new Map<string, TableFormulaDefinition>();\n\tconst list: TableFormulaDefinition[] = [];\n\n\tfor (const definition of definitions) {\n\t\tconst normalizedId = definition.id.trim();\n\t\tif (!normalizedId) {\n\t\t\tthrow new Error(\"Formula id не может быть пустым\");\n\t\t}\n\n\t\tif (byId.has(normalizedId)) {\n\t\t\tthrow new Error(`Найден дублирующийся formulaId: ${normalizedId}`);\n\t\t}\n\n\t\tconst normalizedDefinition: TableFormulaDefinition = Object.freeze({\n\t\t\t...definition,\n\t\t\tid: normalizedId,\n\t\t\tname: definition.name.trim(),\n\t\t\tdescription: definition.description.trim(),\n\t\t\tkeywords: definition.keywords ? [...definition.keywords] : undefined\n\t\t});\n\n\t\tbyId.set(normalizedId, normalizedDefinition);\n\t\tlist.push(normalizedDefinition);\n\t}\n\n\treturn Object.freeze({\n\t\tlist: Object.freeze(list),\n\t\tbyId\n\t});\n}\n\nlet tableFormulaRegistry = createTableFormulaRegistry([]);\n\n/**\n * Устанавливает реестр формул host-приложения для всех formula runtime API.\n *\n * Конфигурация выполняется composition root до первого чтения, валидации или\n * компиляции формулы. Библиотека намеренно не содержит прикладной каталог.\n */\nexport function configureTableFormulaRegistry(registry: TableFormulaRegistry): void {\n\ttableFormulaRegistry = registry;\n}\n\nexport function getTableFormulaList(): readonly TableFormulaDefinition[] {\n\treturn tableFormulaRegistry.list;\n}\n\nexport function getTableFormulaById(formulaId: string | undefined): TableFormulaDefinition | undefined {\n\tconst normalizedId = formulaId?.trim();\n\tif (!normalizedId) return undefined;\n\treturn tableFormulaRegistry.byId.get(normalizedId);\n}\n","import { getTableFormulaById } from \"./registry\";\n\nimport type { TableFormulaContext, TableFormulaRowData } from \"./types\";\n\ntype TableFormulaContextInstrumentation = {\n\tonReadIndex?: (index: number) => void;\n\tonOutOfRangeIndex?: (index: number) => void;\n};\n\nfunction toFiniteNumberOrZero(value: unknown): number {\n\tconst normalized = Number(value);\n\treturn Number.isFinite(normalized) ? normalized : 0;\n}\n\nexport function createTableFormulaContext(args: {\n\trowData: TableFormulaRowData;\n\tkeys?: readonly string[];\n\tinstrumentation?: TableFormulaContextInstrumentation;\n}): TableFormulaContext {\n\t// TODO: duplicate createRowBasedFormatterContext src\\shared\\lib\\formatters\\rowBased\\context.ts\n\tconst keys = [...(args.keys ?? [])];\n\n\tconst readKey = (index: number): string | undefined => {\n\t\tif (!Number.isInteger(index) || index < 0) {\n\t\t\targs.instrumentation?.onOutOfRangeIndex?.(index);\n\t\t\treturn undefined;\n\t\t}\n\n\t\targs.instrumentation?.onReadIndex?.(index);\n\n\t\tif (index >= keys.length) {\n\t\t\targs.instrumentation?.onOutOfRangeIndex?.(index);\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn keys[index];\n\t};\n\n\tconst readValue = (index: number): unknown => {\n\t\tconst key = readKey(index);\n\t\tif (!key) return undefined;\n\t\treturn args.rowData[key];\n\t};\n\n\treturn {\n\t\tkey: readKey,\n\t\tvalue: readValue,\n\t\tnum: (index) => toFiniteNumberOrZero(readValue(index))\n\t};\n}\n\nexport type TableFormulaExecutionResult =\n\t| {\n\t\t\tok: true;\n\t\t\tvalue: number;\n\t }\n\t| {\n\t\t\tok: false;\n\t\t\treason: \"formula_not_found\" | \"invalid_result\" | \"runtime_error\";\n\t };\n\nexport type TableFormulaCompiledExecutor = (rowData: TableFormulaRowData) => TableFormulaExecutionResult;\n\ntype CompileTableFormulaResult =\n\t| {\n\t\t\tok: true;\n\t\t\texecute: TableFormulaCompiledExecutor;\n\t }\n\t| {\n\t\t\tok: false;\n\t\t\treason: \"formula_not_found\";\n\t };\n\nexport function compileTableFormula(args: { formulaId: string | undefined; keys?: readonly string[] }): CompileTableFormulaResult {\n\tconst formula = getTableFormulaById(args.formulaId);\n\tif (!formula) {\n\t\treturn { ok: false, reason: \"formula_not_found\" };\n\t}\n\n\tconst keys = [...(args.keys ?? [])];\n\tlet rowDataRef: TableFormulaRowData = {};\n\tconst context: TableFormulaContext = {\n\t\tkey: (index) => (Number.isInteger(index) && index >= 0 && index < keys.length ? keys[index] : undefined),\n\t\tvalue: (index) => {\n\t\t\tif (!Number.isInteger(index) || index < 0 || index >= keys.length) return undefined;\n\t\t\tconst key = keys[index];\n\t\t\tif (!key) return undefined;\n\t\t\treturn rowDataRef[key];\n\t\t},\n\t\tnum: (index) => {\n\t\t\tif (!Number.isInteger(index) || index < 0 || index >= keys.length) return 0;\n\t\t\tconst key = keys[index];\n\t\t\tif (!key) return 0;\n\t\t\treturn toFiniteNumberOrZero(rowDataRef[key]);\n\t\t}\n\t};\n\n\treturn {\n\t\tok: true,\n\t\texecute: (rowData) => {\n\t\t\trowDataRef = rowData;\n\n\t\t\ttry {\n\t\t\t\tconst value = formula.fn(context);\n\t\t\t\tif (!Number.isFinite(value)) {\n\t\t\t\t\treturn { ok: false, reason: \"invalid_result\" };\n\t\t\t\t}\n\n\t\t\t\treturn { ok: true, value };\n\t\t\t} catch {\n\t\t\t\treturn { ok: false, reason: \"runtime_error\" };\n\t\t\t}\n\t\t}\n\t};\n}\n\nexport function executeTableFormula(args: {\n\tformulaId: string | undefined;\n\trowData: TableFormulaRowData;\n\tkeys?: readonly string[];\n}): TableFormulaExecutionResult {\n\tconst compiled = compileTableFormula({\n\t\tformulaId: args.formulaId,\n\t\tkeys: args.keys\n\t});\n\tif (!compiled.ok) {\n\t\treturn { ok: false, reason: compiled.reason };\n\t}\n\n\treturn compiled.execute(args.rowData);\n}\n","/**\n * Подставляет ID упорядоченных зависимостей в шаблон превью формулы.\n *\n * Незаданные позиции остаются в виде $n, чтобы редактор мог показывать неполный\n * черновик. Подстановка однопроходная: ID сохраняются буквально, а полученный\n * текст предназначен только для отображения и никогда не исполняется.\n */\nexport function formatTableFormulaExpression(expressionTemplate: string, dependencies: readonly string[]): string {\n\treturn expressionTemplate.replace(/\\$([0-9]+)/g, (placeholder: string, index: string) => {\n\t\tconst dependencyId = dependencies[Number(index)];\n\t\treturn dependencyId === undefined || dependencyId === \"\" ? placeholder : dependencyId;\n\t});\n}\n","import { createTableFormulaContext } from \"./execute\";\nimport { getTableFormulaById } from \"./registry\";\n\ntype ValidationCode =\n\t| \"formula_not_found\"\n\t| \"dependency_index_out_of_range\"\n\t| \"dependency_not_available\"\n\t| \"unused_dependencies\"\n\t| \"formula_does_not_use_dependencies\"\n\t| \"formula_runtime_error\";\n\nexport interface TableFormulaValidationMessage {\n\tcode: ValidationCode;\n\tmessage: string;\n}\nexport interface TableFormulaDependenciesValidationResult {\n\tok: boolean;\n\terrors: TableFormulaValidationMessage[];\n\twarnings: TableFormulaValidationMessage[];\n\tusage: {\n\t\tusedDependencyIndexes: number[];\n\t\tusedDependencyIds: string[];\n\t\trequiredDependencyCount: number;\n\t};\n}\n\nfunction toSortedArray(items: Set<number>): number[] {\n\treturn [...items].sort((left, right) => left - right);\n}\n\nfunction createSampleRowData(dependencies: readonly string[], multiplier: number): Record<string, unknown> {\n\treturn dependencies.reduce<Record<string, unknown>>((acc, dependencyId, index) => {\n\t\tacc[dependencyId] = (index + 1) * multiplier;\n\t\treturn acc;\n\t}, {});\n}\n\nexport function validateTableFormulaDependencies(args: {\n\tformulaId: string | undefined;\n\tdependencies?: readonly string[];\n\tavailableColumnIds?: readonly string[];\n}): TableFormulaDependenciesValidationResult {\n\tconst errors: TableFormulaValidationMessage[] = [];\n\tconst warnings: TableFormulaValidationMessage[] = [];\n\n\tconst formula = getTableFormulaById(args.formulaId);\n\tconst dependencies = [...(args.dependencies ?? [])];\n\tconst availableColumnIds = args.availableColumnIds ?? [];\n\n\tif (!formula) {\n\t\terrors.push({\n\t\t\tcode: \"formula_not_found\",\n\t\t\tmessage: \"Формула не найдена в реестре.\"\n\t\t});\n\n\t\treturn {\n\t\t\tok: false,\n\t\t\terrors,\n\t\t\twarnings,\n\t\t\tusage: {\n\t\t\t\tusedDependencyIndexes: [],\n\t\t\t\tusedDependencyIds: [],\n\t\t\t\trequiredDependencyCount: 0\n\t\t\t}\n\t\t};\n\t}\n\n\tconst availableSet = new Set(availableColumnIds);\n\tconst missingDependencies = dependencies.filter((dependencyId) => !availableSet.has(dependencyId));\n\tif (missingDependencies.length > 0) {\n\t\terrors.push({\n\t\t\tcode: \"dependency_not_available\",\n\t\t\tmessage: `Не найдены среди доступных полей: ${missingDependencies.join(\", \")}.`\n\t\t});\n\t}\n\n\tconst usedIndexes = new Set<number>();\n\tconst outOfRangeIndexes = new Set<number>();\n\tlet runtimeErrorCount = 0;\n\n\tconst runValidationPass = (sampleRowData: Record<string, unknown>) => {\n\t\ttry {\n\t\t\tconst context = createTableFormulaContext({\n\t\t\t\trowData: sampleRowData,\n\t\t\t\tkeys: dependencies,\n\t\t\t\tinstrumentation: {\n\t\t\t\t\tonReadIndex: (index) => {\n\t\t\t\t\t\tusedIndexes.add(index);\n\t\t\t\t\t},\n\t\t\t\t\tonOutOfRangeIndex: (index) => {\n\t\t\t\t\t\toutOfRangeIndexes.add(index);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tformula.fn(context);\n\t\t} catch {\n\t\t\truntimeErrorCount += 1;\n\t\t}\n\t};\n\n\t// Несколько прогонов повышают шанс увидеть обращения к индексам в разных ветках формулы.\n\trunValidationPass(createSampleRowData(dependencies, 100));\n\trunValidationPass(createSampleRowData(dependencies, -100));\n\n\tconst sortedUsedIndexes = toSortedArray(usedIndexes);\n\tconst requiredDependencyCount = sortedUsedIndexes.length > 0 ? sortedUsedIndexes[sortedUsedIndexes.length - 1] + 1 : 0;\n\n\tif (outOfRangeIndexes.size > 0) {\n\t\terrors.push({\n\t\t\tcode: \"dependency_index_out_of_range\",\n\t\t\tmessage: `Формула обращается к несуществующим индексам. Минимально требуется зависимостей: ${requiredDependencyCount}, выбрано: ${dependencies.length}.`\n\t\t});\n\t}\n\n\tif (sortedUsedIndexes.length === 0) {\n\t\twarnings.push({\n\t\t\tcode: \"formula_does_not_use_dependencies\",\n\t\t\tmessage: \"Формула не использует dependencies.\"\n\t\t});\n\t}\n\n\tconst unusedDependencies = dependencies.filter((_, index) => !usedIndexes.has(index));\n\tif (unusedDependencies.length > 0) {\n\t\twarnings.push({\n\t\t\tcode: \"unused_dependencies\",\n\t\t\tmessage: `Выбраны, но не используются формулой: ${unusedDependencies.join(\", \")}.`\n\t\t});\n\t}\n\n\tif (runtimeErrorCount > 0) {\n\t\twarnings.push({\n\t\t\tcode: \"formula_runtime_error\",\n\t\t\tmessage: \"На тестовых данных формула завершилась с ошибкой.\"\n\t\t});\n\t}\n\n\tconst usedDependencyIds = sortedUsedIndexes.map((index) => dependencies[index]).filter(Boolean);\n\n\treturn {\n\t\tok: errors.length === 0,\n\t\terrors,\n\t\twarnings,\n\t\tusage: {\n\t\t\tusedDependencyIndexes: sortedUsedIndexes,\n\t\t\tusedDependencyIds,\n\t\t\trequiredDependencyCount\n\t\t}\n\t};\n}\n"],"mappings":"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"}