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.
- package/dist/interfaces/Form/formSubmissionHandleInterface.d.ts +6 -0
- package/dist/interfaces/FormBuilder/DefaultEelement.js +76 -0
- package/dist/interfaces/FormBuilder/DefaultInputConfigInterface.d.ts +13 -0
- package/dist/interfaces/FormBuilder/FormInputKeys.d.ts +6 -0
- package/dist/interfaces/FormBuilder/FormInputKeys.js +6 -0
- package/dist/interfaces/FormBuilder/inputConfig/ElementEditConfig.js +188 -3
- package/dist/interfaces/Import/ImportIdentity.d.ts +32 -0
- package/dist/interfaces/Import/ImportIdentity.js +1 -0
- package/dist/interfaces/Import/ImportProgress.d.ts +12 -1
- package/dist/interfaces/Import/ImportRowState.d.ts +52 -1
- package/dist/interfaces/Import/index.d.ts +2 -1
- package/dist/interfaces/formInput/APIDataFetchingConfigurationInterface.d.ts +34 -0
- package/dist/interfaces/formInput/BasicFormInputInterface.d.ts +1 -0
- package/dist/interfaces/formInput/BasicFormInputInterface.js +1 -0
- package/dist/interfaces/formInput/IMscoaAccount.d.ts +19 -3
- package/dist/interfaces/formInput/ISelectInputInterface.d.ts +12 -0
- package/dist/interfaces/formInput/MultipleInterface.d.ts +10 -0
- package/dist/interfaces/formInput/WorkflowDocumentPicker.d.ts +2 -0
- package/dist/schemas/FormInputSchema.js +41 -2
- package/dist/schemas/MatOptionsSchema.js +7 -0
- package/dist/schemas/MscoaConfigSchema.js +1 -0
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.js +2 -1
- package/dist/skillet/authoring-rules.d.ts +41 -0
- package/dist/skillet/authoring-rules.js +61 -0
- package/dist/skillet/extra-members.d.ts +143 -0
- package/dist/skillet/extra-members.js +79 -0
- package/dist/skillet/form.d.ts +170 -0
- package/dist/skillet/form.js +139 -0
- package/dist/skillet/index.d.ts +43 -0
- package/dist/skillet/index.js +46 -0
- package/dist/skillet/input-members.d.ts +375 -0
- package/dist/skillet/input-members.js +186 -0
- package/dist/skillet/internal/coupling.d.ts +63 -0
- package/dist/skillet/internal/coupling.js +69 -0
- package/dist/skillet/internal/editor-guidance.d.ts +64 -0
- package/dist/skillet/internal/editor-guidance.js +291 -0
- package/dist/skillet/members/calculated-field.d.ts +155 -0
- package/dist/skillet/members/calculated-field.js +105 -0
- package/dist/skillet/members/conditional.d.ts +69 -0
- package/dist/skillet/members/conditional.js +16 -0
- package/dist/skillet/members/document-picker.d.ts +182 -0
- package/dist/skillet/members/document-picker.js +111 -0
- package/dist/skillet/members/mat-options.d.ts +187 -0
- package/dist/skillet/members/mat-options.js +92 -0
- package/dist/skillet/members/mscoa.d.ts +182 -0
- package/dist/skillet/members/mscoa.js +129 -0
- package/dist/skillet/members/pagination.d.ts +59 -0
- package/dist/skillet/members/pagination.js +16 -0
- package/dist/skillet/members/table.d.ts +178 -0
- package/dist/skillet/members/table.js +105 -0
- package/dist/skillet/members/validators.d.ts +197 -0
- package/dist/skillet/members/validators.js +74 -0
- package/dist/skillet/members/value.d.ts +154 -0
- package/dist/skillet/members/value.js +97 -0
- package/dist/skillet/shared-types.d.ts +57 -0
- package/dist/skillet/shared-types.js +69 -0
- package/dist/skillet/tests/house-rules.spec.d.ts +1 -0
- package/dist/skillet/tests/house-rules.spec.js +162 -0
- package/dist/skillet/tests/skillet-coupling.spec.d.ts +1 -0
- package/dist/skillet/tests/skillet-coupling.spec.js +191 -0
- package/dist/skillet/tests/variant-assembly.spec.d.ts +1 -0
- package/dist/skillet/tests/variant-assembly.spec.js +643 -0
- package/dist/skillet/variants.d.ts +138 -0
- package/dist/skillet/variants.js +536 -0
- package/dist/skillet/workflow-context.d.ts +95 -0
- package/dist/skillet/workflow-context.js +128 -0
- package/package.json +78 -60
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { AllFormInputPrimaryKeys } from '../../interfaces/FormBuilder/FormInputKeys.js';
|
|
3
|
+
import { optional } from '../internal/coupling.js';
|
|
4
|
+
import { describe } from '../internal/editor-guidance.js';
|
|
5
|
+
const paginationColumnSchema = s.object('One column shown in the selection table.', {
|
|
6
|
+
key: s.string('The property on each record whose value fills this column.'),
|
|
7
|
+
label: s.string('The column heading, in sentence case. Name what the column shows.'),
|
|
8
|
+
});
|
|
9
|
+
export const paginationSelectionConfigDraftSchema = s.object('How the selection table pages through records and which of their properties it shows.', {
|
|
10
|
+
useLocalPagination: optional(s.boolean(describe('Whether paging is done in the browser over an already-fetched list.', AllFormInputPrimaryKeys.PaginationSelectionConfig, 'useLocalPagination'))),
|
|
11
|
+
columns: s.array('The columns shown in the table, in order. Pick the few properties a user needs to tell one record from another.', paginationColumnSchema),
|
|
12
|
+
});
|
|
13
|
+
/** Members of a generated config the application must supply. */
|
|
14
|
+
export const PAGINATION_DRAFT_COMPLETION = [
|
|
15
|
+
'paginationSelectionConfig.columns[].id',
|
|
16
|
+
];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import type { IMatrixInput } from '../../interfaces/formInput/MatrixInputInterface.js';
|
|
3
|
+
import type { TableColumnConfigInterface } from '../../interfaces/formInput/TableConfigurationsInterface.js';
|
|
4
|
+
import { AssertNever } from '../internal/coupling.js';
|
|
5
|
+
import { calculatedFieldRulesDraftSchema } from './calculated-field.js';
|
|
6
|
+
/**
|
|
7
|
+
* The two members that configure a table-shaped control: where a matrix reads
|
|
8
|
+
* its rows from, and how a table's columns are laid out.
|
|
9
|
+
*
|
|
10
|
+
* `matrixTableConfig` is a single string and models directly. `tableConfig`
|
|
11
|
+
* does not, and the reason is worth stating up front: its `columnsConfig` is a
|
|
12
|
+
* record keyed by column name, and Skillet has no record node. That one fact
|
|
13
|
+
* decides the shape of everything below.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The `matrixTableConfig` member: which data set a matrix control renders.
|
|
17
|
+
*
|
|
18
|
+
* `{ dataSource: string }` — the whole type. Nothing here is builder-assigned
|
|
19
|
+
* and nothing is unexpressible, so this is one of the few nested configs that
|
|
20
|
+
* models as itself, with no draft and no narrowing.
|
|
21
|
+
*/
|
|
22
|
+
export declare const matrixTableConfigSchema: s.ObjectType<{
|
|
23
|
+
dataSource: s.StringType;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* The four values `TableColumnConfigInterface['type']` admits.
|
|
27
|
+
*
|
|
28
|
+
* A string union, not an enum, so there is no runtime object to derive from and
|
|
29
|
+
* the members have to be restated. {@link exhaustive} makes restating them
|
|
30
|
+
* safe: drop one, or add a fifth to the union, and the tuple collapses to
|
|
31
|
+
* `never` and this file stops compiling.
|
|
32
|
+
*
|
|
33
|
+
* `'caluculated'` is spelled that way in the source interface. It is NOT
|
|
34
|
+
* corrected here. It is the value stored on every table already in the
|
|
35
|
+
* database, so the misspelling is the contract; "fixing" it would emit a
|
|
36
|
+
* literal nothing reads and quietly break every calculated column generated
|
|
37
|
+
* from this point on.
|
|
38
|
+
*/
|
|
39
|
+
export declare const TABLE_COLUMN_TYPE_VALUES: readonly ["caluculated", "property", "checkBox", "primaryKey"];
|
|
40
|
+
/**
|
|
41
|
+
* One column, as the model authors it.
|
|
42
|
+
*
|
|
43
|
+
* Identical to `TableColumnConfigInterface` but for `key`, which the stored
|
|
44
|
+
* shape does not carry because there it IS the record key. Optional members
|
|
45
|
+
* surface as `| null`: Skillet has no `optional()`, so every declared key is
|
|
46
|
+
* emitted and `null` is how the model says "not set".
|
|
47
|
+
*/
|
|
48
|
+
export interface TableColumnDraft {
|
|
49
|
+
/**
|
|
50
|
+
* The name this column is filed under in `columnsConfig`, and the name
|
|
51
|
+
* `displayedColumnsInOrder` refers to it by.
|
|
52
|
+
*/
|
|
53
|
+
key: string;
|
|
54
|
+
type: TableColumnConfigInterface['type'];
|
|
55
|
+
property: string;
|
|
56
|
+
label: string;
|
|
57
|
+
inEdit: boolean;
|
|
58
|
+
checkUpto: string;
|
|
59
|
+
calculatedFieldRules: s.Infer<typeof calculatedFieldRulesDraftSchema>;
|
|
60
|
+
reductionFunction: 'sum' | null;
|
|
61
|
+
colorScale: string | null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* What the model produces in place of a `TableConfigurationsInterface`.
|
|
65
|
+
*
|
|
66
|
+
* The columns arrive as a LIST, each carrying its own key. The application
|
|
67
|
+
* folds that list into the `columnsConfig` record — `key` becomes the record
|
|
68
|
+
* key and the rest of the entry becomes the value — before the input is
|
|
69
|
+
* validated. Until it does, this is not a `TableConfigurationsInterface` and
|
|
70
|
+
* must not be treated as one.
|
|
71
|
+
*/
|
|
72
|
+
export interface TableConfigDraft {
|
|
73
|
+
displayedColumnsInOrder: string[];
|
|
74
|
+
columns: TableColumnDraft[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The `tableConfig` member: the columns of a table control.
|
|
78
|
+
*
|
|
79
|
+
* ## Why this produces a draft
|
|
80
|
+
*
|
|
81
|
+
* `columnsConfig` is declared `{ [key: string]: TableColumnConfigInterface | any }`
|
|
82
|
+
* — a record keyed by the column's own name, decided per table by whoever is
|
|
83
|
+
* authoring it. Skillet has no record or map node, so there is no way to say
|
|
84
|
+
* "an object whose keys I do not know yet and whose values all look like this".
|
|
85
|
+
* The only expressible alternatives are both bad: a fixed set of column names,
|
|
86
|
+
* which would be wrong for every table but the one it was written for, or an
|
|
87
|
+
* open object, which a model fills with structure nothing reads.
|
|
88
|
+
*
|
|
89
|
+
* So the record is INVERTED into a list. Each entry carries the key it is to be
|
|
90
|
+
* filed under, and the application folds the list back into the record before
|
|
91
|
+
* validation. A list is also the shape the second half of this config already
|
|
92
|
+
* wants: `displayedColumnsInOrder` is an ordered list of those same keys, and
|
|
93
|
+
* asking a model to keep an array and an object's key set in agreement is
|
|
94
|
+
* asking for the two to disagree.
|
|
95
|
+
*
|
|
96
|
+
* ## What is not defined here
|
|
97
|
+
*
|
|
98
|
+
* `calculatedFieldRules` belongs to `./calculated-field` and is composed in.
|
|
99
|
+
* The interface declares it REQUIRED on every column, not just calculated ones,
|
|
100
|
+
* so the node is present on every column too; the description says what to do
|
|
101
|
+
* with it on a column that computes nothing.
|
|
102
|
+
*/
|
|
103
|
+
export declare const tableConfigSchema: s.ObjectType<{
|
|
104
|
+
displayedColumnsInOrder: s.ArrayType<s.StringType>;
|
|
105
|
+
columns: s.ArrayType<s.ObjectType<{
|
|
106
|
+
key: s.StringType;
|
|
107
|
+
type: s.EnumType<["caluculated", "property", "checkBox", "primaryKey"]>;
|
|
108
|
+
property: s.StringType;
|
|
109
|
+
label: s.StringType;
|
|
110
|
+
inEdit: s.BooleanType;
|
|
111
|
+
checkUpto: s.StringType;
|
|
112
|
+
calculatedFieldRules: s.ObjectType<{
|
|
113
|
+
formula: s.StringType;
|
|
114
|
+
variables: s.ArrayType<import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
|
|
115
|
+
bindingType: "field";
|
|
116
|
+
variable: string;
|
|
117
|
+
label: string;
|
|
118
|
+
formControlName: string;
|
|
119
|
+
} | {
|
|
120
|
+
bindingType: "listAggregate";
|
|
121
|
+
variable: string;
|
|
122
|
+
label: string;
|
|
123
|
+
formControlName: string;
|
|
124
|
+
parentInputFormControl: string;
|
|
125
|
+
function: import("../../index.js").CalculationFunctions;
|
|
126
|
+
applyFunctionToCol: string;
|
|
127
|
+
applyFunctionToLabel: string | null;
|
|
128
|
+
filterValuesByCol: string | null;
|
|
129
|
+
filterValuesByThisColLabel: string | null;
|
|
130
|
+
tableCell: boolean | null;
|
|
131
|
+
}>>;
|
|
132
|
+
decimalPlaces: import("@hashbrownai/core/src/schema/base").SchemaForUnion<number | null>;
|
|
133
|
+
roundingMode: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"FLOOR" | "CEIL" | "ROUND" | null>;
|
|
134
|
+
getFormulaFromAFormInput: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
|
|
135
|
+
formControlWithFormula: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
136
|
+
}>;
|
|
137
|
+
reductionFunction: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"sum" | null>;
|
|
138
|
+
colorScale: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
139
|
+
}>>;
|
|
140
|
+
}>;
|
|
141
|
+
/**
|
|
142
|
+
* `matrixTableConfig` models as itself, so it is held to the real member type.
|
|
143
|
+
*/
|
|
144
|
+
export type _NoMatrixTableConfigDrift = AssertNever<[
|
|
145
|
+
s.Infer<typeof matrixTableConfigSchema>
|
|
146
|
+
] extends [
|
|
147
|
+
NonNullable<IMatrixInput['matrixTableConfig']>
|
|
148
|
+
] ? never : 'matrixTableConfig'>;
|
|
149
|
+
/**
|
|
150
|
+
* `tableConfig` is held to its DRAFT, not to `TableConfigurationsInterface`.
|
|
151
|
+
*
|
|
152
|
+
* The two differ in shape on purpose — a list of keyed columns against a record
|
|
153
|
+
* of columns — so asserting against the interface would fail by design and tell
|
|
154
|
+
* us nothing. What is worth checking is that the node keeps producing the draft
|
|
155
|
+
* the application's folding step expects, and that is what this checks. The
|
|
156
|
+
* fold itself is the caller's contract and no compiler here can see it.
|
|
157
|
+
*/
|
|
158
|
+
export type _NoTableConfigDraftDrift = AssertNever<[
|
|
159
|
+
s.Infer<typeof tableConfigSchema>
|
|
160
|
+
] extends [TableConfigDraft] ? never : 'tableConfig'>;
|
|
161
|
+
/**
|
|
162
|
+
* The draft column still covers every member of the stored column.
|
|
163
|
+
*
|
|
164
|
+
* This is the assertion the draft would otherwise lose. Because
|
|
165
|
+
* {@link _NoTableConfigDraftDrift} checks the node against a type declared in
|
|
166
|
+
* this file, both could drift away from `TableColumnConfigInterface` together
|
|
167
|
+
* without anything failing. A field added to the stored column surfaces here
|
|
168
|
+
* instead, by name.
|
|
169
|
+
*/
|
|
170
|
+
export type _TableColumnShapeCovered = AssertNever<Exclude<keyof TableColumnConfigInterface, keyof Omit<TableColumnDraft, 'key'>>>;
|
|
171
|
+
/**
|
|
172
|
+
* What the application must do to a generated `tableConfig`.
|
|
173
|
+
*
|
|
174
|
+
* The first entry is not a missing value but a change of shape: the column LIST
|
|
175
|
+
* must be folded into the `columnsConfig` record before anything will accept
|
|
176
|
+
* it. See {@link TableConfigDraft}.
|
|
177
|
+
*/
|
|
178
|
+
export declare const TABLE_DRAFT_COMPLETION: readonly ["tableConfig.columns[] folded into columnsConfig, keyed by each entry's `key`", "tableConfig.columnsConfig[].calculatedFieldRules (see CALCULATED_FIELD_DRAFT_COMPLETION)"];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { exhaustive, optional } from '../internal/coupling.js';
|
|
3
|
+
// Calculated-field rules are owned by their own module. They appear on a table
|
|
4
|
+
// column, on a plain input and inside an mSCOA config, so defining them here
|
|
5
|
+
// would create a second definition that starts drifting from the first the day
|
|
6
|
+
// it is written. This module composes theirs.
|
|
7
|
+
import { calculatedFieldRulesDraftSchema } from './calculated-field.js';
|
|
8
|
+
/**
|
|
9
|
+
* The two members that configure a table-shaped control: where a matrix reads
|
|
10
|
+
* its rows from, and how a table's columns are laid out.
|
|
11
|
+
*
|
|
12
|
+
* `matrixTableConfig` is a single string and models directly. `tableConfig`
|
|
13
|
+
* does not, and the reason is worth stating up front: its `columnsConfig` is a
|
|
14
|
+
* record keyed by column name, and Skillet has no record node. That one fact
|
|
15
|
+
* decides the shape of everything below.
|
|
16
|
+
*/
|
|
17
|
+
// --- matrixTableConfig ------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* The `matrixTableConfig` member: which data set a matrix control renders.
|
|
20
|
+
*
|
|
21
|
+
* `{ dataSource: string }` — the whole type. Nothing here is builder-assigned
|
|
22
|
+
* and nothing is unexpressible, so this is one of the few nested configs that
|
|
23
|
+
* models as itself, with no draft and no narrowing.
|
|
24
|
+
*/
|
|
25
|
+
export const matrixTableConfigSchema = s.object('Which data set the matrix renders its rows from. Set this on a matrix control and on nothing else.', {
|
|
26
|
+
dataSource: s.string('The name of the data set supplying the matrix rows. It must be a source the application already publishes under that name — this is a lookup key, not a description, so a plausible-sounding name that nothing serves leaves the matrix permanently empty.'),
|
|
27
|
+
});
|
|
28
|
+
// --- tableConfig ------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* The four values `TableColumnConfigInterface['type']` admits.
|
|
31
|
+
*
|
|
32
|
+
* A string union, not an enum, so there is no runtime object to derive from and
|
|
33
|
+
* the members have to be restated. {@link exhaustive} makes restating them
|
|
34
|
+
* safe: drop one, or add a fifth to the union, and the tuple collapses to
|
|
35
|
+
* `never` and this file stops compiling.
|
|
36
|
+
*
|
|
37
|
+
* `'caluculated'` is spelled that way in the source interface. It is NOT
|
|
38
|
+
* corrected here. It is the value stored on every table already in the
|
|
39
|
+
* database, so the misspelling is the contract; "fixing" it would emit a
|
|
40
|
+
* literal nothing reads and quietly break every calculated column generated
|
|
41
|
+
* from this point on.
|
|
42
|
+
*/
|
|
43
|
+
export const TABLE_COLUMN_TYPE_VALUES = exhaustive()(['caluculated', 'property', 'checkBox', 'primaryKey']);
|
|
44
|
+
/**
|
|
45
|
+
* `reductionFunction` is declared as the single literal `'sum'` — a union of
|
|
46
|
+
* one, which will not stay that way forever. Held under {@link exhaustive} for
|
|
47
|
+
* the same reason as the column types.
|
|
48
|
+
*/
|
|
49
|
+
const REDUCTION_FUNCTION_VALUES = exhaustive()(['sum']);
|
|
50
|
+
/**
|
|
51
|
+
* The `tableConfig` member: the columns of a table control.
|
|
52
|
+
*
|
|
53
|
+
* ## Why this produces a draft
|
|
54
|
+
*
|
|
55
|
+
* `columnsConfig` is declared `{ [key: string]: TableColumnConfigInterface | any }`
|
|
56
|
+
* — a record keyed by the column's own name, decided per table by whoever is
|
|
57
|
+
* authoring it. Skillet has no record or map node, so there is no way to say
|
|
58
|
+
* "an object whose keys I do not know yet and whose values all look like this".
|
|
59
|
+
* The only expressible alternatives are both bad: a fixed set of column names,
|
|
60
|
+
* which would be wrong for every table but the one it was written for, or an
|
|
61
|
+
* open object, which a model fills with structure nothing reads.
|
|
62
|
+
*
|
|
63
|
+
* So the record is INVERTED into a list. Each entry carries the key it is to be
|
|
64
|
+
* filed under, and the application folds the list back into the record before
|
|
65
|
+
* validation. A list is also the shape the second half of this config already
|
|
66
|
+
* wants: `displayedColumnsInOrder` is an ordered list of those same keys, and
|
|
67
|
+
* asking a model to keep an array and an object's key set in agreement is
|
|
68
|
+
* asking for the two to disagree.
|
|
69
|
+
*
|
|
70
|
+
* ## What is not defined here
|
|
71
|
+
*
|
|
72
|
+
* `calculatedFieldRules` belongs to `./calculated-field` and is composed in.
|
|
73
|
+
* The interface declares it REQUIRED on every column, not just calculated ones,
|
|
74
|
+
* so the node is present on every column too; the description says what to do
|
|
75
|
+
* with it on a column that computes nothing.
|
|
76
|
+
*/
|
|
77
|
+
export const tableConfigSchema = s.object('The columns of a table control: which columns exist, what each one shows, and the order they are displayed in.', {
|
|
78
|
+
displayedColumnsInOrder: s.array('The column keys to display, left to right. Every entry must be the key of a column defined below, and a column left out of this list exists but is not shown — which is how a value is carried without being displayed, and otherwise a mistake.', s.string('The key of a column defined below.')),
|
|
79
|
+
columns: s.array('Every column this table holds. Define each one once here; the order of this list does not matter, because display order is set by displayedColumnsInOrder.', s.object('One column of the table.', {
|
|
80
|
+
key: s.string('The key this column is stored and referred to by. Derive it from the label, in lowerCamelCase, and use the same spelling in displayedColumnsInOrder.'),
|
|
81
|
+
type: s.enumeration("What the column holds. 'property' reads a stored value; 'caluculated' computes one from the calculated-field rules below; 'checkBox' renders a tick per row; 'primaryKey' is the column that identifies the row. Note the spelling of 'caluculated' — it is stored that way and must be emitted exactly as shown, not corrected.", [...TABLE_COLUMN_TYPE_VALUES]),
|
|
82
|
+
property: s.string('The property on each row this column reads. For a property column it names the field holding the value; for a primaryKey column it names the field that identifies the row.'),
|
|
83
|
+
label: s.string('The column heading, in sentence case. Name what the column contains, not how it is computed.'),
|
|
84
|
+
inEdit: s.boolean('Whether the user may edit this column in place. Leave it false for a calculated column and for the primary key, both of which are derived rather than entered.'),
|
|
85
|
+
checkUpto: s.string(
|
|
86
|
+
// Required by the stored shape, with no editor row and no other use
|
|
87
|
+
// anywhere in this package — so the description commits to the one
|
|
88
|
+
// thing that is safe rather than inventing a meaning for it.
|
|
89
|
+
'Only meaningful on a checkBox column, where it bounds how far the ticks may run. The stored shape requires the key to be present, so use an empty string on every other column type.'),
|
|
90
|
+
calculatedFieldRules: calculatedFieldRulesDraftSchema,
|
|
91
|
+
reductionFunction: optional(s.enumeration("How this column's values are reduced into the footer total. Set it only on a numeric column that genuinely totals; leave it null everywhere else, including on counts and identifiers.", [...REDUCTION_FUNCTION_VALUES])),
|
|
92
|
+
colorScale: optional(s.string('The name of a colour scale used to shade this column by value. Presentation only, and rarely wanted — leave it null unless a heat-map reading of the column was actually asked for.')),
|
|
93
|
+
})),
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* What the application must do to a generated `tableConfig`.
|
|
97
|
+
*
|
|
98
|
+
* The first entry is not a missing value but a change of shape: the column LIST
|
|
99
|
+
* must be folded into the `columnsConfig` record before anything will accept
|
|
100
|
+
* it. See {@link TableConfigDraft}.
|
|
101
|
+
*/
|
|
102
|
+
export const TABLE_DRAFT_COMPLETION = [
|
|
103
|
+
'tableConfig.columns[] folded into columnsConfig, keyed by each entry\'s `key`',
|
|
104
|
+
'tableConfig.columnsConfig[].calculatedFieldRules (see CALCULATED_FIELD_DRAFT_COMPLETION)',
|
|
105
|
+
];
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import type { DraftFormControlCustomValidator, FormControlCustomValidatorsInterface } from '../../interfaces/formInput/FormControlCustomValidatorsInterface.js';
|
|
3
|
+
import { CalculationFunctions } from '../../interfaces/formInput/calculationVariableInterface.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `validators` member: the checks a control must pass.
|
|
6
|
+
*
|
|
7
|
+
* ## An entry is one of two different things
|
|
8
|
+
*
|
|
9
|
+
* `validators` is typed `Array<FormControlCustomValidatorsInterface | string>`,
|
|
10
|
+
* and Joi mirrors that exactly —
|
|
11
|
+
* `Joi.array().items(Joi.alternatives().try(Joi.string(), formControlCustomValidatorSchema))`.
|
|
12
|
+
* An entry is EITHER the name of a validator the application already has, or a
|
|
13
|
+
* whole custom validator authored inline. This is the clearest case in the form
|
|
14
|
+
* model of a member where more than one kind of value is genuinely acceptable,
|
|
15
|
+
* and it is modelled as what it is: an `s.anyOf` at the ARRAY ITEM, so the two
|
|
16
|
+
* possibilities sit side by side in one list rather than being flattened into
|
|
17
|
+
* some third shape that is neither.
|
|
18
|
+
*
|
|
19
|
+
* Flattening was the alternative and it is worse in both directions. Offering
|
|
20
|
+
* only the object forces a model to write out an expression for something the
|
|
21
|
+
* application already implements; offering only the string makes every
|
|
22
|
+
* form-specific rule unexpressible.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the named branch is a factory, and why it may not be offered
|
|
25
|
+
*
|
|
26
|
+
* This package declares no list of built-in validator names — they are
|
|
27
|
+
* registered by the consuming application. So the names are supplied by the
|
|
28
|
+
* caller and enumerated, exactly as `members/mat-options.ts` enumerates
|
|
29
|
+
* endpoint ids, and with none supplied the branch is withdrawn for the same
|
|
30
|
+
* reason the `api` source is: a free string invites a name that looks
|
|
31
|
+
* authoritative and resolves to nothing at runtime.
|
|
32
|
+
*
|
|
33
|
+
* Withdrawing it costs a generated form nothing. The Form Creation Guide
|
|
34
|
+
* records that the application provides five named validators — required,
|
|
35
|
+
* email, min, max and a regex pattern — and that four of them duplicate members
|
|
36
|
+
* the field already has (`required`, `min`, `max`, `pattern`) while the fifth is
|
|
37
|
+
* applied by `type: email`. Its advice is to use the members and reach for a
|
|
38
|
+
* named validator only when asked to by name, which is what the enumeration's
|
|
39
|
+
* description says too.
|
|
40
|
+
*
|
|
41
|
+
* ## Why the custom branch is a draft
|
|
42
|
+
*
|
|
43
|
+
* Two identifiers on the stored shape are assigned by the builder, not chosen:
|
|
44
|
+
*
|
|
45
|
+
* - `FormControlCustomValidatorsInterface.id`, stamped on save. The codebase
|
|
46
|
+
* already names the id-less shape — {@link DraftFormControlCustomValidator} —
|
|
47
|
+
* so this module does not invent that distinction, it reuses it.
|
|
48
|
+
* - `InputObservedForChange.inputId`, the id of another input in the form.
|
|
49
|
+
* Same problem as `calculatedFieldRules`: the form being authored does not
|
|
50
|
+
* exist yet, so neither do its ids. The model binds by `formControlName` and
|
|
51
|
+
* the application resolves it. `parentInputFormControl` is draft-only in the
|
|
52
|
+
* same way, standing in for `parentInputId`.
|
|
53
|
+
*
|
|
54
|
+
* ## Guidance is taken from the interface and the guide, not reinvented
|
|
55
|
+
*
|
|
56
|
+
* `InputObservedForChange` carries an unusually complete doc comment about when
|
|
57
|
+
* `function` is required and when it must be omitted — it is the one rule here
|
|
58
|
+
* a model cannot infer from the types. It is restated in the descriptions
|
|
59
|
+
* below for the same reason the editor hints are harvested in
|
|
60
|
+
* `internal/editor-guidance.ts`: the guidance already exists and a second,
|
|
61
|
+
* looser paraphrase would only drift from it.
|
|
62
|
+
*
|
|
63
|
+
* The rules for the message and for `canOverride` come from the guide's
|
|
64
|
+
* Step 5 and Step 6.2. Two of them are engine rules worth knowing the reason
|
|
65
|
+
* for: an override reason is stored in the transaction's audit trail and
|
|
66
|
+
* GRADED by the application — under 15 characters, or a stock phrase, is
|
|
67
|
+
* reported as weak — so the message has to tell the user what a good reason
|
|
68
|
+
* contains; and the guide's house rule is that a data-quality rule is never
|
|
69
|
+
* overridable, because a phone number is never "overridably" wrong.
|
|
70
|
+
*/
|
|
71
|
+
/** A validator the application registers, offered to the model by name. */
|
|
72
|
+
export interface AvailableValidator {
|
|
73
|
+
/** The name the application registers the validator under. */
|
|
74
|
+
name: string;
|
|
75
|
+
/** What it checks, if the name does not say. */
|
|
76
|
+
description?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface ValidatorsSchemaOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Validators the application already provides, which the model may name.
|
|
81
|
+
* Omit, or pass an empty list, and only inline rules are offered — the safe
|
|
82
|
+
* default, because the alternative is a fabricated name.
|
|
83
|
+
*/
|
|
84
|
+
namedValidators?: readonly AvailableValidator[];
|
|
85
|
+
}
|
|
86
|
+
export interface ObservedInputDraft {
|
|
87
|
+
/** Resolved to `InputObservedForChange.inputId` on expansion. */
|
|
88
|
+
formControlName: string;
|
|
89
|
+
variable: string;
|
|
90
|
+
/** Resolved to `InputObservedForChange.parentInputId` on expansion. */
|
|
91
|
+
parentInputFormControl: string | null;
|
|
92
|
+
function: CalculationFunctions | null;
|
|
93
|
+
}
|
|
94
|
+
export interface CustomValidatorDraft {
|
|
95
|
+
message: string;
|
|
96
|
+
expression: string;
|
|
97
|
+
canOverride: boolean;
|
|
98
|
+
inputsObservedForChanges: ObservedInputDraft[];
|
|
99
|
+
}
|
|
100
|
+
export type ValidatorEntryDraft = string | CustomValidatorDraft;
|
|
101
|
+
/**
|
|
102
|
+
* One dependency of an expression.
|
|
103
|
+
*
|
|
104
|
+
* Exported because `conditionalInputConfig` observes its inputs through the
|
|
105
|
+
* very same `InputObservedForChange`, and a property must mean the same thing
|
|
106
|
+
* everywhere it appears — the rule the member registry in `input-members.ts`
|
|
107
|
+
* states for itself. Redeclaring it there would let the two descriptions drift
|
|
108
|
+
* apart while the stored shape stayed identical.
|
|
109
|
+
*/
|
|
110
|
+
export declare const observedInputSchema: s.ObjectType<{
|
|
111
|
+
formControlName: s.StringType;
|
|
112
|
+
variable: s.StringType;
|
|
113
|
+
parentInputFormControl: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
114
|
+
function: import("@hashbrownai/core/src/schema/base").SchemaForUnion<CalculationFunctions | null>;
|
|
115
|
+
}>;
|
|
116
|
+
/**
|
|
117
|
+
* Builds the `validators` schema for the named validators the caller has.
|
|
118
|
+
*
|
|
119
|
+
* Two whole array nodes rather than one whose item is conditionally a union:
|
|
120
|
+
* the inferred type has to be a union of the two shapes for the drift
|
|
121
|
+
* assertion below to hold each branch to {@link ValidatorEntryDraft}.
|
|
122
|
+
*
|
|
123
|
+
* @param options the validators the model may name. Empty — the default —
|
|
124
|
+
* offers inline rules only.
|
|
125
|
+
*/
|
|
126
|
+
export declare function createValidatorsSchema(options?: ValidatorsSchemaOptions): s.ArrayType<import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | {
|
|
127
|
+
message: string;
|
|
128
|
+
expression: string;
|
|
129
|
+
canOverride: boolean;
|
|
130
|
+
inputsObservedForChanges: {
|
|
131
|
+
formControlName: string;
|
|
132
|
+
variable: string;
|
|
133
|
+
parentInputFormControl: string | null;
|
|
134
|
+
function: CalculationFunctions | null;
|
|
135
|
+
}[];
|
|
136
|
+
}>> | s.ArrayType<s.ObjectType<{
|
|
137
|
+
message: s.StringType;
|
|
138
|
+
expression: s.StringType;
|
|
139
|
+
canOverride: s.BooleanType;
|
|
140
|
+
inputsObservedForChanges: s.ArrayType<s.ObjectType<{
|
|
141
|
+
formControlName: s.StringType;
|
|
142
|
+
variable: s.StringType;
|
|
143
|
+
parentInputFormControl: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
144
|
+
function: import("@hashbrownai/core/src/schema/base").SchemaForUnion<CalculationFunctions | null>;
|
|
145
|
+
}>>;
|
|
146
|
+
}>>;
|
|
147
|
+
/**
|
|
148
|
+
* The default `validators` schema: no named validators, so only rules written
|
|
149
|
+
* inline are offered. Callers whose application registers validators should
|
|
150
|
+
* pass their names through `namedValidators` on the column options instead.
|
|
151
|
+
*/
|
|
152
|
+
export declare const validatorsDraftSchema: s.ArrayType<import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | {
|
|
153
|
+
message: string;
|
|
154
|
+
expression: string;
|
|
155
|
+
canOverride: boolean;
|
|
156
|
+
inputsObservedForChanges: {
|
|
157
|
+
formControlName: string;
|
|
158
|
+
variable: string;
|
|
159
|
+
parentInputFormControl: string | null;
|
|
160
|
+
function: CalculationFunctions | null;
|
|
161
|
+
}[];
|
|
162
|
+
}>> | s.ArrayType<s.ObjectType<{
|
|
163
|
+
message: s.StringType;
|
|
164
|
+
expression: s.StringType;
|
|
165
|
+
canOverride: s.BooleanType;
|
|
166
|
+
inputsObservedForChanges: s.ArrayType<s.ObjectType<{
|
|
167
|
+
formControlName: s.StringType;
|
|
168
|
+
variable: s.StringType;
|
|
169
|
+
parentInputFormControl: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
170
|
+
function: import("@hashbrownai/core/src/schema/base").SchemaForUnion<CalculationFunctions | null>;
|
|
171
|
+
}>>;
|
|
172
|
+
}>>;
|
|
173
|
+
/** The schema the factory builds, whichever names it was given. */
|
|
174
|
+
type ValidatorsSchema = ReturnType<typeof createValidatorsSchema>;
|
|
175
|
+
/** Both shapes the factory can build produce the draft they declare. */
|
|
176
|
+
export type _NoValidatorDraftDrift = [s.Infer<ValidatorsSchema>] extends [
|
|
177
|
+
ValidatorEntryDraft[]
|
|
178
|
+
] ? never : ['createValidatorsSchema drifted from ValidatorEntryDraft[]'];
|
|
179
|
+
/**
|
|
180
|
+
* The members carried through expansion untouched stay pinned to the stored
|
|
181
|
+
* interface. Only `inputsObservedForChanges` is excluded, because that is the
|
|
182
|
+
* part the draft deliberately reshapes; retyping `message`, `expression` or
|
|
183
|
+
* `canOverride` upstream still surfaces here.
|
|
184
|
+
*/
|
|
185
|
+
export type _DraftMatchesStoredShape = [
|
|
186
|
+
Omit<CustomValidatorDraft, 'inputsObservedForChanges'>
|
|
187
|
+
] extends [Omit<DraftFormControlCustomValidator, 'inputsObservedForChanges'>] ? never : ['CustomValidatorDraft drifted from DraftFormControlCustomValidator'];
|
|
188
|
+
/** The string branch really is a member of the stored union. */
|
|
189
|
+
export type _NamedBranchIsStorable = [string] extends [
|
|
190
|
+
FormControlCustomValidatorsInterface | string
|
|
191
|
+
] ? never : ['the named-validator branch is not assignable to a stored validator'];
|
|
192
|
+
/**
|
|
193
|
+
* Members of a generated validator the application must supply before Joi will
|
|
194
|
+
* accept it.
|
|
195
|
+
*/
|
|
196
|
+
export declare const VALIDATOR_DRAFT_COMPLETION: readonly ["validators[].id", "validators[].inputsObservedForChanges[].inputId (resolved from formControlName)", "validators[].inputsObservedForChanges[].parentInputId (resolved from parentInputFormControl)"];
|
|
197
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { CalculationFunctions } from '../../interfaces/formInput/calculationVariableInterface.js';
|
|
3
|
+
import { enumValues, optional } from '../internal/coupling.js';
|
|
4
|
+
import { describe } from '../internal/editor-guidance.js';
|
|
5
|
+
// --- schema -----------------------------------------------------------------
|
|
6
|
+
/**
|
|
7
|
+
* One dependency of an expression.
|
|
8
|
+
*
|
|
9
|
+
* Exported because `conditionalInputConfig` observes its inputs through the
|
|
10
|
+
* very same `InputObservedForChange`, and a property must mean the same thing
|
|
11
|
+
* everywhere it appears — the rule the member registry in `input-members.ts`
|
|
12
|
+
* states for itself. Redeclaring it there would let the two descriptions drift
|
|
13
|
+
* apart while the stored shape stayed identical.
|
|
14
|
+
*/
|
|
15
|
+
export const observedInputSchema = s.object('One input the expression reads.', {
|
|
16
|
+
formControlName: s.string('The formControlName of the field being read. It must be a field that actually exists in the form being authored.'),
|
|
17
|
+
variable: s.string('The name the expression references this value by. Use the same spelling in the expression exactly.', { pattern: '^[a-zA-Z][a-zA-Z0-9_]*$' }),
|
|
18
|
+
parentInputFormControl: optional(s.string('Only for a field inside a repeatable item list: the formControlName of the list containing it. Leave unset for an ordinary field.')),
|
|
19
|
+
function: optional(s.enumeration('How a list column is reduced to a single value. Required when this validator sits on an ordinary field but reads a field inside a list, because the expression cannot compare against an array. Leave unset when the validator and the field it reads are in the same list, where a row resolves to its own value.', enumValues(CalculationFunctions))),
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* The custom branch: a rule written for this form.
|
|
23
|
+
*
|
|
24
|
+
* `expression` is free text by necessity — it is evaluated at runtime and this
|
|
25
|
+
* package holds no parser to constrain it against. The description therefore
|
|
26
|
+
* does the constraining: it states the engine's grammar in prose — a comparison
|
|
27
|
+
* language that is emphatically not JavaScript — and ties every name in an
|
|
28
|
+
* expression to the declared variables.
|
|
29
|
+
*/
|
|
30
|
+
const customValidatorSchema = s.object('A validation rule written specifically for this form: a check of this field against a rule, often involving other fields, that the ordinary limits cannot express.', {
|
|
31
|
+
message: s.string("The message shown to the user when the check fails: one sentence saying what is wrong and what to do, starting with the action — 'Enter a delivery date that is after today', 'Select at least one item'. Be specific to the rule, never write 'invalid', 'error', 'please' or 'sorry', and do not repeat the hint. When the check can be overridden, say what a good reason contains, because the reason is stored, graded and read by reviewers: 'Total exceeds the available budget. To continue, state who approved the excess and attach the approval'."),
|
|
32
|
+
expression: s.string("An expression in the form engine's comparison language — NOT JavaScript — that is TRUE when the value must be REJECTED. It states the fault, not the requirement: for a spending limit write amount > limit, never amount <= limit; for an end date write endDate < startDate. The grammar is a comparison `variable OP value`, with OP one of ===, !==, ==, !=, >, <, >=, <=, or a keyword operator includes, in, startsWith, endsWith or matches; a bare variable tests truthiness; ! negates; comparisons combine with && and || and group with parentheses; a variable may be a dotted path (reason.length > 15). There is no arithmetic, no function or method call and no ternary: to compare against a computed figure, make the figure a calculated field and compare against that field. A date is compared as an ISO string. The message above is what the user is shown when the expression holds. Reference other fields only by the variable names declared below, and put quotes around every string literal you compare against (urgency === 'urgent')."),
|
|
33
|
+
canOverride: s.boolean('Whether the form may still be submitted while this check fails. Ask of every rule whether there is a legitimate exception: if there is, true, and the user must type a reason that is stored in the audit trail and shown to reviewers — the established case is a total that exceeds the available budget. If there is not, false, and the form cannot be submitted. Never make a data-quality rule overridable; a phone number is never overridably wrong.'),
|
|
34
|
+
inputsObservedForChanges: s.array('Every field the expression reads, one entry per variable name used in it — including this field itself when the expression tests its own value. An expression sees nothing but the variables declared here, not even the field it sits on, and an undeclared name is not an error: it reads as an absent field or as literal text, and the rule silently never fires.', observedInputSchema),
|
|
35
|
+
});
|
|
36
|
+
/** Renders the supplied names into prose so the model chooses on meaning. */
|
|
37
|
+
function namedValidatorSchema(validators) {
|
|
38
|
+
const candidates = validators
|
|
39
|
+
.map((v) => (v.description ? `${v.name} (${v.description})` : v.name))
|
|
40
|
+
.join('; ');
|
|
41
|
+
return s.enumeration(`The name of a validator the application already provides, chosen by meaning: ${candidates}. Reach for one only when the request asks for it by name. The ordinary limits are members of the field, not validators: required, min, max, minLength, maxLength and pattern, with the email check applied by type email.`, validators.map((v) => v.name));
|
|
42
|
+
}
|
|
43
|
+
const VALIDATORS_DESCRIPTION = describe("The checks this control must pass, beyond being filled in. Leave empty unless the form states a real rule; the ordinary limits — required, min, max, minLength, maxLength, pattern — are the field's own members and need no validator.", 'validators');
|
|
44
|
+
/**
|
|
45
|
+
* Builds the `validators` schema for the named validators the caller has.
|
|
46
|
+
*
|
|
47
|
+
* Two whole array nodes rather than one whose item is conditionally a union:
|
|
48
|
+
* the inferred type has to be a union of the two shapes for the drift
|
|
49
|
+
* assertion below to hold each branch to {@link ValidatorEntryDraft}.
|
|
50
|
+
*
|
|
51
|
+
* @param options the validators the model may name. Empty — the default —
|
|
52
|
+
* offers inline rules only.
|
|
53
|
+
*/
|
|
54
|
+
export function createValidatorsSchema(options = {}) {
|
|
55
|
+
const named = options.namedValidators ?? [];
|
|
56
|
+
return named.length
|
|
57
|
+
? s.array(VALIDATORS_DESCRIPTION, s.anyOf([namedValidatorSchema(named), customValidatorSchema]))
|
|
58
|
+
: s.array(VALIDATORS_DESCRIPTION, customValidatorSchema);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The default `validators` schema: no named validators, so only rules written
|
|
62
|
+
* inline are offered. Callers whose application registers validators should
|
|
63
|
+
* pass their names through `namedValidators` on the column options instead.
|
|
64
|
+
*/
|
|
65
|
+
export const validatorsDraftSchema = createValidatorsSchema();
|
|
66
|
+
/**
|
|
67
|
+
* Members of a generated validator the application must supply before Joi will
|
|
68
|
+
* accept it.
|
|
69
|
+
*/
|
|
70
|
+
export const VALIDATOR_DRAFT_COMPLETION = [
|
|
71
|
+
'validators[].id',
|
|
72
|
+
'validators[].inputsObservedForChanges[].inputId (resolved from formControlName)',
|
|
73
|
+
'validators[].inputsObservedForChanges[].parentInputId (resolved from parentInputFormControl)',
|
|
74
|
+
];
|