/** The result edition a form was opened on: none for a first entry, or the report version it shows. */ export type EntryBasis = "none" | { reportId: string; versionId: string }; /** A fresh operation for one attempt. */ export const newEntryOperation = (): string => crypto.randomUUID(); /** * The operation for what is about to be sent. The same values sent again are the same entry, so a retry after a * lost answer finishes it instead of filing a second result; edited values are a new entry. */ export const entryOperationFor = ( previous: { operationId: string; key: string } | undefined, key: string, ): { operationId: string; key: string } => previous?.key === key ? previous : { operationId: newEntryOperation(), key }; /** An attempt whose outcome is not yet known, kept under its viewer and order until that exact attempt settles. */ export interface PendingAttempt { token: symbol; operationId: string; } /** Only the exact attempt, by its opening token and its operation, may clear what it left pending. */ export const settlesPending = (held: PendingAttempt | undefined, token: symbol, operationId: string): boolean => held !== undefined && held.token === token && held.operationId === operationId; /** Another attempt's pending entry blocks this one from sending; its own never does. */ export const blockedByPending = (held: PendingAttempt | undefined, token: symbol, operationId: string): boolean => held !== undefined && !settlesPending(held, token, operationId); const pendingEntries = new Map(); const listeners = new Set<() => void>(); const changed = () => listeners.forEach(listener => listener()); /** Only opaque identities survive an opening; clinical drafts remain in the form. */ export const resultEntryRecovery = { read: (key: string) => pendingEntries.get(key), subscribe: (listener: () => void) => { listeners.add(listener); return () => { listeners.delete(listener); }; }, reserve: (key: string, attempt: PendingAttempt): boolean => { if (blockedByPending(pendingEntries.get(key), attempt.token, attempt.operationId)) return false; pendingEntries.set(key, attempt); changed(); return true; }, settle: (key: string, attempt: PendingAttempt): boolean => { if (!settlesPending(pendingEntries.get(key), attempt.token, attempt.operationId)) return false; pendingEntries.delete(key); changed(); return true; }, /** A reviewed takeover replaces only the exact hold that was read in that opening. */ takeOver: (key: string, previous: PendingAttempt | undefined, attempt: PendingAttempt): boolean => { if (pendingEntries.get(key) !== previous) return false; pendingEntries.set(key, attempt); changed(); return true; }, }; const object = (value: unknown): Record | undefined => value && typeof value === 'object' ? value as Record : undefined; export const entryFailureIsSettled = (error: unknown): boolean => { const failed = object(error); const body = object(failed?.body); return ['not-admitted', 'refused', 'superseded', 'changed'].includes(String(body?.entryOutcome ?? '')); }; export const entryIsCompleted = (answer: unknown, orderId: string, operationId: string): boolean => { const result = object(answer); return result?.entryOutcome === 'completed' && result.orderId === orderId && result.operationId === operationId; }; export interface EntryRecovery { operationId: string; basis: EntryBasis; canResumeStored?: boolean } export const entryReadIdentityMatches = (value: unknown, orderId: string): boolean => { const read = object(value); if (!read) return false; if (read.entryRecovery === undefined && read.entrySettlements === undefined) return true; if (read.orderId !== orderId) return false; if (read.entryRecovery !== undefined) { const recovery = readEntryRecovery(read.entryRecovery); const operation = object(read.entryOperation); if (!recovery || operation?.state !== 'in-progress' || operation.operationId !== recovery.operationId) return false; } return true; }; export const readEntryRecovery = (value: unknown): EntryRecovery | undefined => { const recovery = object(value); const basis = object(recovery?.basis); if (!recovery || typeof recovery.operationId !== 'string' || !(recovery.operationId === 'legacy' || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(recovery.operationId))) return undefined; if (recovery.canResumeStored !== undefined && typeof recovery.canResumeStored !== 'boolean') return undefined; const capability = recovery.canResumeStored === undefined ? {} : { canResumeStored: recovery.canResumeStored }; if (recovery.basis === 'none') return { operationId: recovery.operationId, basis: 'none', ...capability }; if (basis && typeof basis.reportId === 'string' && /^[A-Za-z0-9.-]{1,64}$/.test(basis.reportId) && typeof basis.versionId === 'string' && /^[A-Za-z0-9.-]{1,64}$/.test(basis.versionId)) { return { operationId: recovery.operationId, basis: { reportId: basis.reportId, versionId: basis.versionId }, ...capability }; } return undefined; };