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,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
+ ];
@@ -0,0 +1,182 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import type { AccountingBasis, DualCashExclusionMatchMode } from '../../interfaces/formInput/IMscoaAccount.js';
3
+ import { AssertNever } from '../internal/coupling.js';
4
+ /**
5
+ * The `mscoaConfig` member: how a municipal SCOA account picker is set up.
6
+ *
7
+ * One `mscoaSelection` element asks a user to pick accounts out of the chart of
8
+ * accounts, segment by segment. This object says which segments it asks for,
9
+ * how each account is labelled back to them, which accounting basis is being
10
+ * captured, and — for a dual basis — when the cash side is not actually
11
+ * required.
12
+ *
13
+ * ## Why this produces a draft
14
+ *
15
+ * `IScoaInputConfig` requires `inputs: ScoaInnerInput[]`, and a
16
+ * `ScoaInnerInput` is a whole `FormColumnInputs` carrying its own `id` plus a
17
+ * `linkedSegmentId` pointing at a segment row that does not exist until the
18
+ * segments themselves have been assigned ids. Asking a model for that is asking
19
+ * it to invent two sets of identifiers and then cross-reference them, which it
20
+ * will do, plausibly and wrongly. So `inputs` is not offered here at all; the
21
+ * application supplies `inputs: []` when it completes the draft, and the
22
+ * builder's own "additional inputs" editor is where those are authored.
23
+ *
24
+ * The same reasoning retires `id` from segments and from exclusion rules. Both
25
+ * are row keys stamped by the builder's record editors (`uuidv4()` at the point
26
+ * of save), never typed by a human — which is exactly why `id` and `sectionId`
27
+ * sit in `SYSTEM_OWNED_INPUT_MEMBERS` one level up. A model-authored id is at
28
+ * best noise and at worst a collision with a row that already exists.
29
+ *
30
+ * ## Draft-completion obligations
31
+ *
32
+ * Before this output can be handed to `validateMscoaInputConfig`, the caller
33
+ * must:
34
+ *
35
+ * 1. add `inputs: []` — required by `MscoaInputConfigSchema` and deliberately
36
+ * absent here;
37
+ * 2. stamp an `id` on every entry of `segments`, `cashSegments` and
38
+ * `dualCashExclusion.rules`;
39
+ * 3. strip the nulls standing in for absent optional members — Joi rejects a
40
+ * `null` where it declared an optional string.
41
+ *
42
+ * None of the three is optional, and none of them is knowable from a prompt.
43
+ *
44
+ * ## Deliberate narrowing
45
+ *
46
+ * `IScoaInputConfig` also declares `label` and `hint`. `MscoaInputConfigSchema`
47
+ * does not, and a Joi object rejects unknown keys by default — so emitting
48
+ * either would fail the very validation this schema exists to pass. The visible
49
+ * label of the control is the input's own `label` member, one level up. Joi
50
+ * wins, and they are omitted.
51
+ *
52
+ * `accountValueLabel` is narrowed from `string` to the pool the settings panel
53
+ * offers, derived from {@link MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS} at evaluation
54
+ * rather than retyped — the same treatment the enum-backed nodes get, for the
55
+ * same reason: a hand-copied list is a list that drifts.
56
+ *
57
+ * `segment` is asked for on every segment row, although the interface types it
58
+ * `string | undefined`. The builder's segment form declares it
59
+ * `Validators.required`, so a row without one cannot be saved by hand either.
60
+ * Narrower than the interface is the permitted direction; wider is not.
61
+ */
62
+ export interface MscoaSegmentDraft {
63
+ segment: string;
64
+ customSegment: string | null;
65
+ label: string;
66
+ readOnly: boolean;
67
+ singleSelect: boolean;
68
+ additionalAccounts: string[];
69
+ inheritValueFromAccrual: boolean | null;
70
+ vatSelectonActive: boolean | null;
71
+ segmentExtension: boolean;
72
+ }
73
+ export interface MscoaDualCashExclusionRuleDraft {
74
+ pattern: string;
75
+ flags: string | null;
76
+ matchField: string | null;
77
+ segment: string | null;
78
+ description: string | null;
79
+ }
80
+ export interface MscoaDualCashExclusionDraft {
81
+ rules: MscoaDualCashExclusionRuleDraft[] | null;
82
+ matchMode: DualCashExclusionMatchMode | null;
83
+ }
84
+ export interface MscoaConfigDraft {
85
+ segments: MscoaSegmentDraft[];
86
+ cashSegments: MscoaSegmentDraft[] | null;
87
+ accountValueLabel: string;
88
+ accountingBasis: AccountingBasis | null;
89
+ extensionAccountsForSegments: string[];
90
+ showAllSegments: boolean;
91
+ dualCashExclusion: MscoaDualCashExclusionDraft | null;
92
+ }
93
+ /**
94
+ * `DualCashExclusionMatchMode` is a string-union type with no runtime object to
95
+ * read, so its members are restated under {@link exhaustive} rather than
96
+ * derived — drop one and this stops compiling.
97
+ */
98
+ export declare const DUAL_CASH_MATCH_MODE_VALUES: readonly ["any", "all"];
99
+ /**
100
+ * The `mscoaConfig` schema.
101
+ *
102
+ * A plain const, not a factory: nothing in here is chosen from candidates the
103
+ * caller holds. The one member that draws on a fixed pool — `accountValueLabel`
104
+ * — reads that pool out of this package at evaluation.
105
+ *
106
+ * ## Where the editor guidance is attached, and where it is not
107
+ *
108
+ * There is exactly ONE editor row for this whole object, at path
109
+ * `['mscoaConfig']`: a composite `MscoaConfig` editor that replaced eight
110
+ * separate rows (accountingBasis, accountValueLabel, showAllSegments, segments,
111
+ * cashSegments, the two dual-cash members and extensionAccountsForSegments).
112
+ * So {@link describe} is called once, on the object the row actually edits, and
113
+ * the members below carry authored descriptions only.
114
+ *
115
+ * That is a deliberate choice rather than an oversight. Calling
116
+ * `describe(base, 'mscoaConfig')` on each member compiles and would append the
117
+ * same forty-word hint — ending in "Opens a guided setup", which is advice for
118
+ * someone looking at a settings panel — to all seven of them. Layering it once,
119
+ * on the composite, says the same thing in one place.
120
+ */
121
+ export declare const mscoaConfigSchema: s.ObjectType<{
122
+ accountingBasis: import("@hashbrownai/core/src/schema/base").SchemaForUnion<AccountingBasis | null>;
123
+ segments: s.ArrayType<s.ObjectType<{
124
+ segment: s.StringType;
125
+ customSegment: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
126
+ label: s.StringType;
127
+ readOnly: s.BooleanType;
128
+ singleSelect: s.BooleanType;
129
+ additionalAccounts: s.ArrayType<s.StringType>;
130
+ inheritValueFromAccrual: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
131
+ vatSelectonActive: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
132
+ segmentExtension: s.BooleanType;
133
+ }>>;
134
+ cashSegments: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
135
+ segment: string;
136
+ customSegment: string | null;
137
+ label: string;
138
+ readOnly: boolean;
139
+ singleSelect: boolean;
140
+ additionalAccounts: string[];
141
+ inheritValueFromAccrual: boolean | null;
142
+ vatSelectonActive: boolean | null;
143
+ segmentExtension: boolean;
144
+ }[] | null>;
145
+ accountValueLabel: s.EnumType<string[]>;
146
+ showAllSegments: s.BooleanType;
147
+ extensionAccountsForSegments: s.ArrayType<s.StringType>;
148
+ dualCashExclusion: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
149
+ rules: {
150
+ pattern: string;
151
+ flags: string | null;
152
+ matchField: string | null;
153
+ segment: string | null;
154
+ description: string | null;
155
+ }[] | null;
156
+ matchMode: "any" | "all" | null;
157
+ } | null>;
158
+ }>;
159
+ /**
160
+ * The schema produces the draft it declares.
161
+ *
162
+ * Same shape of check as `_NoDraftDrift` in `input-members.ts`, and there for
163
+ * the same reason: the expansion from draft to `IScoaInputConfig` belongs to
164
+ * the caller, but the draft itself stays pinned to the node that generates it.
165
+ * Loosen a member below and this fails here rather than three layers away.
166
+ */
167
+ export type _NoMscoaDraftDrift = AssertNever<[
168
+ s.Infer<typeof mscoaConfigSchema>
169
+ ] extends [MscoaConfigDraft] ? never : 'mscoaConfig'>;
170
+ /**
171
+ * Members of a generated `mscoaConfig` the application must supply before Joi
172
+ * will accept it.
173
+ *
174
+ * `inputs` is the notable one: `MscoaInputConfigSchema` REQUIRES it, and the
175
+ * model is never asked for it, so a draft that is not expanded fails
176
+ * validation rather than merely losing a detail. An empty array satisfies it —
177
+ * the segments' inner inputs are generated from the segments themselves.
178
+ *
179
+ * `dualCashExclusion.rules[].id` is deliberately absent from this list: Joi
180
+ * marks that one optional, so leaving it unset is valid.
181
+ */
182
+ export declare const MSCOA_DRAFT_COMPLETION: readonly ["mscoaConfig.inputs (application supplies; an empty array is valid)", "mscoaConfig.segments[].id", "mscoaConfig.cashSegments[].id"];
@@ -0,0 +1,129 @@
1
+ import { s } from '@hashbrownai/core';
2
+ import { MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS } from '../../interfaces/FormBuilder/inputConfig/ElementEditConfig.js';
3
+ import { exhaustive, optional } from '../internal/coupling.js';
4
+ import { describe, describeIn } from '../internal/editor-guidance.js';
5
+ import { accountingBasisSchema } from '../shared-types.js';
6
+ // --- the account-label pool -------------------------------------------------
7
+ /**
8
+ * The fields an account may be displayed by, read off the option pool the
9
+ * unified MSCOA setup editor renders for `accountValueLabel`.
10
+ */
11
+ const ACCOUNT_VALUE_LABEL_ENTRIES = MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS.map((option) => option.value);
12
+ /** Compares a label to its value ignoring case, spacing and punctuation. */
13
+ const flatten = (text) => text.replace(/[^a-z0-9]/gi, '').toLowerCase();
14
+ /**
15
+ * The handful of entries whose label says something their value does not.
16
+ *
17
+ * `internal/editor-guidance` rejects editor `options` as a description source
18
+ * on the evidence that the labels merely restate the values. That holds for
19
+ * most of this pool too — `SCOAAccount` is labelled "SCOA Account", `SA34B` is
20
+ * labelled "SA34B" — and those are dropped here rather than repeated. What is
21
+ * left is the residue that genuinely disambiguates: `AccountNumber` is the
22
+ * FULL account number and `AccountNumberShortened` is the one an administrator
23
+ * calls "the account number", which is not a distinction a model can recover
24
+ * from the two identifiers alone.
25
+ */
26
+ const ACCOUNT_VALUE_LABEL_GLOSS = MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS.filter((option) => flatten(option.label) !== flatten(option.value))
27
+ .map((option) => `${option.value} is "${option.label}"`)
28
+ .join('; ');
29
+ // --- schema -----------------------------------------------------------------
30
+ /**
31
+ * `DualCashExclusionMatchMode` is a string-union type with no runtime object to
32
+ * read, so its members are restated under {@link exhaustive} rather than
33
+ * derived — drop one and this stops compiling.
34
+ */
35
+ export const DUAL_CASH_MATCH_MODE_VALUES = exhaustive()(['any', 'all']);
36
+ /**
37
+ * One segment row: which slice of the chart of accounts the user picks from,
38
+ * and how that pick behaves.
39
+ *
40
+ * Shared by `segments` and `cashSegments` rather than split into two nodes.
41
+ * Two members are basis-specific (`inheritValueFromAccrual` is meaningless off
42
+ * the cash side, `vatSelectonActive` off the ITEM segment), and Skillet cannot
43
+ * express conditional applicability structurally — so, as with `min` and `max`
44
+ * on the input members, the condition is stated in the description where it can
45
+ * still stop a model setting a flag that cannot mean anything.
46
+ */
47
+ const segmentSchema = s.object('One segment of the chart of accounts the user picks an account from.', {
48
+ segment: s.string('The segment key exactly as the chart of accounts spells it, upper case — ITEM, FUNCTION, FUND, PROJECT, REGION and so on. The real list comes from the loaded account tree, so use the key the municipality actually publishes rather than inventing a plausible one. On an extension row this names the segment being extended.'),
49
+ customSegment: optional(s.string('The upper-cased key an extension row stores its accounts under, derived from the label (a row labelled "Retention" is keyed RETENTION). Leave null on an ordinary segment, which is keyed by its segment key instead.')),
50
+ label: s.string('The heading shown above this row in the account chart, in title case. Name what the user is choosing, not the segment code.'),
51
+ readOnly: s.boolean('Whether the row is displayed but cannot be changed by the user. Set this on a segment whose account is fixed by policy or copied from elsewhere.'),
52
+ singleSelect: s.boolean('True when the row captures one account only. False adds a second account to every row in this table, so the user picks a debit and a credit pair. ITEM is the only segment that carries two legs; every other segment holds the same account on both sides. So set it false on ITEM alone, and only when the form genuinely captures both sides.'),
53
+ additionalAccounts: s.array('Extra account columns shown on this row, named exactly as they appear in extensionAccountsForSegments on this same configuration. A name that is not in that list renders nothing. Leave empty when the row needs no extra columns.', s.string('An extension-account name declared in extensionAccountsForSegments.')),
54
+ inheritValueFromAccrual: optional(s.boolean('Cash rows only: copies the account selected on the matching accrual row instead of asking again. Leave null on an accrual segment.')),
55
+ // The member really is spelled `vatSelectonActive` in the stored shape. It
56
+ // is not corrected here: renaming it would silently drop the flag on every
57
+ // form already persisted with it.
58
+ vatSelectonActive: optional(s.boolean('Adds the VAT-status column to the account chart. It applies to the ITEM segment, which is the only one carrying a VAT treatment; leave null elsewhere.')),
59
+ segmentExtension: s.boolean('True for an extra level authored under a segment rather than a segment of the published chart. An extension row is keyed by customSegment and may reuse a segment key another row already claims; an ordinary row may not.'),
60
+ });
61
+ /**
62
+ * One cash-exclusion rule.
63
+ *
64
+ * These are the only members here with per-property guidance to draw on: the
65
+ * `mscoaConfig` row opens a composite editor, and its `secondaryElementEditorConfig`
66
+ * carries a hand-written hint for each of `pattern`, `flags`, `matchField`,
67
+ * `segment` and `description`. {@link describeIn} reads them from that secondary editor
68
+ * rather than from the top-level index — see `internal/editor-guidance` for why
69
+ * those rows are indexed under the row that opens them instead of being
70
+ * prefixed with its path.
71
+ */
72
+ const dualCashExclusionRuleSchema = s.object('One condition under which the cash side is not required.', {
73
+ pattern: s.string(describeIn('A JavaScript regular expression, written without the surrounding slashes, tested against each selected accrual account.', ['mscoaConfig'], 'pattern')),
74
+ flags: optional(s.string(describeIn('Regular-expression flags for the pattern above. Leave null for an exact-case match.', ['mscoaConfig'], 'flags'))),
75
+ matchField: optional(s.enumeration(describeIn('Overrides, for THIS rule only, the labelled field the pattern is tested against; accountValueLabel above is the default. Set it when the condition keys off something the control does not display — a description field, say, on a control that shows account numbers. Leave null otherwise, which is the usual case.', ['mscoaConfig'], 'matchField'), ACCOUNT_VALUE_LABEL_ENTRIES)),
76
+ segment: optional(s.string(describeIn('Restricts the test to the accounts chosen for one accrual segment, named by its upper-cased segment key. It must be a segment this configuration actually declares — a scope nothing declares can never match. Leave null to test every accrual segment.', ['mscoaConfig'], 'segment'))),
77
+ description: optional(s.string(describeIn('A note for administrators explaining the business intent of this condition. It is never shown to the person filling in the form.', ['mscoaConfig'], 'description'))),
78
+ });
79
+ /**
80
+ * The `mscoaConfig` schema.
81
+ *
82
+ * A plain const, not a factory: nothing in here is chosen from candidates the
83
+ * caller holds. The one member that draws on a fixed pool — `accountValueLabel`
84
+ * — reads that pool out of this package at evaluation.
85
+ *
86
+ * ## Where the editor guidance is attached, and where it is not
87
+ *
88
+ * There is exactly ONE editor row for this whole object, at path
89
+ * `['mscoaConfig']`: a composite `MscoaConfig` editor that replaced eight
90
+ * separate rows (accountingBasis, accountValueLabel, showAllSegments, segments,
91
+ * cashSegments, the two dual-cash members and extensionAccountsForSegments).
92
+ * So {@link describe} is called once, on the object the row actually edits, and
93
+ * the members below carry authored descriptions only.
94
+ *
95
+ * That is a deliberate choice rather than an oversight. Calling
96
+ * `describe(base, 'mscoaConfig')` on each member compiles and would append the
97
+ * same forty-word hint — ending in "Opens a guided setup", which is advice for
98
+ * someone looking at a settings panel — to all seven of them. Layering it once,
99
+ * on the composite, says the same thing in one place.
100
+ */
101
+ export const mscoaConfigSchema = s.object(describe('How this control asks the user to select a municipal SCOA account.', 'mscoaConfig'), {
102
+ accountingBasis: optional(accountingBasisSchema),
103
+ segments: s.array('The accrual segments the user picks an account for, in the order they should appear. At least one is required, and this is the list the control is built from — an empty one gives the user nothing to choose. A budget capture usually asks for all seven segments of the chart.', segmentSchema, { minItems: 1 }),
104
+ cashSegments: optional(s.array('The segments captured on the cash side. Only meaningful when the accounting basis is cash or dual; leave null for an accrual-only input. A cash row that mirrors an accrual one should normally set inheritValueFromAccrual rather than ask the user twice.', segmentSchema)),
105
+ accountValueLabel: s.enumeration(`Which field of the account identifies it to the user, everywhere this control shows one. Choose the field an administrator would read back in the chart of accounts. ${ACCOUNT_VALUE_LABEL_GLOSS}.`, ACCOUNT_VALUE_LABEL_ENTRIES),
106
+ showAllSegments: s.boolean('True reflects every segment of the loaded account tree, read-only, instead of only the ones listed here — the application reconciles the list against the tree, so segments authored here are kept but not exclusive. False shows exactly the segments listed and nothing else, which is the usual choice.'),
107
+ extensionAccountsForSegments: s.array('Names of extra account columns this control offers on top of the ordinary account, each becoming a column on the chart. A segment opts into one by naming it in its own additionalAccounts. Leave empty when the control captures ordinary accounts only.', s.string('An extension-account name, such as a counter-account this form also captures.')),
108
+ dualCashExclusion: optional(s.object('Dual basis only: when the cash side is NOT required, despite the basis asking for both. Leave null to require both sides always, which is the default behaviour and the right answer unless the form has a stated exception.', {
109
+ rules: optional(s.array('The conditions tested against the accounts the user selected on the accrual side. A rule whose pattern does not compile never matches, so it can never suppress the cash side by accident.', dualCashExclusionRuleSchema)),
110
+ matchMode: optional(s.enumeration("How the rules combine into one verdict: 'any' suppresses cash as soon as one rule matches, 'all' only when every rule does. Absent behaves as 'any'.", [...DUAL_CASH_MATCH_MODE_VALUES])),
111
+ })),
112
+ });
113
+ /**
114
+ * Members of a generated `mscoaConfig` the application must supply before Joi
115
+ * will accept it.
116
+ *
117
+ * `inputs` is the notable one: `MscoaInputConfigSchema` REQUIRES it, and the
118
+ * model is never asked for it, so a draft that is not expanded fails
119
+ * validation rather than merely losing a detail. An empty array satisfies it —
120
+ * the segments' inner inputs are generated from the segments themselves.
121
+ *
122
+ * `dualCashExclusion.rules[].id` is deliberately absent from this list: Joi
123
+ * marks that one optional, so leaving it unset is valid.
124
+ */
125
+ export const MSCOA_DRAFT_COMPLETION = [
126
+ 'mscoaConfig.inputs (application supplies; an empty array is valid)',
127
+ 'mscoaConfig.segments[].id',
128
+ 'mscoaConfig.cashSegments[].id',
129
+ ];
@@ -0,0 +1,59 @@
1
+ import { s } from '@hashbrownai/core';
2
+ /**
3
+ * The `paginationSelectionConfig` member: how a paged selection table lists and
4
+ * identifies its records.
5
+ *
6
+ * ## This member has no interface behind it
7
+ *
8
+ * Unusually, nothing in this package declares `paginationSelectionConfig` on a
9
+ * form input. It exists as a key in `AllFormInputPrimaryKeys`, and as
10
+ * `paginationSelectionConfigSchema` in `schemas/FormInputSchema.ts`, but no
11
+ * member of the `FormColumnInputs` intersection has that name — which is why it
12
+ * falls under `UnbackedInputMember` in `input-members.ts`.
13
+ *
14
+ * That matters here rather than being trivia: the `_NoValueDrift` assertion
15
+ * resolves an unbacked key to `unknown`, and everything is assignable to
16
+ * `unknown`, so the compiler cannot check this node against anything. The Joi
17
+ * schema is therefore the ONLY thing holding it, and it is followed literally.
18
+ * A description that drifts from `paginationSelectionConfigSchema` will not
19
+ * fail the build the way the backed members do; it will fail validation at
20
+ * runtime.
21
+ *
22
+ * ## Why this is a draft
23
+ *
24
+ * Each column carries a required `id`, a builder-assigned row key, handled as
25
+ * every other assigned identifier is: omitted here and stamped on expansion.
26
+ *
27
+ * `primaryIdentifierKey` is omitted outright. It is an array of key-tree nodes
28
+ * built by the settings panel from a real sample document, and the editor row
29
+ * says an unset value means `_id` is used — so an absent one is both authorable
30
+ * and correct, whereas a model inventing tree nodes is neither.
31
+ */
32
+ export interface PaginationColumnDraft {
33
+ key: string;
34
+ label: string;
35
+ }
36
+ export interface PaginationSelectionConfigDraft {
37
+ useLocalPagination: boolean | null;
38
+ columns: PaginationColumnDraft[];
39
+ }
40
+ export declare const paginationSelectionConfigDraftSchema: s.ObjectType<{
41
+ useLocalPagination: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
42
+ columns: s.ArrayType<s.ObjectType<{
43
+ key: s.StringType;
44
+ label: s.StringType;
45
+ }>>;
46
+ }>;
47
+ /**
48
+ * The node produces the draft it declares.
49
+ *
50
+ * This is the only compile-time check available for this member — see the note
51
+ * above about it having no interface to be checked against.
52
+ */
53
+ export type _NoPaginationDraftDrift = [
54
+ s.Infer<typeof paginationSelectionConfigDraftSchema>
55
+ ] extends [PaginationSelectionConfigDraft] ? never : [
56
+ 'paginationSelectionConfigDraftSchema drifted from PaginationSelectionConfigDraft'
57
+ ];
58
+ /** Members of a generated config the application must supply. */
59
+ export declare const PAGINATION_DRAFT_COMPLETION: readonly ["paginationSelectionConfig.columns[].id"];