jsfe 1.6.0 → 1.7.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/README.md +27 -4
- package/dist/forms/action-projection.d.ts +4 -0
- package/dist/forms/action-projection.d.ts.map +1 -0
- package/dist/forms/action-projection.js +21 -0
- package/dist/forms/action-recovery.d.ts +12 -0
- package/dist/forms/action-recovery.d.ts.map +1 -0
- package/dist/forms/action-recovery.js +125 -0
- package/dist/forms/action-tools.d.ts +29 -0
- package/dist/forms/action-tools.d.ts.map +1 -0
- package/dist/forms/action-tools.js +6 -0
- package/dist/forms/address-flows.d.ts +2 -0
- package/dist/forms/address-flows.d.ts.map +1 -0
- package/dist/forms/address-flows.js +39 -0
- package/dist/forms/choice-flow.d.ts +419 -0
- package/dist/forms/choice-flow.d.ts.map +1 -0
- package/dist/forms/choice-flow.js +707 -0
- package/dist/forms/contracts.d.ts +237 -0
- package/dist/forms/contracts.d.ts.map +1 -0
- package/dist/forms/contracts.js +278 -0
- package/dist/forms/flows.d.ts +611 -0
- package/dist/forms/flows.d.ts.map +1 -0
- package/dist/forms/flows.js +675 -0
- package/dist/forms/identity-flows.d.ts +17 -0
- package/dist/forms/identity-flows.d.ts.map +1 -0
- package/dist/forms/identity-flows.js +302 -0
- package/dist/forms/index.d.ts +7 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms/index.js +10 -0
- package/dist/forms/prompt-compiler.d.ts +3 -0
- package/dist/forms/prompt-compiler.d.ts.map +1 -0
- package/dist/forms/prompt-compiler.js +30 -0
- package/dist/helpers/address.d.ts +27 -0
- package/dist/helpers/address.d.ts.map +1 -0
- package/dist/helpers/address.js +29 -0
- package/dist/helpers/answer.d.ts +5 -0
- package/dist/helpers/answer.d.ts.map +1 -1
- package/dist/helpers/answer.js +5 -3
- package/dist/helpers/choice-offer.d.ts +3 -0
- package/dist/helpers/choice-offer.d.ts.map +1 -0
- package/dist/helpers/choice-offer.js +6 -0
- package/dist/helpers/form-actions.d.ts +13 -0
- package/dist/helpers/form-actions.d.ts.map +1 -0
- package/dist/helpers/form-actions.js +144 -0
- package/dist/helpers/form-sources.d.ts +7 -0
- package/dist/helpers/form-sources.d.ts.map +1 -0
- package/dist/helpers/form-sources.js +47 -0
- package/dist/helpers/form-state.d.ts +94 -0
- package/dist/helpers/form-state.d.ts.map +1 -0
- package/dist/helpers/form-state.js +870 -0
- package/dist/helpers/index.d.ts +114 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +9 -4
- package/dist/helpers/match-inputs.d.ts +14 -0
- package/dist/helpers/match-inputs.d.ts.map +1 -1
- package/dist/helpers/match-inputs.js +154 -41
- package/dist/helpers/money.d.ts +12 -0
- package/dist/helpers/money.d.ts.map +1 -0
- package/dist/helpers/money.js +54 -0
- package/dist/helpers/spoken-money.d.ts +20 -0
- package/dist/helpers/spoken-money.d.ts.map +1 -0
- package/dist/helpers/spoken-money.js +143 -0
- package/dist/host/action-journal.d.ts +98 -0
- package/dist/host/action-journal.d.ts.map +1 -0
- package/dist/host/action-journal.js +218 -0
- package/dist/host/action-session.d.ts +12 -0
- package/dist/host/action-session.d.ts.map +1 -0
- package/dist/host/action-session.js +29 -0
- package/dist/host/capabilities.d.ts +12 -0
- package/dist/host/capabilities.d.ts.map +1 -0
- package/dist/host/capabilities.js +43 -0
- package/dist/host/checkpoint-codec-error.d.ts +6 -0
- package/dist/host/checkpoint-codec-error.d.ts.map +1 -0
- package/dist/host/checkpoint-codec-error.js +4 -0
- package/dist/host/index.d.ts +6 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +6 -0
- package/dist/host/intent-stages.d.ts +1 -0
- package/dist/host/intent-stages.d.ts.map +1 -1
- package/dist/host/intent-stages.js +3 -3
- package/dist/host/node.d.ts +17 -0
- package/dist/host/node.d.ts.map +1 -0
- package/dist/host/node.js +64 -0
- package/dist/host/recovery-identity.d.ts +16 -0
- package/dist/host/recovery-identity.d.ts.map +1 -0
- package/dist/host/recovery-identity.js +73 -0
- package/dist/host/recovery-policy.d.ts +5 -0
- package/dist/host/recovery-policy.d.ts.map +1 -0
- package/dist/host/recovery-policy.js +54 -0
- package/dist/host/runtime-manifest.d.ts +3 -0
- package/dist/host/runtime-manifest.d.ts.map +1 -0
- package/dist/host/runtime-manifest.js +38 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -45
- package/dist/runtime-identity.d.ts +3 -0
- package/dist/runtime-identity.d.ts.map +1 -0
- package/dist/runtime-identity.js +1 -0
- package/docs/development-release.md +23 -0
- package/docs/forms.md +122 -0
- package/package.json +15 -4
package/dist/helpers/answer.js
CHANGED
|
@@ -232,7 +232,9 @@ const DOLLARS_JOINED_CENTS = /(\$\s*)?(\d+)(\s*(?:dollars?|d[oó]lar(?:es)?|buck
|
|
|
232
232
|
* one by one, are not amounts; two different remaining numbers are ambiguous unless exactly one is marked as money ($,
|
|
233
233
|
* dollars, cents or decimals). "50 cents" alone is $0.50; "82 dollars and 50" and "82 con 50" are $82.50. Amounts said only in words are NaN (not parsed yet).
|
|
234
234
|
*/
|
|
235
|
-
export function amountInAnswer(input) {
|
|
235
|
+
export function amountInAnswer(input) { return numericAmountReading(input).value; }
|
|
236
|
+
/** Internal numeric evidence shared by the opt-in money reader; legacy results stay unchanged. */
|
|
237
|
+
export function numericAmountReading(input) {
|
|
236
238
|
const raw = String(input ?? '').replace(/\\(['"\\])/g, '$1');
|
|
237
239
|
// 1.234,56 (European) → 1234.56; 1,234.56 → 1234.56; 12,50 → 12.50
|
|
238
240
|
let t = /\d{1,3}(\.\d{3})+,\d{1,2}\b/.test(raw) ? raw.replace(/\./g, '').replace(',', '.') : raw.replace(/,(?=\d{3}\b)/g, '').replace(/(\d),(\d{1,2})\b/g, '$1.$2');
|
|
@@ -259,11 +261,11 @@ export function amountInAnswer(input) {
|
|
|
259
261
|
// no number at all
|
|
260
262
|
}
|
|
261
263
|
if (!pick) {
|
|
262
|
-
return NaN;
|
|
264
|
+
return { value: NaN, candidates: found.length };
|
|
263
265
|
}
|
|
264
266
|
else {
|
|
265
267
|
// Cents alone only when nothing else was said: "76 con50 centavos" read as $0.50 (prod, 10-15).
|
|
266
|
-
return pick.cents ? (values.length === 1 ? Math.round(pick.v) / 100 : NaN) : pick.v;
|
|
268
|
+
return { value: pick.cents ? (values.length === 1 ? Math.round(pick.v) / 100 : NaN) : pick.v, candidates: found.length };
|
|
267
269
|
}
|
|
268
270
|
}
|
|
269
271
|
// Number words a caller says for a keypad key (prod 2026-10-16 7:07 AM: "dos" at "presione 2 o diga NO" was re-asked).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choice-offer.d.ts","sourceRoot":"","sources":["../../src/helpers/choice-offer.js"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,iDAIC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Consume the one-time conversational offer shared by question and terminal presenters. */
|
|
2
|
+
export function takeChoiceOffer(cargo) {
|
|
3
|
+
const offer = cargo.switchOffer;
|
|
4
|
+
cargo.switchOffer = null;
|
|
5
|
+
return offer && typeof offer === 'object' && !Array.isArray(offer) && typeof offer.flow === 'string' && offer.flow ? offer : null;
|
|
6
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formSignature(value: any): string;
|
|
2
|
+
export function selectFormAction(cargo: any, id: any, actions: any): any;
|
|
3
|
+
export function confirmFormRevision(cargo: any, id: any): boolean;
|
|
4
|
+
export function prepareFormAction(cargo: any, id: any, action: any, request: any): any;
|
|
5
|
+
/** Unknown SEND results may be retried only by a declared policy and a freshly accepted recovery choice. */
|
|
6
|
+
export function authorizeFormActionRetry(cargo: any, id: any, action: any, consent: any): boolean;
|
|
7
|
+
export function actionConfirmationValid(cargo: any, id: any, record: any): boolean;
|
|
8
|
+
export function beginFormAction(cargo: any, id: any, action: any, record: any): boolean;
|
|
9
|
+
export function actionGateVerified(cargo: any, action: any): boolean;
|
|
10
|
+
export function finishFormAction(cargo: any, id: any, action: any, outcome: any): any;
|
|
11
|
+
/** A result is consumed only after its effect-free domain projection succeeds. */
|
|
12
|
+
export function consumeActionReceipt(cargo: any, receipt: any): boolean;
|
|
13
|
+
//# sourceMappingURL=form-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-actions.d.ts","sourceRoot":"","sources":["../../src/helpers/form-actions.js"],"names":[],"mappings":"AAEA,kDAGC;AACD,yEAMC;AACD,kEAKC;AACD,uFAkBC;AACD,4GAA4G;AAC5G,kGAkBC;AACD,mFAGC;AACD,wFAYC;AACD,qEAGC;AACD,sFASC;AAED,kFAAkF;AAClF,wEAYC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Session-owned action state. Backend idempotency and reconciliation remain explicit parts of the action contract. */
|
|
2
|
+
import { nextFormInput, formPath, formInputApplies } from './form-state.js';
|
|
3
|
+
export function formSignature(value) {
|
|
4
|
+
return JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
|
|
5
|
+
? Object.fromEntries(Object.keys(v).sort().map(k => [k, v[k]])) : v);
|
|
6
|
+
}
|
|
7
|
+
export function selectFormAction(cargo, id, actions) {
|
|
8
|
+
const state = cargo.forms?.[id];
|
|
9
|
+
if (!state)
|
|
10
|
+
throw new Error('Cannot select an action without a form instance');
|
|
11
|
+
const eligible = (actions || []).filter(action => formInputApplies(action, state.values, cargo, state));
|
|
12
|
+
if (eligible.length > 1)
|
|
13
|
+
throw new Error('Ambiguous action selection');
|
|
14
|
+
return eligible[0] || null;
|
|
15
|
+
}
|
|
16
|
+
export function confirmFormRevision(cargo, id) {
|
|
17
|
+
const state = cargo.forms?.[id];
|
|
18
|
+
if (!state || nextFormInput(state, cargo))
|
|
19
|
+
throw new Error('Cannot confirm an incomplete form');
|
|
20
|
+
state.confirmation = { revision: state.revision, values: formSignature(state.values), proposal: state.proposal?.signature || null };
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
export function prepareFormAction(cargo, id, action, request) {
|
|
24
|
+
const state = cargo.forms?.[id];
|
|
25
|
+
if (!state || nextFormInput(state, cargo))
|
|
26
|
+
throw new Error('Cannot prepare action from an incomplete form');
|
|
27
|
+
if (!action?.action || !action.outcomes)
|
|
28
|
+
throw new Error('Action contract is missing');
|
|
29
|
+
const subject = action.subject ? formPath({ cargo, form: state, request }, action.subject) : null;
|
|
30
|
+
const scope = formSignature({ action: action.action, subject });
|
|
31
|
+
const signature = formSignature({ action: action.action, subject, request });
|
|
32
|
+
cargo.actionJournal ||= {};
|
|
33
|
+
const unresolved = Object.values(cargo.actionJournal).find(record => record.scope === scope && ['attempted', 'unknown'].includes(record.status));
|
|
34
|
+
state.actions ||= {};
|
|
35
|
+
if (unresolved) {
|
|
36
|
+
state.actions[action.action] = unresolved;
|
|
37
|
+
state.proposal = unresolved;
|
|
38
|
+
return unresolved;
|
|
39
|
+
}
|
|
40
|
+
const existing = state.actions[action.action];
|
|
41
|
+
if (existing?.signature === signature && existing.status !== 'superseded' && (existing.revision === state.revision || existing.status !== 'completed' || !(action.retry?.after || []).includes(existing.result?.status)))
|
|
42
|
+
return existing;
|
|
43
|
+
if (existing?.scope === scope && ['attempted', 'unknown'].includes(existing.status))
|
|
44
|
+
return existing;
|
|
45
|
+
const record = { key: globalThis.crypto.randomUUID(), signature, revision: state.revision, status: 'prepared', scope, request: structuredClone(request), action: action.action };
|
|
46
|
+
if (cargo.actionRetryAuthorizations?.[scope]) {
|
|
47
|
+
record.retryAuthorization = cargo.actionRetryAuthorizations[scope];
|
|
48
|
+
delete cargo.actionRetryAuthorizations[scope];
|
|
49
|
+
}
|
|
50
|
+
state.actions[action.action] = record;
|
|
51
|
+
state.proposal = record;
|
|
52
|
+
return record;
|
|
53
|
+
}
|
|
54
|
+
/** Unknown SEND results may be retried only by a declared policy and a freshly accepted recovery choice. */
|
|
55
|
+
export function authorizeFormActionRetry(cargo, id, action, consent) {
|
|
56
|
+
const state = cargo.forms?.[id];
|
|
57
|
+
if (action?.effect !== 'send' || action.retry?.unknown !== 'explicit')
|
|
58
|
+
throw new Error('Action does not allow an explicit unknown-result retry');
|
|
59
|
+
if (!state || !consent?.input || !state.accepted[consent.input] || state.errors[consent.input] || state.pending[consent.input] || state.values[consent.input] !== consent.value)
|
|
60
|
+
throw new Error('Retry requires an accepted recovery choice');
|
|
61
|
+
const scope = formSignature({ action: action.action, subject: action.subject ? formPath({ cargo, form: state }, action.subject) : null });
|
|
62
|
+
const consentKey = formSignature({ scope, input: consent.input, sequence: state.accepted[consent.input].sequence ?? state.accepted[consent.input].revision });
|
|
63
|
+
state.retryConsents ||= {};
|
|
64
|
+
if (state.retryConsents[consentKey])
|
|
65
|
+
throw new Error('Recovery consent was already consumed');
|
|
66
|
+
const records = Object.values(cargo.actionJournal || {}).filter(record => record.scope === scope && record.status === 'unknown');
|
|
67
|
+
if (records.length)
|
|
68
|
+
state.retryConsents[consentKey] = true;
|
|
69
|
+
for (const record of records) {
|
|
70
|
+
const authorization = { form: id, input: consent.input, value: consent.value, revision: state.revision, sequence: state.accepted[consent.input].sequence, at: Date.now() };
|
|
71
|
+
record.status = 'superseded';
|
|
72
|
+
record.retryAuthorization = authorization;
|
|
73
|
+
cargo.actionRetryHistory ||= [];
|
|
74
|
+
cargo.actionRetryHistory.push({ key: record.key, action: record.action, authorization });
|
|
75
|
+
cargo.actionRetryAuthorizations ||= {};
|
|
76
|
+
cargo.actionRetryAuthorizations[scope] = { previousKey: record.durableKey || record.key, authorization };
|
|
77
|
+
for (const owner of Object.values(cargo.forms))
|
|
78
|
+
for (const previous of Object.values(owner.actions || {}))
|
|
79
|
+
if (previous.key === record.key) {
|
|
80
|
+
previous.status = 'superseded';
|
|
81
|
+
previous.retryAuthorization = authorization;
|
|
82
|
+
}
|
|
83
|
+
delete cargo.actionJournal[record.key];
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
export function actionConfirmationValid(cargo, id, record) {
|
|
88
|
+
const state = cargo.forms?.[id], confirmation = state?.confirmation;
|
|
89
|
+
return !!confirmation && confirmation.revision === state.revision && confirmation.values === formSignature(state.values) && confirmation.proposal === record.signature;
|
|
90
|
+
}
|
|
91
|
+
export function beginFormAction(cargo, id, action, record) {
|
|
92
|
+
const state = cargo.forms?.[id];
|
|
93
|
+
if (!state || nextFormInput(state, cargo))
|
|
94
|
+
throw new Error('Cannot execute action from an incomplete form');
|
|
95
|
+
const owned = state.actions?.[action.action];
|
|
96
|
+
if (!owned || record.key !== owned.key || record.signature !== owned.signature)
|
|
97
|
+
throw new Error('Action ownership mismatch');
|
|
98
|
+
if (owned.status !== 'prepared' || !formInputApplies(action, state.values, cargo, state))
|
|
99
|
+
return false;
|
|
100
|
+
if (owned.signature !== formSignature({ action: action.action, subject: action.subject ? formPath({ cargo, form: state, request: owned.request }, action.subject) : null, request: owned.request }))
|
|
101
|
+
return false;
|
|
102
|
+
if (action.confirm?.exact && !actionConfirmationValid(cargo, id, owned))
|
|
103
|
+
return false;
|
|
104
|
+
if (!actionGateVerified(cargo, action))
|
|
105
|
+
return false;
|
|
106
|
+
owned.status = 'attempted';
|
|
107
|
+
cargo.actionJournal ||= {};
|
|
108
|
+
cargo.actionJournal[owned.key] = owned;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
export function actionGateVerified(cargo, action) {
|
|
112
|
+
const gate = action.gate?.verified;
|
|
113
|
+
return !gate || [].concat(gate).every(path => !!formPath({ cargo }, path));
|
|
114
|
+
}
|
|
115
|
+
export function finishFormAction(cargo, id, action, outcome) {
|
|
116
|
+
const record = cargo.forms?.[id]?.actions?.[action.action];
|
|
117
|
+
if (!record || record.status !== 'attempted' && record.status !== 'unknown')
|
|
118
|
+
throw new Error('Action was not attempted');
|
|
119
|
+
const valid = outcome && Object.prototype.hasOwnProperty.call(action.outcomes, outcome.status);
|
|
120
|
+
record.result = valid ? outcome : { status: 'unknown', reason: 'invalid_action_result' };
|
|
121
|
+
record.status = record.result.status === 'unknown' ? 'unknown' : 'completed';
|
|
122
|
+
if (record.status === 'completed')
|
|
123
|
+
delete cargo.actionJournal?.[record.key];
|
|
124
|
+
else
|
|
125
|
+
cargo.actionJournal[record.key] = record;
|
|
126
|
+
return record.result;
|
|
127
|
+
}
|
|
128
|
+
/** A result is consumed only after its effect-free domain projection succeeds. */
|
|
129
|
+
export function consumeActionReceipt(cargo, receipt) {
|
|
130
|
+
if (!receipt)
|
|
131
|
+
return true;
|
|
132
|
+
if (!receipt.key || !Number.isSafeInteger(receipt.sequence) || receipt.sequence < 1)
|
|
133
|
+
throw new Error('Invalid action receipt');
|
|
134
|
+
const key = receipt.key, sequence = receipt.sequence;
|
|
135
|
+
cargo.actionJournalSequence = Math.max(Number(cargo.actionJournalSequence || 0), sequence);
|
|
136
|
+
cargo.actionJournalApplied ||= {};
|
|
137
|
+
cargo.actionJournalApplied[key] = Math.max(Number(cargo.actionJournalApplied[key] || 0), sequence);
|
|
138
|
+
cargo.actionJournalReceipts ||= [];
|
|
139
|
+
if (!cargo.actionJournalReceipts.includes(key))
|
|
140
|
+
cargo.actionJournalReceipts.push(key);
|
|
141
|
+
cargo.actionJournalReceiptRevisions ||= {};
|
|
142
|
+
cargo.actionJournalReceiptRevisions[key] = Math.max(Number(cargo.actionJournalReceiptRevisions[key] || 0), sequence);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function sourceNeedsLoad(cargo: any, source: any, now?: number): boolean;
|
|
2
|
+
export function beginSourceRefresh(cargo: any, source: any): boolean;
|
|
3
|
+
export function completeSourceRefresh(cargo: any, source: any, now?: number): boolean;
|
|
4
|
+
/** Prefer the most specific positively matched catalog phrase over a contained broad fact alias.
|
|
5
|
+
* Ordinary menu actions retain their own exact-choice rules. No match returns no fact. */
|
|
6
|
+
export function pickProviderQuestion(answer: any, questions?: any[]): any;
|
|
7
|
+
//# sourceMappingURL=form-sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-sources.d.ts","sourceRoot":"","sources":["../../src/helpers/form-sources.js"],"names":[],"mappings":"AAKA,gFAKC;AACD,qEASC;AACD,sFAKC;AAED;0FAC0F;AAC1F,0EAMC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Provider freshness is tied to the subject and to a successful write by the loader. */
|
|
2
|
+
import { formPath } from './form-state.js';
|
|
3
|
+
import { pickChoice } from './answer.js';
|
|
4
|
+
const present = value => value !== undefined && value !== null;
|
|
5
|
+
const subject = (cargo, source) => source.subject ? JSON.stringify(formPath({ cargo }, source.subject) ?? null) : '';
|
|
6
|
+
export function sourceNeedsLoad(cargo, source, now = Date.now()) {
|
|
7
|
+
if (!source?.result || !source.flow)
|
|
8
|
+
throw new Error('Provider source needs a result path and loader');
|
|
9
|
+
const have = formPath({ cargo }, source.result), at = cargo.sourceLoadedAt?.[source.result];
|
|
10
|
+
if (!present(have) || !at || cargo.sourceLoadedFor?.[source.result] !== subject(cargo, source))
|
|
11
|
+
return true;
|
|
12
|
+
return source.refresh === 'per_turn' || (source.max_age_ms !== undefined && now - at >= Number(source.max_age_ms));
|
|
13
|
+
}
|
|
14
|
+
export function beginSourceRefresh(cargo, source) {
|
|
15
|
+
const keys = String(source.result || '').split('.');
|
|
16
|
+
if (keys.shift() !== 'cargo' || !keys.length || keys.some(k => !k || ['__proto__', 'constructor', 'prototype'].includes(k)))
|
|
17
|
+
throw new Error('Provider result must be a safe cargo path');
|
|
18
|
+
let target = cargo;
|
|
19
|
+
for (const key of keys.slice(0, -1)) {
|
|
20
|
+
if (!target[key] || typeof target[key] !== 'object')
|
|
21
|
+
target[key] = {};
|
|
22
|
+
target = target[key];
|
|
23
|
+
}
|
|
24
|
+
delete target[keys[keys.length - 1]];
|
|
25
|
+
if (cargo.sourceLoadedAt)
|
|
26
|
+
delete cargo.sourceLoadedAt[source.result];
|
|
27
|
+
if (cargo.sourceLoadedFor)
|
|
28
|
+
delete cargo.sourceLoadedFor[source.result];
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
export function completeSourceRefresh(cargo, source, now = Date.now()) {
|
|
32
|
+
if (!present(formPath({ cargo }, source.result)))
|
|
33
|
+
return false;
|
|
34
|
+
cargo.sourceLoadedAt ||= {};
|
|
35
|
+
cargo.sourceLoadedFor ||= {};
|
|
36
|
+
cargo.sourceLoadedAt[source.result] = now;
|
|
37
|
+
cargo.sourceLoadedFor[source.result] = subject(cargo, source);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
/** Prefer the most specific positively matched catalog phrase over a contained broad fact alias.
|
|
41
|
+
* Ordinary menu actions retain their own exact-choice rules. No match returns no fact. */
|
|
42
|
+
export function pickProviderQuestion(answer, questions = []) {
|
|
43
|
+
const matches = questions.flatMap((question, index) => (question.words || []).flatMap(word => pickChoice(answer, [{ ...question, words: [word] }], false) === question.key
|
|
44
|
+
? [{ key: question.key, length: String(word).normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim().length, index }] : []));
|
|
45
|
+
matches.sort((a, b) => b.length - a.length || a.index - b.index);
|
|
46
|
+
return matches[0]?.key || '';
|
|
47
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export function formValuesEqual(a: any, b: any): any;
|
|
2
|
+
export function formPath(root: any, path: any): any;
|
|
3
|
+
/** Resolve once before the state is used by collection, validation or completion. */
|
|
4
|
+
export function resolveFormInputs(inputs: any, presets?: {}): any[];
|
|
5
|
+
export function formInputApplies(input: any, values?: {}, cargo?: {}, state?: {}): boolean;
|
|
6
|
+
export function formInputFilled(input: any, state: any, cargo?: {}, checking?: Set<any>): any;
|
|
7
|
+
export function nextFormInput(state: any, cargo?: {}): any;
|
|
8
|
+
export function formInputChain(forms: any, id: any, cargo?: {}): any[];
|
|
9
|
+
/** Attach the canonical field contract to every claim, including independent ambiguity groups. */
|
|
10
|
+
export function enrichFormMatches(matches: any, chain: any, form: any, names?: any[], asked?: string): any;
|
|
11
|
+
/** Expand inferred controllers before normal ordering/validation. A slot's entry and controllers form one commit unit. */
|
|
12
|
+
export function expandFormActivations(cargo: any, hits?: any[]): any[];
|
|
13
|
+
/** No partial activation: validation failure suppresses inferred changes; pending prerequisites retain the complete group. */
|
|
14
|
+
export function settleFormActivations(batch?: {}, cargo?: null): {
|
|
15
|
+
accepted: any[];
|
|
16
|
+
rejected: any[];
|
|
17
|
+
pending: any[];
|
|
18
|
+
};
|
|
19
|
+
/** Explicit ownership: unrelated tasks and sibling inputs never share an instance by definition id. */
|
|
20
|
+
export function openFormInstance(cargo: any, definition: any, specs: any, { parent, input, resume, task, providers, questions, context, incomplete_outcome, on_exhausted }?: {
|
|
21
|
+
parent?: null | undefined;
|
|
22
|
+
input?: string | undefined;
|
|
23
|
+
resume?: string | undefined;
|
|
24
|
+
task?: string | undefined;
|
|
25
|
+
providers?: null | undefined;
|
|
26
|
+
questions?: null | undefined;
|
|
27
|
+
context?: boolean | undefined;
|
|
28
|
+
incomplete_outcome?: undefined;
|
|
29
|
+
on_exhausted?: undefined;
|
|
30
|
+
}): any;
|
|
31
|
+
/** An incomplete optional follow-up may preserve an explicitly completed task outcome. */
|
|
32
|
+
export function validateFormIncompleteOutcome(value: any): {
|
|
33
|
+
outcome: any;
|
|
34
|
+
reason: any;
|
|
35
|
+
} | null;
|
|
36
|
+
/** Only actual owned ancestors may provide a fallback; another task's outcome is irrelevant. */
|
|
37
|
+
export function formIncompleteOutcome(cargo: any, id: any, fallback?: {
|
|
38
|
+
outcome: string;
|
|
39
|
+
reason: string;
|
|
40
|
+
}): {
|
|
41
|
+
owner: any;
|
|
42
|
+
outcome: any;
|
|
43
|
+
reason: any;
|
|
44
|
+
} | {
|
|
45
|
+
owner: string;
|
|
46
|
+
outcome?: any;
|
|
47
|
+
reason?: any;
|
|
48
|
+
};
|
|
49
|
+
/** Explicit worker references only; recovery cannot contain an inline effect or caller expression. */
|
|
50
|
+
export function validateFormExhaustion(value: any): string;
|
|
51
|
+
/** Resolve nearest declared owner, release only its exhausted input branch, and withdraw old consent.
|
|
52
|
+
* A reboot to this worker unwinds the collector; no incomplete value is handed to an ancestor.
|
|
53
|
+
* Recovery must deliberately collect another choice before resuming the preserved owning draft.
|
|
54
|
+
*/
|
|
55
|
+
export function exhaustFormInput(cargo: any, failed_form: any, failed_input?: string): {
|
|
56
|
+
flow: string;
|
|
57
|
+
form: any;
|
|
58
|
+
failed_form: any;
|
|
59
|
+
input: string;
|
|
60
|
+
failed_input: string;
|
|
61
|
+
reason: string;
|
|
62
|
+
} | null;
|
|
63
|
+
/** Activation values are queued for the same validator flow as conversational readings. */
|
|
64
|
+
export function rememberFormArguments(state: any, supplied: any, names: any, resume?: boolean): any;
|
|
65
|
+
export function formInitialReadings(state: any, supplied?: {}, cargo?: {}): any;
|
|
66
|
+
/** One exclusive group is clarified at a time; independent readings continue through the same protocol. */
|
|
67
|
+
export function exclusiveFormReadings(cargo: any, hits?: any[]): {
|
|
68
|
+
ambiguous: any[];
|
|
69
|
+
hits: any[];
|
|
70
|
+
};
|
|
71
|
+
export function orderFormReadings(cargo: any, hits?: any[]): any[];
|
|
72
|
+
export function formReadingDependencies(cargo: any, hit: any): any;
|
|
73
|
+
export function rejectFormReadings(cargo: any, hits?: any[]): boolean;
|
|
74
|
+
export function proposedFormValues(cargo: any, id: any, accepted?: any[]): any;
|
|
75
|
+
export function formReadingRelevant(cargo: any, hit: any, accepted?: any[]): boolean;
|
|
76
|
+
export function formReadingReady(cargo: any, hit: any, accepted?: any[], rejected?: any[]): boolean;
|
|
77
|
+
export function formPreparation(cargo: any, id: any, input: any): {
|
|
78
|
+
flow: any;
|
|
79
|
+
input: any;
|
|
80
|
+
} | null;
|
|
81
|
+
export function queueFormReadings(cargo: any, hits?: any[]): boolean;
|
|
82
|
+
export function pendingFormReadings(cargo: any, id: any): any[];
|
|
83
|
+
export function formWorkLimit(cargo: any, id: any): number;
|
|
84
|
+
export function applyFormValidation(cargo: any, id: any, result: any): "failed" | "retry" | "valid";
|
|
85
|
+
/** Invalidation is transitive and independent of declaration order. */
|
|
86
|
+
export function invalidateFormInputs(cargo: any, id: any, changed: any, preserve?: any[], preserveNodes?: any[]): any[];
|
|
87
|
+
/** An intentional retry resets the named inputs as well as all of their dependent state. Unknown actions stay held. */
|
|
88
|
+
export function resetFormInputs(cargo: any, id: any, names?: any[]): boolean;
|
|
89
|
+
/** Commit only validated readings. Callers batch dependency-related candidates before invoking this transition. */
|
|
90
|
+
export function acceptFormReadings(cargo: any, hits: any, additionalCandidates?: any[]): boolean;
|
|
91
|
+
export function formInstanceApplies(cargo: any, id: any): any;
|
|
92
|
+
export function closeFormInstance(cargo: any, id: any, handOn?: boolean): any;
|
|
93
|
+
export function hasFormValue(v: any): boolean;
|
|
94
|
+
//# sourceMappingURL=form-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAMA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEAsBC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEAuCC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;QA8BC;AAED,0FAA0F;AAC1F;;;SAIC;AACD,gGAAgG;AAChG;;;;;;;;;;;EAQC;AAED,sGAAsG;AACtG,2DAIC;AACD;;;GAGG;AACH;;;;;;;SAkBC;AAED,2FAA2F;AAC3F,oGAIC;AACD,gFA4BC;AAED,2GAA2G;AAC3G;;;EASC;AACD,mEAoBC;AACD,mEAGC;AACD,sEAUC;AACD,+EAEC;AACD,qFASC;AACD,oGAoCC;AACD;;;SAKC;AACD,qEAGC;AACD,gEAgBC;AACD,2DAQC;AACD,oGAUC;AAED,uEAAuE;AACvE,wHA+BC;AAED,uHAAuH;AACvH,6EAaC;AACD,mHAAmH;AACnH,iGA4CC;AACD,8DAQC;AACD,8EAeC;AAljBM,8CAAmE"}
|