jsonisch 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/form-ref-BEri3JKv.js +1123 -0
- package/dist/form-ref-D_xHSaqL.d.ts +994 -0
- package/dist/handle-submit-CD3WY9gW.js +208 -0
- package/dist/handle-submit-Dk7QW3_q.d.ts +34 -0
- package/dist/index.d.ts +724 -0
- package/dist/index.js +1521 -0
- package/dist/plugin/index.d.ts +188 -0
- package/dist/plugin/index.js +4 -0
- package/dist/plugin-BCsAE8RK.d.ts +612 -0
- package/dist/plugin-Qka-vaO1.js +1545 -0
- package/dist/react/index.d.ts +476 -0
- package/dist/react/index.js +456 -0
- package/docs/decode-fork.md +24 -0
- package/docs/plugin-lifecycle.md +44 -0
- package/docs/rerender-history.md +61 -0
- package/docs/wire-shapes.md +70 -0
- package/package.json +87 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { D as InternalFieldStore, F as ReadonlySignal, G as JsonSchema, I as Signal, K as Path, a as InternalFormStore, b as PluginKey, m as JsonischPlugin, t as FormRef, y as FieldSlotKey } from "./form-ref-D_xHSaqL.js";
|
|
2
|
+
|
|
3
|
+
//#region src/core/types/derivation.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The result of parsing a formula expression: the engine's opaque AST node,
|
|
7
|
+
* or a parse error. Shaped so a typical engine's parse result passes
|
|
8
|
+
* through unchanged — jsonisch itself never depends on the engine.
|
|
9
|
+
*/
|
|
10
|
+
type CalcParseResult = {
|
|
11
|
+
readonly ok: true;
|
|
12
|
+
readonly node: unknown;
|
|
13
|
+
} | {
|
|
14
|
+
readonly ok: false;
|
|
15
|
+
readonly error: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The injected calc engine, supplied by the host. Method syntax on
|
|
19
|
+
* purpose: bivariant parameters let the engine's concrete AST type satisfy
|
|
20
|
+
* the `unknown` node without an adapter layer —
|
|
21
|
+
* `{ parse: parseFormula, evaluate: evaluateFormula, extractDependencies,
|
|
22
|
+
* extractPathRefs }` passes through unchanged.
|
|
23
|
+
*/
|
|
24
|
+
interface CalcEngine {
|
|
25
|
+
/**
|
|
26
|
+
* Parses a formula expression into an opaque AST node. Called once per
|
|
27
|
+
* `x-formula` at store init.
|
|
28
|
+
*/
|
|
29
|
+
parse(formula: string): CalcParseResult;
|
|
30
|
+
/**
|
|
31
|
+
* Evaluates a parsed node against a flat values scope. May throw (the
|
|
32
|
+
* `#ERROR` contract) — the derivation layer contains the throw to the
|
|
33
|
+
* field's own errors channel.
|
|
34
|
+
*/
|
|
35
|
+
evaluate(node: unknown, scope: Record<string, unknown>): unknown;
|
|
36
|
+
/**
|
|
37
|
+
* Extracts the identifiers a formula reads: plain field refs plus
|
|
38
|
+
* collection names of aggregate refs (`SUM(assets[aiv])` → `assets`).
|
|
39
|
+
* Defines both the dep-graph edges and the eval-scope keys.
|
|
40
|
+
*/
|
|
41
|
+
extractDependencies(node: unknown): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Extracts `collection[field]` reference pairs. Optional: when present, a
|
|
44
|
+
* formula with at least one such reference is classified a rollup (the
|
|
45
|
+
* stricter-persistence set). A scalar root-record ref (`record[…]`) uses the
|
|
46
|
+
* same syntax and currently also flags — shape-aware refinement belongs
|
|
47
|
+
* to the persistence-rule slice.
|
|
48
|
+
*/
|
|
49
|
+
extractPathRefs?(node: unknown): ReadonlyArray<{
|
|
50
|
+
readonly collection: string;
|
|
51
|
+
readonly field: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The mode of a formula field that accepts an estimate: `estimate` holds
|
|
56
|
+
* the provisional manually-entered value (the field's own input signal);
|
|
57
|
+
* `formula` computes. Estimate-first chronology — the formula supersedes
|
|
58
|
+
* the estimate, never "overrides" it. Wire-compatible with the legacy
|
|
59
|
+
* `<key>Source` companion's `manual` (→ `estimate`) / `formula` modes; the
|
|
60
|
+
* companion decode itself is a later slice.
|
|
61
|
+
*/
|
|
62
|
+
type DerivationMode = "estimate" | "formula";
|
|
63
|
+
/**
|
|
64
|
+
* The output of a formula field's derived signal. `error` is the calc
|
|
65
|
+
* error message (`null` when the evaluation succeeded); it feeds the
|
|
66
|
+
* field's composed `errors` channel, where `#ERROR` rendering picks it up.
|
|
67
|
+
*/
|
|
68
|
+
interface DerivedState {
|
|
69
|
+
readonly value: unknown;
|
|
70
|
+
readonly error: string | null;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/plugins/envelopes/types.d.ts
|
|
74
|
+
/**
|
|
75
|
+
* The persisted meta half of an estimate field. Wire `mode` uses the
|
|
76
|
+
* settled names (`estimate` / `formula`). `manualValue` preserves the
|
|
77
|
+
* typed estimate across a mode flip; `lastFlippedAt` is stamped only when
|
|
78
|
+
* the mode actually changes. Server recompute may write a PARTIAL meta
|
|
79
|
+
* (`{ mode: "formula" }` only) — every reader tolerates that.
|
|
80
|
+
*/
|
|
81
|
+
interface EstimateMeta {
|
|
82
|
+
mode?: DerivationMode;
|
|
83
|
+
manualValue?: unknown;
|
|
84
|
+
lastFlippedAt?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The persisted meta half of an amount-or-percent field. Wire `mode` is
|
|
88
|
+
* `amount` | `percent`; `basis` is the percent-of field key. The meta
|
|
89
|
+
* holds ONLY entry state — the value half is always the resolved dollar
|
|
90
|
+
* amount.
|
|
91
|
+
*/
|
|
92
|
+
interface EntryMeta {
|
|
93
|
+
mode?: EntryMode;
|
|
94
|
+
basis?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The entry mode of an amount-or-percent field: enter a dollar `amount`,
|
|
98
|
+
* or a `percent` of the percent basis.
|
|
99
|
+
*/
|
|
100
|
+
type EntryMode = "amount" | "percent";
|
|
101
|
+
/**
|
|
102
|
+
* In-memory estimate envelope (PR #553 kind union). `mode` is omitted
|
|
103
|
+
* when nothing was ever persisted — encode must not fabricate a pin.
|
|
104
|
+
*/
|
|
105
|
+
interface EstimateEnvelope {
|
|
106
|
+
readonly kind: "estimate";
|
|
107
|
+
readonly value?: unknown;
|
|
108
|
+
readonly mode?: DerivationMode;
|
|
109
|
+
readonly manualValue?: unknown;
|
|
110
|
+
readonly lastFlippedAt?: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* In-memory amount-or-percent envelope. `mode` / `basis` are resolved
|
|
114
|
+
* (schema default applied) so dirty compare is `!==`, not re-defaulting.
|
|
115
|
+
*/
|
|
116
|
+
interface AmountOrPercentEnvelope {
|
|
117
|
+
readonly kind: "amount-or-percent";
|
|
118
|
+
readonly value?: unknown;
|
|
119
|
+
readonly mode: EntryMode;
|
|
120
|
+
readonly basis?: string;
|
|
121
|
+
}
|
|
122
|
+
type Envelope = EstimateEnvelope | AmountOrPercentEnvelope;
|
|
123
|
+
/**
|
|
124
|
+
* The envelope slot of an estimate field (the `estimate` family). The mode
|
|
125
|
+
* signal is a computed over `envelope.mode` — write via `setMode` (the
|
|
126
|
+
* flip API), never by assigning `mode`.
|
|
127
|
+
*/
|
|
128
|
+
interface EstimateSlot {
|
|
129
|
+
readonly family: "estimate";
|
|
130
|
+
/**
|
|
131
|
+
* Live envelope. Written only by `writeEnvelope`.
|
|
132
|
+
*/
|
|
133
|
+
readonly envelope: Signal<EstimateEnvelope>;
|
|
134
|
+
/**
|
|
135
|
+
* Dirty baseline / reset target. Reassigned on rebase and on
|
|
136
|
+
* `reset({ initialInput })`.
|
|
137
|
+
*/
|
|
138
|
+
readonly startEnvelope: Signal<EstimateEnvelope>;
|
|
139
|
+
/**
|
|
140
|
+
* Resolved estimate/formula mode. An unpinned empty field follows
|
|
141
|
+
* `x-estimate-default-mode`; a stored unpinned value stays `estimate`.
|
|
142
|
+
*/
|
|
143
|
+
readonly mode: ReadonlySignal<DerivationMode>;
|
|
144
|
+
/**
|
|
145
|
+
* The estimate value preserved when the mode flipped to formula this
|
|
146
|
+
* session (wire `manualValue` carry).
|
|
147
|
+
*/
|
|
148
|
+
readonly manualValue: ReadonlySignal<unknown>;
|
|
149
|
+
/**
|
|
150
|
+
* The flip timestamp stamped this session, or the decoded one.
|
|
151
|
+
*/
|
|
152
|
+
readonly lastFlippedAt: ReadonlySignal<string | undefined>;
|
|
153
|
+
/**
|
|
154
|
+
* Whether the meta half must serialize: the mode changed, or the
|
|
155
|
+
* estimate value did (an estimate keystroke dirties the meta with it).
|
|
156
|
+
*/
|
|
157
|
+
readonly isDirty: ReadonlySignal<boolean>;
|
|
158
|
+
/**
|
|
159
|
+
* The identity-stable react callbacks the plugin's `fieldSnapshot`
|
|
160
|
+
* contributes, created lazily on first snapshot — a fresh closure per
|
|
161
|
+
* snapshot would defeat the equality gate and re-render every
|
|
162
|
+
* notification. Bound to form + path, which is safe because stores are
|
|
163
|
+
* position-fixed (array ops move values, not stores).
|
|
164
|
+
*/
|
|
165
|
+
callbacks?: {
|
|
166
|
+
readonly setMode: (mode: DerivationMode) => void;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The envelope slot of an amount-or-percent field (the `amount-or-percent` family).
|
|
171
|
+
* Value edits never dirty the meta — only entry-state changes do.
|
|
172
|
+
*/
|
|
173
|
+
interface AmountOrPercentSlot {
|
|
174
|
+
readonly family: "amount-or-percent";
|
|
175
|
+
/**
|
|
176
|
+
* Live envelope. Written only by `writeEnvelope`.
|
|
177
|
+
*/
|
|
178
|
+
readonly envelope: Signal<AmountOrPercentEnvelope>;
|
|
179
|
+
/**
|
|
180
|
+
* Dirty baseline / reset target. Reassigned on rebase and on
|
|
181
|
+
* `reset({ initialInput })`.
|
|
182
|
+
*/
|
|
183
|
+
readonly startEnvelope: Signal<AmountOrPercentEnvelope>;
|
|
184
|
+
/**
|
|
185
|
+
* The current entry mode. An unpinned empty field follows
|
|
186
|
+
* `x-hybrid-default-mode`; a stored unpinned value stays `amount`.
|
|
187
|
+
*/
|
|
188
|
+
readonly entryMode: ReadonlySignal<EntryMode>;
|
|
189
|
+
/**
|
|
190
|
+
* The current percent basis (a root-level field key), or `undefined` when the
|
|
191
|
+
* schema declares no default and none was stored.
|
|
192
|
+
*/
|
|
193
|
+
readonly percentBasis: ReadonlySignal<string | undefined>;
|
|
194
|
+
/**
|
|
195
|
+
* Whether the meta half must serialize (entry mode or basis changed).
|
|
196
|
+
*/
|
|
197
|
+
readonly isDirty: ReadonlySignal<boolean>;
|
|
198
|
+
/**
|
|
199
|
+
* The identity-stable react callbacks (see `EstimateSlot.callbacks`).
|
|
200
|
+
*/
|
|
201
|
+
callbacks?: {
|
|
202
|
+
readonly setEntryMode: (mode: EntryMode) => void;
|
|
203
|
+
readonly setPercentBasis: (percentBasis: string) => void;
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The envelope slot a value field may carry: meta state that is
|
|
208
|
+
* dirty-tracked and serialized by the envelopes plugin, never rendered as
|
|
209
|
+
* a field.
|
|
210
|
+
*/
|
|
211
|
+
type EnvelopeSlot = EstimateSlot | AmountOrPercentSlot;
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/core/form/validate-form-input.d.ts
|
|
214
|
+
/**
|
|
215
|
+
* Configuration for validating the form input.
|
|
216
|
+
*/
|
|
217
|
+
interface ValidateFormInputConfig {
|
|
218
|
+
/**
|
|
219
|
+
* Whether to focus the first field with an error.
|
|
220
|
+
*/
|
|
221
|
+
readonly shouldFocus?: boolean | undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The result of validating the form input.
|
|
225
|
+
*/
|
|
226
|
+
interface ValidationResult {
|
|
227
|
+
/**
|
|
228
|
+
* Whether the input passed validation.
|
|
229
|
+
*/
|
|
230
|
+
readonly success: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* The validated form input (the current tree input — AJV does not
|
|
233
|
+
* transform).
|
|
234
|
+
*/
|
|
235
|
+
readonly output: unknown;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Validates the form input using the injected validator. Runs the validator
|
|
239
|
+
* against the current tree input, routes each issue's `instancePath` to its
|
|
240
|
+
* field store's `errors` signal (accumulating multiple issues per field,
|
|
241
|
+
* clearing every other field), and optionally focuses the first field with
|
|
242
|
+
* an error. A form without a validator always validates successfully.
|
|
243
|
+
*
|
|
244
|
+
* @param internalFormStore The form store to validate.
|
|
245
|
+
* @param config The validation configuration.
|
|
246
|
+
*
|
|
247
|
+
* @returns The validation result.
|
|
248
|
+
*/
|
|
249
|
+
declare function validateFormInput(internalFormStore: InternalFormStore, config?: ValidateFormInputConfig): ValidationResult;
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/plugins/envelopes/plugin.d.ts
|
|
252
|
+
/**
|
|
253
|
+
* The envelopes plugin's state: one slot per estimate/amount-or-percent
|
|
254
|
+
* value store, keyed by store identity.
|
|
255
|
+
*/
|
|
256
|
+
type EnvelopeState = Map<InternalFieldStore, EnvelopeSlot>;
|
|
257
|
+
/**
|
|
258
|
+
* The envelopes plugin: owns the meta half of estimate and
|
|
259
|
+
* amount-or-percent fields — mode/entry state decoded from the kind
|
|
260
|
+
* envelope, dirty-tracked, and serialized by wrapping the field's own
|
|
261
|
+
* payload entry. No factory arguments: the envelope rides the field key,
|
|
262
|
+
* so every scope's raw value already carries the meta half (no envelope
|
|
263
|
+
* side-channel to decode).
|
|
264
|
+
*/
|
|
265
|
+
declare function envelopes(): JsonischPlugin<EnvelopeState>;
|
|
266
|
+
declare module "../../react/types" {
|
|
267
|
+
interface FieldStoreSlots {
|
|
268
|
+
/**
|
|
269
|
+
* The estimate/formula mode of an estimate field, `undefined`
|
|
270
|
+
* otherwise.
|
|
271
|
+
*/
|
|
272
|
+
readonly mode: DerivationMode | undefined;
|
|
273
|
+
/**
|
|
274
|
+
* Flips an estimate field's mode (the `setMode` method — seeds the
|
|
275
|
+
* estimate from the last formula result, stamps the meta half).
|
|
276
|
+
* Contributed only for estimate fields.
|
|
277
|
+
*/
|
|
278
|
+
readonly setMode: (mode: DerivationMode) => void;
|
|
279
|
+
/**
|
|
280
|
+
* The entry mode of an amount-or-percent field, `undefined` otherwise.
|
|
281
|
+
*/
|
|
282
|
+
readonly entryMode: EntryMode | undefined;
|
|
283
|
+
/**
|
|
284
|
+
* Sets an amount-or-percent field's entry mode (dirties the meta
|
|
285
|
+
* half). Contributed only for amount-or-percent fields.
|
|
286
|
+
*/
|
|
287
|
+
readonly setEntryMode: (mode: EntryMode) => void;
|
|
288
|
+
/**
|
|
289
|
+
* The percent basis of an amount-or-percent field (a root-level field key).
|
|
290
|
+
*/
|
|
291
|
+
readonly percentBasis: string | undefined;
|
|
292
|
+
/**
|
|
293
|
+
* Sets an amount-or-percent field's percent basis (dirties the meta
|
|
294
|
+
* half). Contributed only for amount-or-percent fields.
|
|
295
|
+
*/
|
|
296
|
+
readonly setPercentBasis: (percentBasis: string) => void;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/plugins/derivation/key.d.ts
|
|
301
|
+
/**
|
|
302
|
+
* The derivation slot of a formula/estimate value store.
|
|
303
|
+
*/
|
|
304
|
+
interface DerivationSlot {
|
|
305
|
+
/**
|
|
306
|
+
* The derived output dependents chain through and the field displays: a
|
|
307
|
+
* computed over the deps' input signals + `offFormValues`, resolved
|
|
308
|
+
* through the single scope path — mode-aware: an estimate pin holds the
|
|
309
|
+
* field's own input. NEVER written back into `input` — derived values
|
|
310
|
+
* are outputs, excluded from dirty and payload by construction.
|
|
311
|
+
*/
|
|
312
|
+
readonly derived: ReadonlySignal<DerivedState>;
|
|
313
|
+
/**
|
|
314
|
+
* The always-computed formula result, IGNORING the estimate pin — the
|
|
315
|
+
* candidate value the estimate wrapper's nudge compares against and the
|
|
316
|
+
* seed for a formula→estimate flip. The same signal object as `derived`
|
|
317
|
+
* on a plain formula field.
|
|
318
|
+
*/
|
|
319
|
+
readonly formulaValue: ReadonlySignal<DerivedState>;
|
|
320
|
+
/**
|
|
321
|
+
* Whether the formula references a collection (`SUM(assets[…])`) —
|
|
322
|
+
* classified statically at wire time. Marks the stricter-persistence
|
|
323
|
+
* set; carries no behavior in this slice.
|
|
324
|
+
*/
|
|
325
|
+
readonly isRollup: boolean;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* The derivation plugin's slot key. The scope resolvers and the react
|
|
329
|
+
* surface read derived state through this identity.
|
|
330
|
+
*/
|
|
331
|
+
declare const derivationKey: FieldSlotKey<DerivationSlot>;
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/plugins/derivation/plugin.d.ts
|
|
334
|
+
/**
|
|
335
|
+
* The derivation plugin's state: one slot per formula/estimate value
|
|
336
|
+
* store, keyed by store identity.
|
|
337
|
+
*/
|
|
338
|
+
type DerivationState = Map<InternalFieldStore, DerivationSlot>;
|
|
339
|
+
/**
|
|
340
|
+
* The derivation plugin: parses every `x-formula` once per scope (the
|
|
341
|
+
* document root and each array row — it is scope, not machinery, that
|
|
342
|
+
* differs; LOS-596), breaks dependency cycles deterministically, and gives
|
|
343
|
+
* each formula field its `formulaValue`/`derived` slot plus the composed
|
|
344
|
+
* `errors` channel.
|
|
345
|
+
*
|
|
346
|
+
* Scope resolution lives HERE and only here (the LOS-514 closure — no
|
|
347
|
+
* other read path exists): per dependency, the form value wins (a formula
|
|
348
|
+
* dep resolves through its own derived signal, chaining fresh values),
|
|
349
|
+
* `offFormValues` fills only `undefined`, and when both sides are arrays
|
|
350
|
+
* the canonical rows merge with the live form rows (the collection
|
|
351
|
+
* overlay for aggregate refs).
|
|
352
|
+
*
|
|
353
|
+
* Declares `dependsOn: [envelopesKey]` — the estimate pin reads the mode
|
|
354
|
+
* signal the envelopes plugin creates; without it the pin would silently
|
|
355
|
+
* never engage and a manually pinned value would be overwritten by the
|
|
356
|
+
* next recompute (the D2 data-loss scenario), so a missing envelopes
|
|
357
|
+
* plugin is a startup error instead.
|
|
358
|
+
*/
|
|
359
|
+
declare function derivation(engine: CalcEngine): JsonischPlugin<DerivationState>;
|
|
360
|
+
declare module "../../react/types" {
|
|
361
|
+
interface FieldStoreSlots {
|
|
362
|
+
/**
|
|
363
|
+
* The mode-aware derived output of a formula/estimate field (what the
|
|
364
|
+
* field displays; an estimate pin holds the input). `undefined` on
|
|
365
|
+
* non-derived fields or without the derivation plugin.
|
|
366
|
+
*/
|
|
367
|
+
readonly derived: DerivedState | undefined;
|
|
368
|
+
/**
|
|
369
|
+
* The always-computed formula result of a formula/estimate field,
|
|
370
|
+
* ignoring the estimate pin — the nudge's candidate value.
|
|
371
|
+
*/
|
|
372
|
+
readonly formulaValue: DerivedState | undefined;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/plugins/visibility/plugin.d.ts
|
|
377
|
+
/**
|
|
378
|
+
* The visibility plugin's key. It keeps no state (rules and computeds live
|
|
379
|
+
* on the field stores' base `visibleWhen`/`visible` members).
|
|
380
|
+
*/
|
|
381
|
+
declare const visibilityKey: PluginKey<null>;
|
|
382
|
+
/**
|
|
383
|
+
* The visibility plugin: every field gated by an `allOf` `if/then/else`
|
|
384
|
+
* block on its OWN scope's schema gets a `visible` computed signal over the
|
|
385
|
+
* watched field's resolved value. Two scope kinds (LOS-722 extended
|
|
386
|
+
* visibility into rows — the spec's open question 3):
|
|
387
|
+
*
|
|
388
|
+
* - the root scope, whose rules come from the form schema's `allOf`;
|
|
389
|
+
* - an array-row scope, whose rules come from the ITEMS schema's `allOf`
|
|
390
|
+
* (`buildNestedFieldItems` carries the related dataset's conditionals),
|
|
391
|
+
* evaluated against THAT row's values — row A's trigger never gates
|
|
392
|
+
* row B's field.
|
|
393
|
+
*
|
|
394
|
+
* Visibility gates RENDERING only: a hidden field keeps its state, stays
|
|
395
|
+
* in the dirty diff, and rides the payload. Fields without a rule get no
|
|
396
|
+
* signal — the public store reads that as always visible.
|
|
397
|
+
*
|
|
398
|
+
* Register AFTER derivation (array order): a WHEN watching a formula field
|
|
399
|
+
* resolves through its derived slot. No hard `dependsOn` — a form without
|
|
400
|
+
* a calc engine legitimately omits derivation, and the watched-value read
|
|
401
|
+
* falls back to the field's input.
|
|
402
|
+
*/
|
|
403
|
+
declare function visibility(): JsonischPlugin<null>;
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/plugins/bagger/compute-scope.d.ts
|
|
406
|
+
interface BaggerOptions {
|
|
407
|
+
bag?: string;
|
|
408
|
+
/**
|
|
409
|
+
* Second classification source for row collections, union-ed with the
|
|
410
|
+
* walked schema's own relation nodes (`collectionKeys`). The host passes
|
|
411
|
+
* its DATASET schema here: which record keys hold rows is a property of
|
|
412
|
+
* the record, not of the stage being rendered.
|
|
413
|
+
*/
|
|
414
|
+
collectionsSchema?: JsonSchema;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* `computeBag` is store-less, so the alias key arrives as an option here;
|
|
418
|
+
* the plugin threads `form.rootRecordAlias` through (the one home — the
|
|
419
|
+
* plugin deliberately has NO alias option of its own, so the alias writer
|
|
420
|
+
* and the row-scope reader can never disagree).
|
|
421
|
+
*/
|
|
422
|
+
interface ComputeBagOptions extends BaggerOptions {
|
|
423
|
+
rootRecordAlias?: string;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The record keys that hold relation ROWS, from both classification
|
|
427
|
+
* sources. The union is load-bearing in both directions:
|
|
428
|
+
*
|
|
429
|
+
* - `collectionsSchema` (the dataset schema) alone decides collections the
|
|
430
|
+
* walked schema does not configure. A stage that renders no assets tray
|
|
431
|
+
* still needs `assets` on the shelf as FLATTENED rows — left raw, every
|
|
432
|
+
* bag-resident key (`assignmentFee`) reads undefined per row and
|
|
433
|
+
* `SUM(assets[assignmentFee])` or a check silently computes 0.
|
|
434
|
+
* - The walked schema alone declares relations the dataset schema lacks:
|
|
435
|
+
* registry-role slugs and fallback defs are synthesized per stage
|
|
436
|
+
* (`buildStageSchemaFromConfig`), so replacing rather than union-ing
|
|
437
|
+
* would drop those collections off the shelf.
|
|
438
|
+
*/
|
|
439
|
+
declare function collectionKeys(schema: JsonSchema, collectionsSchema?: JsonSchema): Set<string>;
|
|
440
|
+
/**
|
|
441
|
+
* Merges a source row's `x-column` fields with its bag column — bag wins,
|
|
442
|
+
* per the `x-column` save-routing authority (AI.md: read = row spread over
|
|
443
|
+
* its `data`). INTACT merge only, no envelope unwrapping: a later task
|
|
444
|
+
* seeds form rows from this output, and unwrapping here would hand the
|
|
445
|
+
* store already-flattened envelope values, reproducing the LOS-461
|
|
446
|
+
* regression.
|
|
447
|
+
*/
|
|
448
|
+
declare function flattenSourceRow(row: Record<string, unknown>, bag: string): Record<string, unknown>;
|
|
449
|
+
/**
|
|
450
|
+
* Computes the off-form value bag for a canonical record: every declared
|
|
451
|
+
* scalar and relation collection, bag-merged and envelope-unwrapped, plus
|
|
452
|
+
* an alias entry holding the whole computed scope (so a formula or
|
|
453
|
+
* widget can address `record.termMonths` as readily as bare `termMonths`).
|
|
454
|
+
*/
|
|
455
|
+
declare function computeBag(schema: JsonSchema, record: Record<string, unknown>, opts?: ComputeBagOptions): Record<string, unknown>;
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/plugins/bagger/plugin.d.ts
|
|
458
|
+
/**
|
|
459
|
+
* The bagger plugin's state: the computed shelf plus an id-keyed row index
|
|
460
|
+
* per many:true relation field, used to seed a row's declared keys with
|
|
461
|
+
* their canonical baseline. Single-pick relations (`many: false`) hold a
|
|
462
|
+
* ref object, not a row collection, so they have no rows to index.
|
|
463
|
+
*/
|
|
464
|
+
interface BaggerState {
|
|
465
|
+
readonly scope: Record<string, unknown>;
|
|
466
|
+
readonly rowsByField: Map<string, Map<string, Record<string, unknown>>>;
|
|
467
|
+
}
|
|
468
|
+
declare const baggerKey: PluginKey<BaggerState>;
|
|
469
|
+
/**
|
|
470
|
+
* The bagger plugin: computes the off-form shelf once (`computeBag`) and
|
|
471
|
+
* owns `offFormValues`, then seeds every relation row's declared keys from
|
|
472
|
+
* its canonical row as an unedited baseline. Registration order:
|
|
473
|
+
* `[envelopes(), bagger(record), derivation?, visibility()]` — envelopes
|
|
474
|
+
* first so its slot already exists on every estimate/amount-or-percent leaf by the
|
|
475
|
+
* time seeding runs (`dependsOn: [envelopesKey]` enforces this at
|
|
476
|
+
* `createFormStore`).
|
|
477
|
+
*/
|
|
478
|
+
declare function bagger(record: Record<string, unknown>, opts?: BaggerOptions): JsonischPlugin<BaggerState>;
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/plugins/checks/types.d.ts
|
|
481
|
+
type Severity = "info" | "warning" | "error";
|
|
482
|
+
type SeverityConfig = Severity | "off";
|
|
483
|
+
interface Finding {
|
|
484
|
+
/** Instance id — the bespoke.check row id. */
|
|
485
|
+
readonly id: string;
|
|
486
|
+
/** Definition id — "formula", … */
|
|
487
|
+
readonly checkId: string;
|
|
488
|
+
readonly messageId?: string;
|
|
489
|
+
/** Already interpolated. */
|
|
490
|
+
readonly message: string;
|
|
491
|
+
/** Display title (instance options.name, else the definition id). */
|
|
492
|
+
readonly name: string;
|
|
493
|
+
/** From config. A check cannot set this. */
|
|
494
|
+
readonly severity: Severity;
|
|
495
|
+
/** Field this finding attaches to; `[]` means form-level. */
|
|
496
|
+
readonly path: Path;
|
|
497
|
+
}
|
|
498
|
+
interface CheckScope {
|
|
499
|
+
/**
|
|
500
|
+
* One key through the derivation closure (LOS-514): when BOTH the
|
|
501
|
+
* canonical side and the live side are arrays, mergeCollectionRows;
|
|
502
|
+
* otherwise live wins and offForm / plugin collections fill only
|
|
503
|
+
* `undefined`. A root-record ref like `record` is a plain object — get
|
|
504
|
+
* returns that object, never an empty array.
|
|
505
|
+
*/
|
|
506
|
+
get(key: string): unknown;
|
|
507
|
+
/**
|
|
508
|
+
* `get(collection)` when the value is an array, else `[]`. Never forces
|
|
509
|
+
* a non-array live value through the collection merge.
|
|
510
|
+
*/
|
|
511
|
+
rows(collection: string): readonly Readonly<Record<string, unknown>>[];
|
|
512
|
+
/**
|
|
513
|
+
* The whole bag. Frozen; building it subscribes to every root key.
|
|
514
|
+
*/
|
|
515
|
+
values(): Readonly<Record<string, unknown>>;
|
|
516
|
+
}
|
|
517
|
+
interface FindingDescriptor {
|
|
518
|
+
readonly messageId?: string;
|
|
519
|
+
readonly message?: string;
|
|
520
|
+
readonly data?: Readonly<Record<string, string | number>>;
|
|
521
|
+
/** Field(s) the finding attaches to. Empty/omitted → form-level. */
|
|
522
|
+
readonly paths?: readonly Path[];
|
|
523
|
+
}
|
|
524
|
+
interface CheckContext<Options = unknown> {
|
|
525
|
+
readonly id: string;
|
|
526
|
+
readonly checkId: string;
|
|
527
|
+
readonly options: Options;
|
|
528
|
+
readonly scope: CheckScope;
|
|
529
|
+
report(finding: FindingDescriptor): void;
|
|
530
|
+
}
|
|
531
|
+
interface CheckMeta<Options = unknown> {
|
|
532
|
+
readonly name: string;
|
|
533
|
+
readonly messages: Readonly<Record<string, string>>;
|
|
534
|
+
readonly optionsSchema?: Record<string, unknown> | false;
|
|
535
|
+
readonly defaultOptions?: Options;
|
|
536
|
+
}
|
|
537
|
+
interface CheckHandlers {
|
|
538
|
+
evaluate(): void;
|
|
539
|
+
}
|
|
540
|
+
interface CheckDefinition<Options = unknown> {
|
|
541
|
+
readonly meta: CheckMeta<Options>;
|
|
542
|
+
create(context: CheckContext<Options>): CheckHandlers;
|
|
543
|
+
}
|
|
544
|
+
interface CheckInstanceConfig<Options = unknown> {
|
|
545
|
+
readonly id: string;
|
|
546
|
+
readonly check: string;
|
|
547
|
+
readonly severity: SeverityConfig;
|
|
548
|
+
readonly options?: Options;
|
|
549
|
+
}
|
|
550
|
+
interface ChecksConfig {
|
|
551
|
+
readonly definitions: Readonly<Record<string, CheckDefinition<unknown>>>;
|
|
552
|
+
readonly instances: readonly CheckInstanceConfig[];
|
|
553
|
+
/**
|
|
554
|
+
* Canonical collection rows for CheckScope only (legacy host
|
|
555
|
+
* `evalCollections`). Must not be written onto the form's offForm shelf.
|
|
556
|
+
*/
|
|
557
|
+
readonly collections?: Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>;
|
|
558
|
+
}
|
|
559
|
+
interface FormulaOptions {
|
|
560
|
+
readonly formula: string;
|
|
561
|
+
readonly message?: string;
|
|
562
|
+
readonly name?: string;
|
|
563
|
+
readonly targetFieldKeys?: readonly string[];
|
|
564
|
+
}
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/plugins/checks/key.d.ts
|
|
567
|
+
interface ChecksState {
|
|
568
|
+
readonly findings: ReadonlySignal<readonly Finding[]>;
|
|
569
|
+
readonly hasBlockingFinding: ReadonlySignal<boolean>;
|
|
570
|
+
/**
|
|
571
|
+
* Per-instance finding computeds. `replaceCheckInstances` swaps this
|
|
572
|
+
* signal's value so live admin edits rebind without remounting the form.
|
|
573
|
+
*/
|
|
574
|
+
readonly instances: Signal<readonly ReadonlySignal<Finding[]>[]>;
|
|
575
|
+
/**
|
|
576
|
+
* Canonical collection rows for CheckScope (legacy `evalCollections`).
|
|
577
|
+
* Reactive so replace can refresh them with the instances.
|
|
578
|
+
*/
|
|
579
|
+
readonly collections: Signal<Readonly<Record<string, ReadonlyArray<Record<string, unknown>>>>>;
|
|
580
|
+
}
|
|
581
|
+
declare const checksKey: PluginKey<ChecksState>;
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/plugins/checks/formula.d.ts
|
|
584
|
+
/** Shared messageId for parse/eval failures (plugin catch + host mapping). */
|
|
585
|
+
declare const UNEVALUABLE_MESSAGE_ID = "unevaluable";
|
|
586
|
+
/**
|
|
587
|
+
* The one built-in check definition: a `bespoke.check` row is an *instance*
|
|
588
|
+
* of this, not a definition of its own (ANALYSIS-eslint.md §2.6).
|
|
589
|
+
*/
|
|
590
|
+
declare function formulaCheck(engine: CalcEngine): CheckDefinition<FormulaOptions>;
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/plugins/checks/plugin.d.ts
|
|
593
|
+
/**
|
|
594
|
+
* Checks plugin (LOS-605 / spec D8): eslint's contract on a form store.
|
|
595
|
+
* Findings are a parallel channel — they never merge into `errors`.
|
|
596
|
+
*
|
|
597
|
+
* Register after derivation so a check that reads a formula field resolves
|
|
598
|
+
* through its derived slot. Array order, not a hard `dependsOn` — a form
|
|
599
|
+
* without a calc engine legitimately omits derivation.
|
|
600
|
+
*
|
|
601
|
+
* Always register (even with zero instances). Live instance edits go through
|
|
602
|
+
* `replaceCheckInstances` so the host does not remount the form.
|
|
603
|
+
*/
|
|
604
|
+
declare function checks(config: ChecksConfig): JsonischPlugin<ChecksState>;
|
|
605
|
+
/**
|
|
606
|
+
* Re-registers check instances on a live form store (admin edits, late
|
|
607
|
+
* prop arrival) without remounting. Swaps the per-instance computeds the
|
|
608
|
+
* `findings` signal already reads.
|
|
609
|
+
*/
|
|
610
|
+
declare function replaceCheckInstances(form: FormRef, config: ChecksConfig): void;
|
|
611
|
+
//#endregion
|
|
612
|
+
export { EnvelopeState as A, EnvelopeSlot as B, flattenSourceRow as C, derivation as D, DerivationState as E, AmountOrPercentEnvelope as F, CalcParseResult as G, EstimateMeta as H, AmountOrPercentSlot as I, DerivationMode as K, EntryMeta as L, ValidateFormInputConfig as M, ValidationResult as N, DerivationSlot as O, validateFormInput as P, EntryMode as R, computeBag as S, visibilityKey as T, EstimateSlot as U, EstimateEnvelope as V, CalcEngine as W, bagger as _, ChecksState as a, ComputeBagOptions as b, CheckDefinition as c, ChecksConfig as d, Finding as f, BaggerState as g, SeverityConfig as h, formulaCheck as i, envelopes as j, derivationKey as k, CheckInstanceConfig as l, Severity as m, replaceCheckInstances as n, checksKey as o, FormulaOptions as p, DerivedState as q, UNEVALUABLE_MESSAGE_ID as r, CheckContext as s, checks as t, CheckScope as u, baggerKey as v, visibility as w, collectionKeys as x, BaggerOptions as y, Envelope as z };
|