ngx-t-forms-types 0.0.30 → 0.0.33

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 (63) 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/formInput/APIDataFetchingConfigurationInterface.d.ts +34 -0
  8. package/dist/interfaces/formInput/BasicFormInputInterface.d.ts +1 -0
  9. package/dist/interfaces/formInput/BasicFormInputInterface.js +1 -0
  10. package/dist/interfaces/formInput/IMscoaAccount.d.ts +19 -3
  11. package/dist/interfaces/formInput/ISelectInputInterface.d.ts +12 -0
  12. package/dist/interfaces/formInput/MultipleInterface.d.ts +10 -0
  13. package/dist/interfaces/formInput/WorkflowDocumentPicker.d.ts +2 -0
  14. package/dist/schemas/FormInputSchema.js +41 -2
  15. package/dist/schemas/MatOptionsSchema.js +7 -0
  16. package/dist/schemas/MscoaConfigSchema.js +1 -0
  17. package/dist/schemas/index.d.ts +2 -1
  18. package/dist/schemas/index.js +2 -1
  19. package/dist/skillet/authoring-rules.d.ts +41 -0
  20. package/dist/skillet/authoring-rules.js +61 -0
  21. package/dist/skillet/extra-members.d.ts +143 -0
  22. package/dist/skillet/extra-members.js +79 -0
  23. package/dist/skillet/form.d.ts +170 -0
  24. package/dist/skillet/form.js +139 -0
  25. package/dist/skillet/index.d.ts +43 -0
  26. package/dist/skillet/index.js +46 -0
  27. package/dist/skillet/input-members.d.ts +375 -0
  28. package/dist/skillet/input-members.js +186 -0
  29. package/dist/skillet/internal/coupling.d.ts +63 -0
  30. package/dist/skillet/internal/coupling.js +69 -0
  31. package/dist/skillet/internal/editor-guidance.d.ts +64 -0
  32. package/dist/skillet/internal/editor-guidance.js +291 -0
  33. package/dist/skillet/members/calculated-field.d.ts +155 -0
  34. package/dist/skillet/members/calculated-field.js +105 -0
  35. package/dist/skillet/members/conditional.d.ts +69 -0
  36. package/dist/skillet/members/conditional.js +16 -0
  37. package/dist/skillet/members/document-picker.d.ts +182 -0
  38. package/dist/skillet/members/document-picker.js +111 -0
  39. package/dist/skillet/members/mat-options.d.ts +187 -0
  40. package/dist/skillet/members/mat-options.js +92 -0
  41. package/dist/skillet/members/mscoa.d.ts +182 -0
  42. package/dist/skillet/members/mscoa.js +129 -0
  43. package/dist/skillet/members/pagination.d.ts +59 -0
  44. package/dist/skillet/members/pagination.js +16 -0
  45. package/dist/skillet/members/table.d.ts +178 -0
  46. package/dist/skillet/members/table.js +105 -0
  47. package/dist/skillet/members/validators.d.ts +197 -0
  48. package/dist/skillet/members/validators.js +74 -0
  49. package/dist/skillet/members/value.d.ts +154 -0
  50. package/dist/skillet/members/value.js +97 -0
  51. package/dist/skillet/shared-types.d.ts +57 -0
  52. package/dist/skillet/shared-types.js +69 -0
  53. package/dist/skillet/tests/house-rules.spec.d.ts +1 -0
  54. package/dist/skillet/tests/house-rules.spec.js +162 -0
  55. package/dist/skillet/tests/skillet-coupling.spec.d.ts +1 -0
  56. package/dist/skillet/tests/skillet-coupling.spec.js +191 -0
  57. package/dist/skillet/tests/variant-assembly.spec.d.ts +1 -0
  58. package/dist/skillet/tests/variant-assembly.spec.js +643 -0
  59. package/dist/skillet/variants.d.ts +138 -0
  60. package/dist/skillet/variants.js +536 -0
  61. package/dist/skillet/workflow-context.d.ts +95 -0
  62. package/dist/skillet/workflow-context.js +128 -0
  63. package/package.json +78 -60
@@ -0,0 +1,182 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import type { AllFormInputPrimaryKeys } from '../../interfaces/FormBuilder/FormInputKeys.js';
3
+ import { type DocumentPickerFilterOperator, type DocumentPickerFilterValueType } from '../../interfaces/formInput/WorkflowDocumentPicker.js';
4
+ import { AssertNever } from '../internal/coupling.js';
5
+ import type { AvailableWorkflow } from './mat-options.js';
6
+ /**
7
+ * The `workflowPickerConfig` member: which workflow a document picker browses,
8
+ * and how that list is narrowed before the user is shown anything.
9
+ *
10
+ * ## Why this is a factory, and why it may decline to build a schema at all
11
+ *
12
+ * The one field an administrator must supply is `workflowId`, and it is a
13
+ * persisted id. A model asked for one emits a plausible hex string, and what
14
+ * comes back is a picker that renders, opens, and lists nothing — a failure
15
+ * that surfaces to an end user rather than to whoever generated the form.
16
+ *
17
+ * The builder never asks a human to type it either: the editor row for this
18
+ * property is a `WorkflowPicker`, which PICKS from the workflows the tenant
19
+ * actually has. So the model picks too. {@link createWorkflowPickerSchema}
20
+ * takes the workflows available in the caller's context and enumerates their
21
+ * ids, exactly as the `api` branch of `mat-options.ts` enumerates endpoint ids.
22
+ *
23
+ * With no workflows to enumerate, that reasoning goes one step further than it
24
+ * does there. In `mat-options` a missing endpoint list drops one BRANCH and the
25
+ * remaining sources still stand, so a schema is still worth offering. Here the
26
+ * unavailable field is the only required one, and every other member is scoped
27
+ * by it — a step id, a filter path, a document field all mean nothing until the
28
+ * workflow is known — so there is no reduced schema left to offer. The factory
29
+ * returns `null`, and the caller leaves `workflowPickerConfig` off the element
30
+ * rather than letting a model invent the one value that has to be real. That is
31
+ * also why this module exports no ready-made default schema: the no-workflows
32
+ * default is `null`, and an exported `null` reads as an oversight.
33
+ *
34
+ * ## Why this produces a draft
35
+ *
36
+ * `fromInputId` is stored as an input id — the runtime form group is keyed by
37
+ * input id, not by form-control name. A model authoring a form does not have
38
+ * those ids; it has just written the fields by NAME. So the draft carries the
39
+ * `formControlName`, the same trade `inputSourceId` makes in `mat-options.ts`,
40
+ * and the application resolves it against the form it was generated alongside
41
+ * before the config is stored. The member keeps its interface name so that
42
+ * resolution stays a substitution rather than a rename.
43
+ *
44
+ * The absent members are the second reason this is a draft: Skillet has no
45
+ * `optional()`, so every declared key is emitted and optionality is a `| null`
46
+ * branch. {@link WorkflowPickerConfigDraft} therefore describes what the model
47
+ * writes, not what `IWorkflowDocumentPickerConfig` stores, and stripping the
48
+ * nulls is part of the same expansion pass that resolves `fromInputId`.
49
+ *
50
+ * ## Deliberate omissions
51
+ *
52
+ * `primaryIdentifierKey` is `TreeNode[]` — nodes of the key tree the builder
53
+ * derives from a captured sample document. A model has no sample and therefore
54
+ * no tree to select from, and the behaviour it would be overriding (identify
55
+ * the transaction by `_id`) is already the right answer whenever it cannot.
56
+ *
57
+ * `sampleDocument` is captured from a real transaction when the workflow is
58
+ * chosen. It is evidence, not configuration; generated, it would be fiction
59
+ * that the key tree above is then built from.
60
+ *
61
+ * `availableSteps` is a cache of the chosen workflow's steps, refreshed by the
62
+ * builder whenever the workflow changes and never consulted at runtime — the
63
+ * step chooser always reads live steps. Generating it means inventing the very
64
+ * list the step filter is then checked against.
65
+ *
66
+ * A filter's `id` is stamped by the record-list editor the first time the
67
+ * filter is saved, and is the key that editor tracks a row by for edit and for
68
+ * delete. It is an identity, not a decision, so it is left to the editor that
69
+ * owns it.
70
+ *
71
+ * `value` is `unknown` on the interface and is narrowed here to a string,
72
+ * number or boolean — the three shapes `valueType` can actually coerce.
73
+ *
74
+ * ## Known gap: step ids
75
+ *
76
+ * `stepFilter.stepIds` is modelled, but the ids belong to the workflow and
77
+ * {@link AvailableWorkflow} carries only an id and a name, so nothing in this
78
+ * schema can enumerate them. The description says so plainly and tells the
79
+ * model to leave the step filter null unless the request itself names real step
80
+ * ids; an invented id narrows the picker to nothing, which is the same silent
81
+ * failure an invented `workflowId` produces. Widening `AvailableWorkflow` to
82
+ * carry its steps would close this properly, but that type is shared with
83
+ * `mat-options.ts` and is not changed from here.
84
+ */
85
+ export interface WorkflowPickerSchemaOptions {
86
+ /**
87
+ * Workflows the model may point the picker at. Omit, or pass an empty list,
88
+ * and {@link createWorkflowPickerSchema} produces no schema at all — there is
89
+ * no useful subset of this config that does not name a workflow.
90
+ */
91
+ workflows?: readonly AvailableWorkflow[];
92
+ }
93
+ /**
94
+ * The two value sources, addressed by meaning.
95
+ *
96
+ * Each branch of the filter union carries DIFFERENT members, so these literals
97
+ * cannot be taken positionally out of `DOCUMENT_PICKER_FILTER_VALUE_SOURCES`:
98
+ * reordering that array would quietly file `value` and `valueType` under the
99
+ * input-bound source, and produce filters that validate and never match.
100
+ * {@link exhaustive} is this codebase's answer to restating members safely —
101
+ * the union it checks against is itself read off that array, so adding a third
102
+ * source fails to compile here until it is given a branch of its own.
103
+ */
104
+ declare const FIXED_SOURCE: "fixed", INPUT_SOURCE: "input";
105
+ export interface WorkflowStepFilterDraft {
106
+ stepIds: string[];
107
+ }
108
+ /** A clause comparing a document field against a value fixed in the form. */
109
+ export interface FixedValueFilterDraft {
110
+ path: string;
111
+ op: DocumentPickerFilterOperator | null;
112
+ valueSource: typeof FIXED_SOURCE;
113
+ value: string | number | boolean;
114
+ valueType: DocumentPickerFilterValueType | null;
115
+ }
116
+ /** A clause comparing a document field against another field on this form. */
117
+ export interface InputValueFilterDraft {
118
+ path: string;
119
+ op: DocumentPickerFilterOperator | null;
120
+ valueSource: typeof INPUT_SOURCE;
121
+ /** A `formControlName`, which the application resolves to an input id. */
122
+ fromInputId: string;
123
+ omitWhenEmpty: boolean;
124
+ }
125
+ export type DocumentPickerFilterDraft = FixedValueFilterDraft | InputValueFilterDraft;
126
+ export interface WorkflowPickerConfigDraft {
127
+ workflowId: string;
128
+ stepFilter: WorkflowStepFilterDraft | null;
129
+ presetFilters: DocumentPickerFilterDraft[];
130
+ }
131
+ /**
132
+ * Builds the `workflowPickerConfig` schema over the workflows the caller has.
133
+ *
134
+ * @param options the workflows the model may choose between. Empty — the
135
+ * default — yields `null` rather than a schema whose only required field
136
+ * would have to be invented.
137
+ * @returns the schema, or `null` when there is no workflow to point it at.
138
+ */
139
+ export declare function createWorkflowPickerSchema(options?: WorkflowPickerSchemaOptions): s.ObjectType<{
140
+ workflowId: s.EnumType<string[]>;
141
+ stepFilter: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
142
+ stepIds: string[];
143
+ } | null>;
144
+ presetFilters: s.ArrayType<import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
145
+ path: string;
146
+ op: "eq" | "ne" | "in" | "nin" | "gt" | "gte" | "lt" | "lte" | "regex" | "exists" | null;
147
+ valueSource: "fixed";
148
+ value: string | number | boolean;
149
+ valueType: "string" | "number" | "boolean" | null;
150
+ } | {
151
+ path: string;
152
+ op: "eq" | "ne" | "in" | "nin" | "gt" | "gte" | "lt" | "lte" | "regex" | "exists" | null;
153
+ valueSource: "input";
154
+ fromInputId: string;
155
+ omitWhenEmpty: boolean;
156
+ }>>;
157
+ }> | null;
158
+ /** The schema the factory builds, independent of which workflows it was given. */
159
+ type WorkflowPickerSchema = NonNullable<ReturnType<typeof createWorkflowPickerSchema>>;
160
+ /**
161
+ * The drafted node produces the draft it declares. Loosening a branch, or
162
+ * retyping a member of {@link WorkflowPickerConfigDraft}, surfaces here by key
163
+ * name — the guarantee `_NoDraftDrift` gives `matOptions` in
164
+ * `input-members.ts`, applied to the one member this module owns. What no
165
+ * assertion can check is the expansion from draft to stored config; that
166
+ * contract lives with the caller.
167
+ */
168
+ export type _NoWorkflowPickerDraftDrift = AssertNever<[
169
+ s.Infer<WorkflowPickerSchema>
170
+ ] extends [WorkflowPickerConfigDraft] ? never : AllFormInputPrimaryKeys.WorkflowPickerConfig>;
171
+ /**
172
+ * Members of a generated `workflowPickerConfig` the application must resolve.
173
+ *
174
+ * Only one, and it is not a Joi requirement — `presetFilters[].id` is optional
175
+ * there, so an unexpanded filter still validates. It is listed because it would
176
+ * still be WRONG: `fromInputId` is keyed by input id, and a draft carries a
177
+ * formControlName in that slot. A filter left unresolved passes validation and
178
+ * then matches nothing, which is the failure mode this whole layer is most
179
+ * concerned with.
180
+ */
181
+ export declare const WORKFLOW_PICKER_DRAFT_COMPLETION: readonly ["workflowPickerConfig.presetFilters[].fromInputId (resolved from formControlName)"];
182
+ export {};
@@ -0,0 +1,111 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import { DOCUMENT_PICKER_FILTER_OPERATORS, DOCUMENT_PICKER_FILTER_VALUE_TYPES, } from '../../interfaces/formInput/WorkflowDocumentPicker.js';
3
+ import { exhaustive, optional } from '../internal/coupling.js';
4
+ import { describe, describeIn } from '../internal/editor-guidance.js';
5
+ // --- the value-source discriminator -----------------------------------------
6
+ /**
7
+ * The two value sources, addressed by meaning.
8
+ *
9
+ * Each branch of the filter union carries DIFFERENT members, so these literals
10
+ * cannot be taken positionally out of `DOCUMENT_PICKER_FILTER_VALUE_SOURCES`:
11
+ * reordering that array would quietly file `value` and `valueType` under the
12
+ * input-bound source, and produce filters that validate and never match.
13
+ * {@link exhaustive} is this codebase's answer to restating members safely —
14
+ * the union it checks against is itself read off that array, so adding a third
15
+ * source fails to compile here until it is given a branch of its own.
16
+ */
17
+ const [FIXED_SOURCE, INPUT_SOURCE] = exhaustive()([
18
+ 'fixed',
19
+ 'input',
20
+ ]);
21
+ // --- schema -----------------------------------------------------------------
22
+ /**
23
+ * The `deepBind` of the row that opens the preset-filter editor. The filter
24
+ * members below bind relative to the record that editor edits, so their
25
+ * guidance is looked up within it rather than at the top level — see
26
+ * `internal/editor-guidance.ts` for why the two indexes are kept apart.
27
+ */
28
+ const FILTER_EDITOR = ['workflowPickerConfig', 'presetFilters'];
29
+ // Shared by both branches, so declared once: a clause targets a field and
30
+ // compares it the same way whichever side supplies the value.
31
+ const filterPath = s.string(describeIn('The document field this clause compares. It must be a field the transactions of the chosen workflow actually carry — a field of their form, or one of the properties the document itself exposes.', FILTER_EDITOR, 'path'));
32
+ const filterOp = optional(s.enumeration(describeIn("How the field is compared. Leave it null for the default, which is 'eq'. 'in' and 'nin' read the fixed value as a comma-separated list, and 'exists' ignores the comparison entirely and only tests whether the field is present, so pair it with a boolean value.", FILTER_EDITOR, 'op'), [...DOCUMENT_PICKER_FILTER_OPERATORS]));
33
+ /**
34
+ * The fixed branch.
35
+ *
36
+ * Its value node is a union and so has no description slot of its own. The
37
+ * editor's guidance is composed onto the string branch, the only one the comma
38
+ * rule for `in` / `nin` can apply to; repeating it on the numeric and boolean
39
+ * branches would put the same sentence in one prompt three times for no gain,
40
+ * because the choice being made there is the type, not the format.
41
+ */
42
+ const fixedValueFilter = s.object(describeIn('Compares the field against a value written into the form definition itself. Use this for a restriction that is always true of this picker, such as only ever offering approved transactions.', FILTER_EDITOR, 'valueSource'), {
43
+ path: filterPath,
44
+ op: filterOp,
45
+ valueSource: s.literal(FIXED_SOURCE),
46
+ value: s.anyOf([
47
+ s.string(describeIn('The fixed value, as text. Write it as the field is stored, not as it is labelled on screen.', FILTER_EDITOR, 'value')),
48
+ s.number('The fixed value, as a number. Set valueType to number to match.'),
49
+ s.boolean('The fixed value, as a boolean. Set valueType to boolean to match.'),
50
+ ]),
51
+ valueType: optional(s.enumeration(describeIn("How the fixed value is read before it is compared. Leave it null for the default, which is 'string'.", FILTER_EDITOR, 'valueType'), [...DOCUMENT_PICKER_FILTER_VALUE_TYPES])),
52
+ });
53
+ /** The input-bound branch. */
54
+ const inputValueFilter = s.object(describeIn('Compares the field against whatever the user has entered in another field of this form, read when the picker is opened. Use this to tie the picker to a choice the user makes earlier in the same form.', FILTER_EDITOR, 'valueSource'), {
55
+ path: filterPath,
56
+ op: filterOp,
57
+ valueSource: s.literal(INPUT_SOURCE),
58
+ // Named for the interface member it becomes, but carrying a
59
+ // formControlName: the model knows the fields it just wrote by name and has
60
+ // no way to know the ids the runtime keys them by. The application performs
61
+ // that lookup — see the draft note in this file's header.
62
+ fromInputId: s.string(describeIn('The formControlName of the field on this form to take the value from. It must be a field that actually exists in the form being authored, and one the user reaches before this picker. The application resolves the name to that field’s input id.', FILTER_EDITOR, 'fromInputId')),
63
+ omitWhenEmpty: s.boolean(describeIn('Whether to drop this clause while that field is still empty. Prefer true: false filters on the empty value literally, so the user opens the picker, is shown nothing, and has no way to tell that the cause is a field further up the form.', FILTER_EDITOR, 'omitWhenEmpty')),
64
+ });
65
+ /**
66
+ * One preset filter, as a union over the two value sources.
67
+ *
68
+ * The exclusivity is real rather than conventional: the runtime uses one source
69
+ * and ignores the other, so a clause carrying both is not a richer clause — it
70
+ * is a clause half of whose configuration silently does nothing. Modelling it
71
+ * as a union makes the model choose the source BEFORE it is offered the members
72
+ * that source uses, instead of handing it a flat record of six optional keys
73
+ * and leaving it to infer which combinations mean anything.
74
+ */
75
+ const presetFilter = s.anyOf([fixedValueFilter, inputValueFilter]);
76
+ /**
77
+ * Builds the `workflowPickerConfig` schema over the workflows the caller has.
78
+ *
79
+ * @param options the workflows the model may choose between. Empty — the
80
+ * default — yields `null` rather than a schema whose only required field
81
+ * would have to be invented.
82
+ * @returns the schema, or `null` when there is no workflow to point it at.
83
+ */
84
+ export function createWorkflowPickerSchema(options = {}) {
85
+ const workflows = options.workflows ?? [];
86
+ if (!workflows.length)
87
+ return null;
88
+ // Rendered into prose so the choice is made on the workflow's meaning rather
89
+ // than on the shape of its id, which carries none.
90
+ const candidates = workflows.map((w) => `${w.id} (${w.name})`).join('; ');
91
+ return s.object('Which workflow a document picker browses, and how its list of transactions is narrowed before the user sees it. Set this only on a control that picks an existing transaction.', {
92
+ workflowId: s.enumeration(describe(`Whose transactions this picker lists. Everything else here is scoped by it. Choose by meaning, not by position: ${candidates}.`, 'workflowPickerConfig', 'workflowId'), workflows.map((w) => w.id)),
93
+ stepFilter: optional(s.object('Restricts the picker to transactions currently sitting on particular workflow steps.', {
94
+ stepIds: s.array(describe('Workflow step ids the picker is limited to. These ids belong to the workflow and are not listed here, so leave the whole step filter null unless the request names real ones — an id that does not exist narrows the picker to nothing, and nothing says so. An empty list means the same as no filter: every step is eligible, and the user chooses one while browsing.', 'workflowPickerConfig', 'stepFilter', 'stepIds'), s.string('A workflow step id, exactly as the workflow defines it.')),
95
+ })),
96
+ presetFilters: s.array(describe('Filters applied before the user searches, each one narrowing which transactions can be picked at all. Leave this empty unless a restriction was actually asked for: every clause here is one the user can neither see nor undo.', 'workflowPickerConfig', 'presetFilters'), presetFilter),
97
+ });
98
+ }
99
+ /**
100
+ * Members of a generated `workflowPickerConfig` the application must resolve.
101
+ *
102
+ * Only one, and it is not a Joi requirement — `presetFilters[].id` is optional
103
+ * there, so an unexpanded filter still validates. It is listed because it would
104
+ * still be WRONG: `fromInputId` is keyed by input id, and a draft carries a
105
+ * formControlName in that slot. A filter left unresolved passes validation and
106
+ * then matches nothing, which is the failure mode this whole layer is most
107
+ * concerned with.
108
+ */
109
+ export const WORKFLOW_PICKER_DRAFT_COMPLETION = [
110
+ 'workflowPickerConfig.presetFilters[].fromInputId (resolved from formControlName)',
111
+ ];
@@ -0,0 +1,187 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import { DataSources } from '../../interfaces/formInput/APIDataFetchingConfigurationInterface.js';
3
+ import type { FormInputBasicOptionInterface } from '../../interfaces/formInput/FormInputBasicOptionInterface.js';
4
+ /**
5
+ * The `matOptions` member: where a control's value and its options come from.
6
+ *
7
+ * ## Why this is a factory, and why it produces a draft
8
+ *
9
+ * `APIDataFetchingConfigurationInterface` requires `_id`, `name`,
10
+ * `httpEndPoint`, `httpMethod`, `postFormData` and `backEndConfig`. A model
11
+ * asked for that will happily produce `httpEndPoint:
12
+ * 'https://api.example.com/departments'` and a plausible `_id`, and the result
13
+ * is a form that looks correct and is entirely broken — it points at an
14
+ * endpoint that does not exist. Of everything in these schemas, that is the
15
+ * failure most likely to reach production unnoticed, because nothing about the
16
+ * output looks wrong.
17
+ *
18
+ * The form builder never asks a human to do this either. The editor row for
19
+ * this property is `apiEndpointConfig`, backed by `postmanCollectionConfig` and
20
+ * `httpGetDataFunction` — the author PICKS an endpoint from a collection
21
+ * supplied at runtime. So the model picks too: {@link createMatOptionsSchema}
22
+ * takes the endpoints and workflows available in the caller's context and emits
23
+ * an enumeration over their ids. A source that has no candidates is not offered
24
+ * at all, which is why the branches are conditional rather than always present.
25
+ *
26
+ * The consequence is that this schema produces a {@link MatOptionsDraft}, not a
27
+ * `MatDataOptionsInterface`: an api draft carries the id of an endpoint, and
28
+ * the application expands it into the stored configuration before the form is
29
+ * validated. That expansion is the caller's job and there is no way around it —
30
+ * the full configuration is simply not knowable from a prompt.
31
+ *
32
+ * ## Deliberate narrowing
33
+ *
34
+ * `options` is declared as `Array<FormInputBasicOptionInterface |
35
+ * DynamicObjectInterface>`. The second branch is an index signature, which
36
+ * Skillet cannot express and a model has no basis for inventing, so only the
37
+ * `{ label, value }` shape is offered.
38
+ *
39
+ * A `mongoPipeline` source carries `pipeline: any[]` — a raw aggregation
40
+ * pipeline. Selecting the workflow is offered; authoring the pipeline is not.
41
+ */
42
+ /** An endpoint the caller has available for the model to choose between. */
43
+ export interface AvailableApiEndpoint {
44
+ /** The persisted `_id` of the endpoint configuration. */
45
+ id: string;
46
+ /** Human name, shown to the model so it can choose on meaning, not on id. */
47
+ name: string;
48
+ /** What the endpoint returns, if known. Sharpens the choice considerably. */
49
+ description?: string;
50
+ }
51
+ /** A workflow whose data pipeline the caller has available. */
52
+ export interface AvailableWorkflow {
53
+ id: string;
54
+ name: string;
55
+ }
56
+ export interface MatOptionsSchemaOptions {
57
+ /**
58
+ * Endpoints the model may select for an `api` source. Omit, or pass an empty
59
+ * list, and the `api` branch is not offered — which is the safe default,
60
+ * because the alternative is a hallucinated URL.
61
+ */
62
+ apiEndpoints?: readonly AvailableApiEndpoint[];
63
+ /** Workflows the model may select for a `mongoPipeline` source. */
64
+ workflows?: readonly AvailableWorkflow[];
65
+ }
66
+ export interface LocalSourceDraft {
67
+ source: DataSources.Local;
68
+ inputSourceId: string;
69
+ allowManualValueEntry: boolean;
70
+ }
71
+ export interface CustomOptionsSourceDraft {
72
+ source: DataSources.CustomOptions;
73
+ }
74
+ export interface ApiSourceDraft {
75
+ source: DataSources.Api;
76
+ endpointId: string;
77
+ allowManualValueEntry: boolean;
78
+ }
79
+ export interface MongoSourceDraft {
80
+ source: DataSources.MongoDb;
81
+ workflowId: string;
82
+ }
83
+ export type ValueSourceDraft = LocalSourceDraft | CustomOptionsSourceDraft | ApiSourceDraft | MongoSourceDraft;
84
+ export interface MatOptionsDraft {
85
+ optionType: 'user' | null;
86
+ options: FormInputBasicOptionInterface[];
87
+ fetch: {
88
+ value: ValueSourceDraft | null;
89
+ options: ValueSourceDraft | null;
90
+ };
91
+ }
92
+ /**
93
+ * Builds the `matOptions` schema for the sources available to the caller.
94
+ *
95
+ * @param options endpoints and workflows the model may choose between. Both
96
+ * default to empty, which offers only the sources a model can author outright
97
+ * and cannot get factually wrong.
98
+ */
99
+ export declare function createMatOptionsSchema(options?: MatOptionsSchemaOptions): s.ObjectType<{
100
+ optionType: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"user" | null>;
101
+ options: s.ArrayType<s.ObjectType<{
102
+ label: s.StringType;
103
+ value: s.StringType;
104
+ }>>;
105
+ fetch: s.ObjectType<{
106
+ value: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
107
+ source: DataSources.Local;
108
+ inputSourceId: string;
109
+ allowManualValueEntry: boolean;
110
+ } | {
111
+ source: DataSources.CustomOptions;
112
+ } | {
113
+ source: DataSources.Api;
114
+ endpointId: string;
115
+ allowManualValueEntry: boolean;
116
+ } | {
117
+ source: DataSources.MongoDb;
118
+ workflowId: string;
119
+ } | null>;
120
+ options: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
121
+ source: DataSources.Local;
122
+ inputSourceId: string;
123
+ allowManualValueEntry: boolean;
124
+ } | {
125
+ source: DataSources.CustomOptions;
126
+ } | {
127
+ source: DataSources.Api;
128
+ endpointId: string;
129
+ allowManualValueEntry: boolean;
130
+ } | {
131
+ source: DataSources.MongoDb;
132
+ workflowId: string;
133
+ } | null>;
134
+ }>;
135
+ }>;
136
+ /**
137
+ * The default `matOptions` schema: no external endpoints or workflows, so it
138
+ * offers only the sources a model can author correctly from a prompt alone.
139
+ *
140
+ * Callers holding a real endpoint collection should build their own with
141
+ * {@link createMatOptionsSchema} rather than reaching for this one.
142
+ */
143
+ export declare const matOptionsSchema: s.ObjectType<{
144
+ optionType: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"user" | null>;
145
+ options: s.ArrayType<s.ObjectType<{
146
+ label: s.StringType;
147
+ value: s.StringType;
148
+ }>>;
149
+ fetch: s.ObjectType<{
150
+ value: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
151
+ source: DataSources.Local;
152
+ inputSourceId: string;
153
+ allowManualValueEntry: boolean;
154
+ } | {
155
+ source: DataSources.CustomOptions;
156
+ } | {
157
+ source: DataSources.Api;
158
+ endpointId: string;
159
+ allowManualValueEntry: boolean;
160
+ } | {
161
+ source: DataSources.MongoDb;
162
+ workflowId: string;
163
+ } | null>;
164
+ options: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
165
+ source: DataSources.Local;
166
+ inputSourceId: string;
167
+ allowManualValueEntry: boolean;
168
+ } | {
169
+ source: DataSources.CustomOptions;
170
+ } | {
171
+ source: DataSources.Api;
172
+ endpointId: string;
173
+ allowManualValueEntry: boolean;
174
+ } | {
175
+ source: DataSources.MongoDb;
176
+ workflowId: string;
177
+ } | null>;
178
+ }>;
179
+ }>;
180
+ /**
181
+ * Members of a generated `matOptions` the application must expand.
182
+ *
183
+ * `endpointId` and `workflowId` are draft-only keys that do not exist on
184
+ * `MatDataOptionsInterface` at all — Joi rejects unknown keys, so a draft that
185
+ * reaches the validator unexpanded fails outright rather than degrading.
186
+ */
187
+ export declare const MAT_OPTIONS_DRAFT_COMPLETION: readonly ["matOptions.fetch.value|options.endpointId (expanded to the stored APIDataFetchingConfiguration)", "matOptions.fetch.value|options.workflowId (expanded to the stored pipeline configuration)", "matOptions.fetch.value|options.inputSourceId (resolved from formControlName to an input id)"];
@@ -0,0 +1,92 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import { DataSources } from '../../interfaces/formInput/APIDataFetchingConfigurationInterface.js';
3
+ import { describe } from '../internal/editor-guidance.js';
4
+ // --- schema -----------------------------------------------------------------
5
+ const optionSchema = s.object('A selectable option.', {
6
+ label: s.string('The text shown to the user. It may change freely.'),
7
+ value: s.string("The value stored when this option is chosen: a single word with no spaces ('urgent', 'capex'), kept stable once the form is live. A decision gate compares the stored value, not the label, and splits its expression on spaces."),
8
+ });
9
+ /** Renders the endpoint list into prose so the model chooses on meaning. */
10
+ function describeCandidates(lead, candidates) {
11
+ const lines = candidates.map((c) => c.description ? `${c.id} (${c.name}: ${c.description})` : `${c.id} (${c.name})`);
12
+ return `${lead} Choose by meaning, not by position: ${lines.join('; ')}.`;
13
+ }
14
+ function sourceBranches(slot, options) {
15
+ const localLead = slot === 'value'
16
+ ? 'Takes its value from another field on this form.'
17
+ : 'Takes its options from another field on this form.';
18
+ const endpoints = options.apiEndpoints ?? [];
19
+ const workflows = options.workflows ?? [];
20
+ // Built by conditional spread rather than by pushing onto an array: a `push`
21
+ // would have to widen every branch to the shape of the first two, which
22
+ // erases the per-branch typing that `s.Infer` needs to reconstruct the union.
23
+ return [
24
+ s.object(describe(localLead, 'matOptions', 'fetch', slot, 'source'), {
25
+ source: s.literal(DataSources.Local),
26
+ inputSourceId: s.string('The formControlName of the field on this form to read from. It must be a field that actually exists in the form being authored.'),
27
+ allowManualValueEntry: s.boolean('Only meaningful on the value slot of a field whose dataType is object or array (an mSCOA, a multiple-input table). An inherited value of that shape can arrive partly filled in — only some mSCOA segments, rows missing a column — and this decides whether the user may finish it. True leaves the field editable: whenever the inherited value changes, only what actually changed is applied over it and whatever the user added is kept. False locks the field. Choose false unless the field is object- or array-shaped AND the source field is expected to supply only part of it. It has no effect on a scalar field or on an options slot, where the inherited value always wins.'),
28
+ }),
29
+ s.object('Uses a fixed list of options authored into the form itself.', {
30
+ source: s.literal(DataSources.CustomOptions),
31
+ }),
32
+ ...(endpoints.length
33
+ ? [
34
+ s.object('Fetches from a configured API endpoint.', {
35
+ source: s.literal(DataSources.Api),
36
+ endpointId: s.enumeration(describeCandidates('Which configured endpoint to call.', endpoints), endpoints.map((e) => e.id)),
37
+ allowManualValueEntry: s.boolean('Whether the user may supply what the endpoint does not. On a scalar field: true lets a value they type stand whenever the endpoint contributes nothing (no response, an empty one, a failed request), and a non-empty response always wins over what they typed. On a field whose dataType is object or array (an mSCOA, a multiple-input table): true leaves the field editable so they can finish a response that arrives incomplete — rows missing a column, only some mSCOA segments — and each new response is applied over the live value as a delta, so what the endpoint sends updates and what the user added is kept. False locks the field, which is the right choice whenever the endpoint answers completely.'),
38
+ }),
39
+ ]
40
+ : []),
41
+ ...(workflows.length
42
+ ? [
43
+ s.object('Fetches through a workflow data pipeline.', {
44
+ source: s.literal(DataSources.MongoDb),
45
+ workflowId: s.enumeration(describeCandidates('Which workflow supplies the data.', workflows), workflows.map((w) => w.id)),
46
+ }),
47
+ ]
48
+ : []),
49
+ ];
50
+ }
51
+ /**
52
+ * Builds the `matOptions` schema for the sources available to the caller.
53
+ *
54
+ * @param options endpoints and workflows the model may choose between. Both
55
+ * default to empty, which offers only the sources a model can author outright
56
+ * and cannot get factually wrong.
57
+ */
58
+ export function createMatOptionsSchema(options = {}) {
59
+ const valueSource = s.anyOf(sourceBranches('value', options));
60
+ const optionsSource = s.anyOf(sourceBranches('options', options));
61
+ return s.object('Where this control gets its value and the options it offers. Only set this on a control that actually reads from somewhere; a plain text input does not.', {
62
+ optionType: s.anyOf([
63
+ s.literal('user'),
64
+ s.nullish(),
65
+ ]),
66
+ options: s.array(describe('The fixed options offered by this control, for a short list fixed by policy. Fill this in only when the options source is a custom list; leave it empty otherwise. Never retype a list a system already owns — departments, suppliers, users, cost centres — as custom options; it goes stale.', 'matOptions', 'options'), optionSchema),
67
+ fetch: s.object('Where the value and the options are read from.', {
68
+ value: s.anyOf([valueSource, s.nullish()]),
69
+ options: s.anyOf([optionsSource, s.nullish()]),
70
+ }),
71
+ });
72
+ }
73
+ /**
74
+ * The default `matOptions` schema: no external endpoints or workflows, so it
75
+ * offers only the sources a model can author correctly from a prompt alone.
76
+ *
77
+ * Callers holding a real endpoint collection should build their own with
78
+ * {@link createMatOptionsSchema} rather than reaching for this one.
79
+ */
80
+ export const matOptionsSchema = createMatOptionsSchema();
81
+ /**
82
+ * Members of a generated `matOptions` the application must expand.
83
+ *
84
+ * `endpointId` and `workflowId` are draft-only keys that do not exist on
85
+ * `MatDataOptionsInterface` at all — Joi rejects unknown keys, so a draft that
86
+ * reaches the validator unexpanded fails outright rather than degrading.
87
+ */
88
+ export const MAT_OPTIONS_DRAFT_COMPLETION = [
89
+ 'matOptions.fetch.value|options.endpointId (expanded to the stored APIDataFetchingConfiguration)',
90
+ 'matOptions.fetch.value|options.workflowId (expanded to the stored pipeline configuration)',
91
+ 'matOptions.fetch.value|options.inputSourceId (resolved from formControlName to an input id)',
92
+ ];