ngx-t-forms-types 0.0.30 → 0.0.34

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 (68) hide show
  1. package/dist/interfaces/Form/formSubmissionHandleInterface.d.ts +6 -0
  2. package/dist/interfaces/FormBuilder/DefaultEelement.js +76 -0
  3. package/dist/interfaces/FormBuilder/DefaultInputConfigInterface.d.ts +13 -0
  4. package/dist/interfaces/FormBuilder/FormInputKeys.d.ts +6 -0
  5. package/dist/interfaces/FormBuilder/FormInputKeys.js +6 -0
  6. package/dist/interfaces/FormBuilder/inputConfig/ElementEditConfig.js +188 -3
  7. package/dist/interfaces/Import/ImportIdentity.d.ts +32 -0
  8. package/dist/interfaces/Import/ImportIdentity.js +1 -0
  9. package/dist/interfaces/Import/ImportProgress.d.ts +12 -1
  10. package/dist/interfaces/Import/ImportRowState.d.ts +52 -1
  11. package/dist/interfaces/Import/index.d.ts +2 -1
  12. package/dist/interfaces/formInput/APIDataFetchingConfigurationInterface.d.ts +34 -0
  13. package/dist/interfaces/formInput/BasicFormInputInterface.d.ts +1 -0
  14. package/dist/interfaces/formInput/BasicFormInputInterface.js +1 -0
  15. package/dist/interfaces/formInput/IMscoaAccount.d.ts +19 -3
  16. package/dist/interfaces/formInput/ISelectInputInterface.d.ts +12 -0
  17. package/dist/interfaces/formInput/MultipleInterface.d.ts +10 -0
  18. package/dist/interfaces/formInput/WorkflowDocumentPicker.d.ts +2 -0
  19. package/dist/schemas/FormInputSchema.js +41 -2
  20. package/dist/schemas/MatOptionsSchema.js +7 -0
  21. package/dist/schemas/MscoaConfigSchema.js +1 -0
  22. package/dist/schemas/index.d.ts +2 -1
  23. package/dist/schemas/index.js +2 -1
  24. package/dist/skillet/authoring-rules.d.ts +41 -0
  25. package/dist/skillet/authoring-rules.js +61 -0
  26. package/dist/skillet/extra-members.d.ts +143 -0
  27. package/dist/skillet/extra-members.js +79 -0
  28. package/dist/skillet/form.d.ts +170 -0
  29. package/dist/skillet/form.js +139 -0
  30. package/dist/skillet/index.d.ts +43 -0
  31. package/dist/skillet/index.js +46 -0
  32. package/dist/skillet/input-members.d.ts +375 -0
  33. package/dist/skillet/input-members.js +186 -0
  34. package/dist/skillet/internal/coupling.d.ts +63 -0
  35. package/dist/skillet/internal/coupling.js +69 -0
  36. package/dist/skillet/internal/editor-guidance.d.ts +64 -0
  37. package/dist/skillet/internal/editor-guidance.js +291 -0
  38. package/dist/skillet/members/calculated-field.d.ts +155 -0
  39. package/dist/skillet/members/calculated-field.js +105 -0
  40. package/dist/skillet/members/conditional.d.ts +69 -0
  41. package/dist/skillet/members/conditional.js +16 -0
  42. package/dist/skillet/members/document-picker.d.ts +182 -0
  43. package/dist/skillet/members/document-picker.js +111 -0
  44. package/dist/skillet/members/mat-options.d.ts +187 -0
  45. package/dist/skillet/members/mat-options.js +92 -0
  46. package/dist/skillet/members/mscoa.d.ts +182 -0
  47. package/dist/skillet/members/mscoa.js +129 -0
  48. package/dist/skillet/members/pagination.d.ts +59 -0
  49. package/dist/skillet/members/pagination.js +16 -0
  50. package/dist/skillet/members/table.d.ts +178 -0
  51. package/dist/skillet/members/table.js +105 -0
  52. package/dist/skillet/members/validators.d.ts +197 -0
  53. package/dist/skillet/members/validators.js +74 -0
  54. package/dist/skillet/members/value.d.ts +154 -0
  55. package/dist/skillet/members/value.js +97 -0
  56. package/dist/skillet/shared-types.d.ts +57 -0
  57. package/dist/skillet/shared-types.js +69 -0
  58. package/dist/skillet/tests/house-rules.spec.d.ts +1 -0
  59. package/dist/skillet/tests/house-rules.spec.js +162 -0
  60. package/dist/skillet/tests/skillet-coupling.spec.d.ts +1 -0
  61. package/dist/skillet/tests/skillet-coupling.spec.js +191 -0
  62. package/dist/skillet/tests/variant-assembly.spec.d.ts +1 -0
  63. package/dist/skillet/tests/variant-assembly.spec.js +643 -0
  64. package/dist/skillet/variants.d.ts +138 -0
  65. package/dist/skillet/variants.js +536 -0
  66. package/dist/skillet/workflow-context.d.ts +95 -0
  67. package/dist/skillet/workflow-context.js +128 -0
  68. package/package.json +78 -60
@@ -0,0 +1,69 @@
1
+ import { s } from '@hashbrownai/core';
2
+ /**
3
+ * Compile-time coupling primitives that bind the Skillet schemas to the
4
+ * interfaces they describe.
5
+ *
6
+ * The rule these enforce, stated once:
7
+ *
8
+ * `s.Infer<node>` must be ASSIGNABLE TO the interface member's type.
9
+ *
10
+ * Assignability — not equality — is deliberate. A Skillet node is allowed to be
11
+ * NARROWER than the interface (we routinely refuse to let a model emit a `Date`
12
+ * object or a custom-script sentinel even where the interface permits one), but
13
+ * it may never be WIDER. Widening is precisely the drift that produces a value
14
+ * the rest of the codebase cannot hold, so that is the direction we fail on.
15
+ */
16
+ /**
17
+ * Guards a Skillet node against the type it is meant to produce.
18
+ *
19
+ * Curried so the expected type is written explicitly and the node type is
20
+ * inferred:
21
+ *
22
+ * ```ts
23
+ * const colSize = matches<number>()(s.integer('Grid width', { minimum: 1 }));
24
+ * ```
25
+ *
26
+ * If the node's inferred output stops being assignable to `Expected` — because
27
+ * someone retyped the interface member, or loosened the node — the argument
28
+ * collapses to `never` and the call fails to compile.
29
+ */
30
+ export function matches() {
31
+ return (node) => node;
32
+ }
33
+ /**
34
+ * Derives Skillet enum entries from a TypeScript string enum at runtime, so the
35
+ * two can never disagree: add a member to the enum and it appears in the schema
36
+ * on the next evaluation, with `s.Infer` widening to match.
37
+ *
38
+ * The `Record<string, string>` constraint rejects numeric and mixed enums —
39
+ * `Object.values` on those also yields the reverse-mapped numeric keys, which
40
+ * would silently poison the entry list. Skillet enumerations are string-only.
41
+ */
42
+ export function enumValues(enumObject) {
43
+ return Object.values(enumObject);
44
+ }
45
+ /**
46
+ * Asserts a literal tuple covers a string union exactly.
47
+ *
48
+ * Needed for union *types* (`'hard' | 'soft'`), which — unlike enums — have no
49
+ * runtime representation to derive from, so the members must be restated. This
50
+ * makes restating them safe: drop one and the argument becomes `never`.
51
+ *
52
+ * ```ts
53
+ * const WRAP = exhaustive<'hard' | 'soft'>()(['hard', 'soft'] as const);
54
+ * ```
55
+ */
56
+ export function exhaustive() {
57
+ return (entries) => entries;
58
+ }
59
+ /**
60
+ * Wraps a node as an absent-able member.
61
+ *
62
+ * Skillet has no `optional()`: every declared key must appear in the model's
63
+ * response. Optionality is therefore modelled as an explicit null branch — the
64
+ * key is always emitted, and `null` is how the model says "not set". Strip the
65
+ * nulls when converting a generated column into a `FormColumnInputs`.
66
+ */
67
+ export function optional(node) {
68
+ return s.anyOf([node, s.nullish()]);
69
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Expressions this module could not read, exposed so a test can assert the set
3
+ * is empty.
4
+ *
5
+ * The grammar in use is closed — `key === value`, joined only by `||` — but it
6
+ * is a string, so nothing stops a future edit reaching for `&&` or `!==`.
7
+ * Rather than throw at import time and take a consumer's app down over editor
8
+ * copy, unreadable expressions are skipped and recorded here, which turns a
9
+ * silent loss of guidance into a failing test.
10
+ */
11
+ export declare const UNREADABLE_EXPRESSIONS: string[];
12
+ /**
13
+ * Composes the description for a property: the authored sentence, then the
14
+ * editor's hint where it adds something, then when the property applies.
15
+ *
16
+ * @param base authored, model-facing description of the property
17
+ * @param path the property's `deepBind` path — a single key for a top-level
18
+ * member, a longer path for a member of a nested config
19
+ */
20
+ export declare function describe(base: string, ...path: string[]): string;
21
+ /**
22
+ * Composes a description for a member of a record edited by a SECONDARY
23
+ * editor — a preset filter within `workflowPickerConfig.presetFilters`, say.
24
+ *
25
+ * @param base authored, model-facing description of the property
26
+ * @param opensAt the `deepBind` path of the row that opens the editor
27
+ * @param path the property's path within the record being edited
28
+ */
29
+ export declare function describeIn(base: string, opensAt: readonly string[], ...path: string[]): string;
30
+ /**
31
+ * Numeric bounds the editor enforces on a property, ready to spread into a
32
+ * Skillet numeric node so the schema constrains what the settings panel does.
33
+ */
34
+ export declare function rangeFor(...path: string[]): {
35
+ minimum?: number;
36
+ maximum?: number;
37
+ } | undefined;
38
+ /** Property paths the editor config carries guidance for. Used by tests. */
39
+ export declare const GUIDED_PATHS: readonly string[];
40
+ /**
41
+ * The top-level members the settings panel edits on EVERY element.
42
+ *
43
+ * A row named `SpecialElementKeys.Default` is not looked up in an element's
44
+ * `properties` at all: it is rendered for every element, gated only by its own
45
+ * `additionalTest` and `disabled` tests. So the members those rows edit are
46
+ * part of every element's surface in the builder while appearing in no
47
+ * element's property list — `readonly`, `onlySetTempErrorOnTouch` and
48
+ * `tourContent` among them. Read here so variant assembly can offer what the
49
+ * builder offers, from the same source the builder reads.
50
+ *
51
+ * Only ungated rows qualify. A gated row is conditional on a VALUE (`min`
52
+ * needs a numeric `type`; `calculatedFieldRules` needs the flag), which is not
53
+ * "always shown" and is handled elsewhere. Nested bindings (`script.onChange`,
54
+ * `matOptions.fetch.value.source`) are members of a nested object, not of the
55
+ * column, and are left to the modules that own those objects.
56
+ */
57
+ export declare const ALWAYS_SHOWN_MEMBERS: readonly string[];
58
+ /**
59
+ * Secondary-editor paths, as `openingPath -> propertyPath`. Used by tests to
60
+ * assert the nested editors are actually being reached: before these were
61
+ * collected, every hint inside a record-list editor was invisible here, and
62
+ * nothing failed to say so.
63
+ */
64
+ export declare const SECONDARY_GUIDED_PATHS: ReadonlyMap<string, readonly string[]>;
@@ -0,0 +1,291 @@
1
+ import { SpecialElementKeys } from '../../interfaces/FormBuilder/FormInputKeys.js';
2
+ import { getElementEditorConfig } from '../../interfaces/FormBuilder/inputConfig/ElementEditConfig.js';
3
+ /** Joins a `deepBind` path into the key this module indexes rows by. */
4
+ const pathKey = (path) => path.join('.');
5
+ function collectRows() {
6
+ const rows = [];
7
+ for (const section of getElementEditorConfig.editorSections ?? []) {
8
+ for (const element of section.elements ?? []) {
9
+ rows.push(element);
10
+ }
11
+ }
12
+ return rows;
13
+ }
14
+ /**
15
+ * Rows belonging to a `secondaryElementEditorConfig`, indexed by the path of
16
+ * the row that opens them and then by their own path within that editor.
17
+ *
18
+ * ## Why these are indexed separately rather than prefixed into `rowsByPath`
19
+ *
20
+ * A secondary editor's rows bind RELATIVE to the record it edits, so making
21
+ * them globally addressable means prefixing them with something. The obvious
22
+ * prefix — the opening row's `deepBind` — is right in one case and wrong in the
23
+ * other, which is the whole reason for this split:
24
+ *
25
+ * - `workflowPickerConfig.presetFilters` opens a `RecordListManager` over the
26
+ * filters themselves, so its inner `op` row really is
27
+ * `workflowPickerConfig.presetFilters[].op`.
28
+ * - `mscoaConfig` opens a composite editor whose secondary rows edit
29
+ * `dualCashExclusion.rules[]`, NOT `mscoaConfig` directly. Prefixing there
30
+ * would file the dual-cash `pattern` hint under `mscoaConfig.pattern` — a
31
+ * path nothing has, silently attaching accounting-exclusion guidance to
32
+ * whatever member later claimed that name.
33
+ *
34
+ * Nothing on the row says which of the two it is, so rather than guess, the
35
+ * caller states the container it is describing and looks rows up within it.
36
+ * Keeping this index apart from `rowsByPath` also means top-level descriptions
37
+ * are unchanged by its existence.
38
+ */
39
+ function collectSecondaryRows() {
40
+ const index = new Map();
41
+ for (const parent of collectRows()) {
42
+ const sections = parent.secondaryElementEditorConfig;
43
+ if (!sections?.length || !parent.deepBind?.length)
44
+ continue;
45
+ const parentKey = pathKey(parent.deepBind);
46
+ const inner = index.get(parentKey) ?? new Map();
47
+ for (const section of sections) {
48
+ for (const row of section.elements ?? []) {
49
+ if (!row.deepBind?.length)
50
+ continue;
51
+ const key = pathKey(row.deepBind);
52
+ const existing = inner.get(key);
53
+ if (existing)
54
+ existing.push(row);
55
+ else
56
+ inner.set(key, [row]);
57
+ }
58
+ }
59
+ if (inner.size)
60
+ index.set(parentKey, inner);
61
+ }
62
+ return index;
63
+ }
64
+ /**
65
+ * Every editor row, indexed by its `deepBind` path.
66
+ *
67
+ * `name` is not the index: it reads `'default'` on most rows, with the property
68
+ * actually being edited carried in `deepBind`. Nested paths
69
+ * (`matOptions.fetch.options`) are indexed too, so the sub-schemas for the
70
+ * pending members can draw on the same guidance when they are built.
71
+ */
72
+ const rowsByPath = (() => {
73
+ const index = new Map();
74
+ for (const row of collectRows()) {
75
+ if (!row.deepBind?.length)
76
+ continue;
77
+ const key = pathKey(row.deepBind);
78
+ const existing = index.get(key);
79
+ if (existing)
80
+ existing.push(row);
81
+ else
82
+ index.set(key, [row]);
83
+ }
84
+ return index;
85
+ })();
86
+ /**
87
+ * Expressions this module could not read, exposed so a test can assert the set
88
+ * is empty.
89
+ *
90
+ * The grammar in use is closed — `key === value`, joined only by `||` — but it
91
+ * is a string, so nothing stops a future edit reaching for `&&` or `!==`.
92
+ * Rather than throw at import time and take a consumer's app down over editor
93
+ * copy, unreadable expressions are skipped and recorded here, which turns a
94
+ * silent loss of guidance into a failing test.
95
+ */
96
+ export const UNREADABLE_EXPRESSIONS = [];
97
+ /**
98
+ * Renders one comparison as prose.
99
+ *
100
+ * `!==` is tested first because `===` is not a substring of it — splitting on
101
+ * `===` would leave `valueSource !` as the key and read the rule backwards,
102
+ * which is worse than not reading it at all.
103
+ */
104
+ function readComparison(clause) {
105
+ const negated = clause.includes('!==');
106
+ const parts = clause.split(negated ? '!==' : '===');
107
+ if (parts.length !== 2)
108
+ return null;
109
+ const key = parts[0].trim();
110
+ const value = parts[1].trim();
111
+ if (!key || !value)
112
+ return null;
113
+ return { key, negated, value };
114
+ }
115
+ /**
116
+ * Turns one applicability test into a clause such as
117
+ * `type is number or date`, or `null` if it cannot be read.
118
+ */
119
+ function readTest(test) {
120
+ if (test.expression) {
121
+ const expression = test.expression;
122
+ // `&&` remains unreadable. Unlike `!==`, it changes the SHAPE of the rule
123
+ // from "any of these" to "all of these", and every collapse below assumes
124
+ // alternatives — reading one as a disjunction would state the opposite of
125
+ // what the editor enforces.
126
+ if (expression.includes('&&')) {
127
+ UNREADABLE_EXPRESSIONS.push(expression);
128
+ return null;
129
+ }
130
+ const comparisons = expression.split('||').map(readComparison);
131
+ if (comparisons.some((c) => c === null)) {
132
+ UNREADABLE_EXPRESSIONS.push(expression);
133
+ return null;
134
+ }
135
+ const read = comparisons;
136
+ const phrase = (negated) => (negated ? 'is not' : 'is');
137
+ // Every clause in the observed grammar tests the same key against
138
+ // alternatives, which reads far better collapsed than repeated. Collapsing
139
+ // requires a shared operator as well as a shared key: `a === x || a !== y`
140
+ // states two different things about `a` and must stay spelled out.
141
+ const [first] = read;
142
+ const uniform = read.every((c) => c.key === first.key && c.negated === first.negated);
143
+ if (uniform) {
144
+ const values = read.map((c) => c.value);
145
+ const list = values.length === 1
146
+ ? values[0]
147
+ : `${values.slice(0, -1).join(', ')} or ${values[values.length - 1]}`;
148
+ return `${first.key} ${phrase(first.negated)} ${list}`;
149
+ }
150
+ return read
151
+ .map((c) => `${c.key} ${phrase(c.negated)} ${c.value}`)
152
+ .join(', or ');
153
+ }
154
+ if (test.testType === 'exists' && test.deepBind?.length) {
155
+ return `${pathKey(test.deepBind)} is set`;
156
+ }
157
+ return null;
158
+ }
159
+ /**
160
+ * Secondary-editor rows, indexed by opening path and then by their own path.
161
+ * See {@link collectSecondaryRows} for why these are kept out of
162
+ * {@link rowsByPath}.
163
+ */
164
+ const secondaryRowsByParent = collectSecondaryRows();
165
+ /** The applicability sentence for a property, if the editor declares one. */
166
+ function applicabilityIn(rows) {
167
+ if (!rows?.length)
168
+ return undefined;
169
+ const clauses = rows
170
+ .flatMap((row) => row.additionalTest ?? [])
171
+ .map(readTest)
172
+ .filter((clause) => clause !== null);
173
+ const unique = [...new Set(clauses)];
174
+ if (!unique.length)
175
+ return undefined;
176
+ return `Only applies when ${unique.join(', or when ')}.`;
177
+ }
178
+ /**
179
+ * Collapses the whitespace a hint picked up from its source.
180
+ *
181
+ * Several hints are written as indented multi-line template literals, so they
182
+ * arrive carrying newlines and runs of leading spaces. Rendered into a schema
183
+ * description those become blank lines and stray gaps before punctuation, which
184
+ * is exactly the kind of noise that makes a long system prompt harder to read.
185
+ */
186
+ const collapseWhitespace = (text) => text
187
+ .replace(/\s+/g, ' ')
188
+ .replace(/\s+([.,;:])/g, '$1')
189
+ // Pulling a hint flush can bring a stray trailing full stop up against the
190
+ // one ending the previous sentence — at least one hint is written with a
191
+ // lone `.` on its own line. Collapse the pair back to a single stop.
192
+ .replace(/([.!?])[\s]*\1+/g, '$1')
193
+ .trim();
194
+ /** The authored hint for a property, if the editor carries one. */
195
+ function hintIn(rows) {
196
+ const hint = rows
197
+ ?.map((row) => row.hint)
198
+ .find((h) => !!h?.trim());
199
+ return hint ? collapseWhitespace(hint) : undefined;
200
+ }
201
+ /** Ends a fragment with a full stop so composed sentences read cleanly. */
202
+ const terminate = (text) => /[.!?]$/.test(text) ? text : `${text}.`;
203
+ /**
204
+ * Composes the description for a property: the authored sentence, then the
205
+ * editor's hint where it adds something, then when the property applies.
206
+ *
207
+ * @param base authored, model-facing description of the property
208
+ * @param path the property's `deepBind` path — a single key for a top-level
209
+ * member, a longer path for a member of a nested config
210
+ */
211
+ export function describe(base, ...path) {
212
+ return compose(base, rowsByPath.get(pathKey(path)));
213
+ }
214
+ /**
215
+ * Composes a description for a member of a record edited by a SECONDARY
216
+ * editor — a preset filter within `workflowPickerConfig.presetFilters`, say.
217
+ *
218
+ * @param base authored, model-facing description of the property
219
+ * @param opensAt the `deepBind` path of the row that opens the editor
220
+ * @param path the property's path within the record being edited
221
+ */
222
+ export function describeIn(base, opensAt, ...path) {
223
+ return compose(base, secondaryRowsByParent.get(pathKey(opensAt))?.get(pathKey(path)));
224
+ }
225
+ /** Shared body of {@link describe} and {@link describeIn}. */
226
+ function compose(base, rows) {
227
+ const parts = [terminate(base)];
228
+ const hint = hintIn(rows);
229
+ // Skip a hint the authored text already covers, so composition cannot
230
+ // produce the same guidance twice in one description.
231
+ if (hint && !base.toLowerCase().includes(hint.toLowerCase())) {
232
+ parts.push(terminate(hint));
233
+ }
234
+ const applicability = applicabilityIn(rows);
235
+ if (applicability)
236
+ parts.push(applicability);
237
+ return parts.join(' ');
238
+ }
239
+ /**
240
+ * Numeric bounds the editor enforces on a property, ready to spread into a
241
+ * Skillet numeric node so the schema constrains what the settings panel does.
242
+ */
243
+ export function rangeFor(...path) {
244
+ const rows = rowsByPath.get(pathKey(path));
245
+ const row = rows?.find((r) => r.min !== undefined || r.max !== undefined);
246
+ if (!row)
247
+ return undefined;
248
+ const range = {};
249
+ if (typeof row.min === 'number')
250
+ range.minimum = row.min;
251
+ if (typeof row.max === 'number')
252
+ range.maximum = row.max;
253
+ return Object.keys(range).length ? range : undefined;
254
+ }
255
+ /** Property paths the editor config carries guidance for. Used by tests. */
256
+ export const GUIDED_PATHS = [...rowsByPath.keys()];
257
+ /**
258
+ * The top-level members the settings panel edits on EVERY element.
259
+ *
260
+ * A row named `SpecialElementKeys.Default` is not looked up in an element's
261
+ * `properties` at all: it is rendered for every element, gated only by its own
262
+ * `additionalTest` and `disabled` tests. So the members those rows edit are
263
+ * part of every element's surface in the builder while appearing in no
264
+ * element's property list — `readonly`, `onlySetTempErrorOnTouch` and
265
+ * `tourContent` among them. Read here so variant assembly can offer what the
266
+ * builder offers, from the same source the builder reads.
267
+ *
268
+ * Only ungated rows qualify. A gated row is conditional on a VALUE (`min`
269
+ * needs a numeric `type`; `calculatedFieldRules` needs the flag), which is not
270
+ * "always shown" and is handled elsewhere. Nested bindings (`script.onChange`,
271
+ * `matOptions.fetch.value.source`) are members of a nested object, not of the
272
+ * column, and are left to the modules that own those objects.
273
+ */
274
+ export const ALWAYS_SHOWN_MEMBERS = [
275
+ ...new Set(collectRows()
276
+ .filter((row) => row.name === SpecialElementKeys.Default &&
277
+ row.deepBind?.length === 1 &&
278
+ !row.additionalTest?.length &&
279
+ !row.disabled?.length)
280
+ .map((row) => row.deepBind[0])),
281
+ ];
282
+ /**
283
+ * Secondary-editor paths, as `openingPath -> propertyPath`. Used by tests to
284
+ * assert the nested editors are actually being reached: before these were
285
+ * collected, every hint inside a record-list editor was invisible here, and
286
+ * nothing failed to say so.
287
+ */
288
+ export const SECONDARY_GUIDED_PATHS = new Map([...secondaryRowsByParent].map(([parent, inner]) => [
289
+ parent,
290
+ [...inner.keys()],
291
+ ]));
@@ -0,0 +1,155 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import type { CalculatedFieldRules } from '../../interfaces/formInput/calculatedFieldRules.js';
3
+ import { CalculationFunctions } from '../../interfaces/formInput/calculationVariableInterface.js';
4
+ /**
5
+ * The `calculatedFieldRules` member: a value computed from other fields rather
6
+ * than typed by the user.
7
+ *
8
+ * ## Why this is a draft
9
+ *
10
+ * `calculationVariableInterface` requires both `id` and `inputId` on every
11
+ * variable. Those are builder-assigned: `inputId` is the `id` of another input
12
+ * in the form, stamped when that input was created, and a model has no way to
13
+ * know it — the form it is authoring does not exist yet, so the ids do not
14
+ * either. Asked for one it will invent a plausible string, and the formula then
15
+ * references a field that is not there. The failure is quiet: the form saves,
16
+ * renders, and computes nothing.
17
+ *
18
+ * So the model binds by `formControlName`, which it genuinely does author, and
19
+ * the application resolves each one to that input's `id` on expansion. Both
20
+ * names are already on the interface — `formControlName` and
21
+ * `parentInputFormControl` sit alongside `inputId` and `parentInputId` — so the
22
+ * draft is not inventing a vocabulary. It fills in the half of each pair a
23
+ * model can actually know.
24
+ *
25
+ * ## The two ways a variable binds
26
+ *
27
+ * A variable reads EITHER one field's value directly, OR an aggregate over one
28
+ * column of a repeatable list. The second needs a set of members the first has
29
+ * no use for — `function`, `parentInputFormControl`, `applyFunctionToCol` — and
30
+ * Joi accepts them all on the same flat object, so nothing in the stored shape
31
+ * says which belong together. Offered flat, a model fills in
32
+ * `applyFunctionToCol` for a plain field and `function` for something with
33
+ * nothing to aggregate.
34
+ *
35
+ * They are therefore two branches of an `anyOf`, discriminated by an explicit
36
+ * `bindingType`. That key is NOT a member of `calculationVariableInterface`;
37
+ * like `endpointId` in `members/mat-options.ts` it exists only in the draft and
38
+ * is dropped on expansion. It earns the extra expansion step because an
39
+ * undiscriminated union of two objects sharing three of their keys is exactly
40
+ * the shape a model picks wrongly.
41
+ *
42
+ * The grouping is not invented here: `schemas/customValidationSchema.ts`
43
+ * annotates the same `parentInputId` + `function` pair as "Multiple-input
44
+ * (list) bindings", which is the distinction drawn.
45
+ */
46
+ /** `roundingMode` is a string union with no runtime enum to derive from. */
47
+ export declare const ROUNDING_MODE_VALUES: readonly ["FLOOR", "CEIL", "ROUND"];
48
+ interface VariableCommonDraft {
49
+ /** The token this variable is referenced by inside `formula`. */
50
+ variable: string;
51
+ label: string;
52
+ formControlName: string;
53
+ }
54
+ export interface FieldVariableDraft extends VariableCommonDraft {
55
+ bindingType: 'field';
56
+ }
57
+ export interface ListVariableDraft extends VariableCommonDraft {
58
+ bindingType: 'listAggregate';
59
+ parentInputFormControl: string;
60
+ function: CalculationFunctions;
61
+ applyFunctionToCol: string;
62
+ applyFunctionToLabel: string | null;
63
+ filterValuesByCol: string | null;
64
+ filterValuesByThisColLabel: string | null;
65
+ tableCell: boolean | null;
66
+ }
67
+ export type CalculationVariableDraft = FieldVariableDraft | ListVariableDraft;
68
+ export interface CalculatedFieldRulesDraft {
69
+ formula: string;
70
+ variables: CalculationVariableDraft[];
71
+ decimalPlaces: number | null;
72
+ roundingMode: (typeof ROUNDING_MODE_VALUES)[number] | null;
73
+ getFormulaFromAFormInput: boolean | null;
74
+ formControlWithFormula: string | null;
75
+ }
76
+ export declare const calculationVariableDraftSchema: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
77
+ bindingType: "field";
78
+ variable: string;
79
+ label: string;
80
+ formControlName: string;
81
+ } | {
82
+ bindingType: "listAggregate";
83
+ variable: string;
84
+ label: string;
85
+ formControlName: string;
86
+ parentInputFormControl: string;
87
+ function: CalculationFunctions;
88
+ applyFunctionToCol: string;
89
+ applyFunctionToLabel: string | null;
90
+ filterValuesByCol: string | null;
91
+ filterValuesByThisColLabel: string | null;
92
+ tableCell: boolean | null;
93
+ }>;
94
+ /**
95
+ * The editor's applicability text for this row is deliberately NOT composed in.
96
+ *
97
+ * `describe(..., 'calculatedFieldRules')` resolves to "Only applies when
98
+ * isCalculatedField is true", which is correct in the settings panel and
99
+ * incoherent here: `variants.ts` encodes that rule as the shape of the union
100
+ * and drops `isCalculatedField` from the schema entirely, so the sentence would
101
+ * point a model at a member it is never shown — an invitation to invent one.
102
+ *
103
+ * This is the same judgement `internal/editor-guidance.ts` records for `label`
104
+ * and `options`: derived text earns its place by telling a model something it
105
+ * does not already have, and a rule the structure already makes unbreakable is
106
+ * not that.
107
+ */
108
+ export declare const calculatedFieldRulesDraftSchema: s.ObjectType<{
109
+ formula: s.StringType;
110
+ variables: s.ArrayType<import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
111
+ bindingType: "field";
112
+ variable: string;
113
+ label: string;
114
+ formControlName: string;
115
+ } | {
116
+ bindingType: "listAggregate";
117
+ variable: string;
118
+ label: string;
119
+ formControlName: string;
120
+ parentInputFormControl: string;
121
+ function: CalculationFunctions;
122
+ applyFunctionToCol: string;
123
+ applyFunctionToLabel: string | null;
124
+ filterValuesByCol: string | null;
125
+ filterValuesByThisColLabel: string | null;
126
+ tableCell: boolean | null;
127
+ }>>;
128
+ decimalPlaces: import("@hashbrownai/core/src/schema/base").SchemaForUnion<number | null>;
129
+ roundingMode: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"FLOOR" | "CEIL" | "ROUND" | null>;
130
+ getFormulaFromAFormInput: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
131
+ formControlWithFormula: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
132
+ }>;
133
+ /**
134
+ * The draft is checked against its declared shape, not against
135
+ * `CalculatedFieldRules`: it deliberately differs, carrying `bindingType` and
136
+ * omitting the two identifiers the application supplies. What the compiler can
137
+ * still hold is that the node produces the draft it claims to.
138
+ */
139
+ export type _NoCalculatedFieldDraftDrift = [
140
+ s.Infer<typeof calculatedFieldRulesDraftSchema>
141
+ ] extends [CalculatedFieldRulesDraft] ? never : ['calculatedFieldRulesDraftSchema drifted from CalculatedFieldRulesDraft'];
142
+ /**
143
+ * The parts of the draft stored verbatim stay pinned to the real interface, so
144
+ * retyping `roundingMode` upstream surfaces here rather than in a form that
145
+ * silently rounds the wrong way.
146
+ */
147
+ export type _RoundingModeMatchesInterface = [
148
+ (typeof ROUNDING_MODE_VALUES)[number]
149
+ ] extends [NonNullable<CalculatedFieldRules['roundingMode']>] ? never : ['ROUNDING_MODE_VALUES drifted from CalculatedFieldRules'];
150
+ /**
151
+ * Members of a generated `calculatedFieldRules` the application must supply
152
+ * before Joi will accept it.
153
+ */
154
+ export declare const CALCULATED_FIELD_DRAFT_COMPLETION: readonly ["calculatedFieldRules.variables[].id", "calculatedFieldRules.variables[].inputId (resolved from formControlName)", "calculatedFieldRules.variables[].parentInputId (resolved from parentInputFormControl)", "calculatedFieldRules.variables[].bindingType (draft-only; dropped on expansion)"];
155
+ export {};