@marktiderman/genesis-forms 0.7.0 → 0.9.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.
Files changed (36) hide show
  1. package/README.md +386 -8
  2. package/dist/destinations.cjs +856 -53
  3. package/dist/destinations.cjs.map +1 -1
  4. package/dist/destinations.d.cts +452 -18
  5. package/dist/destinations.d.ts +452 -18
  6. package/dist/destinations.js +826 -54
  7. package/dist/destinations.js.map +1 -1
  8. package/dist/{engine-C5tb3mkf.d.cts → engine-BaHPC2-1.d.cts} +2 -2
  9. package/dist/{engine-DcNQ-6oJ.d.ts → engine-Da99Tv-E.d.ts} +2 -2
  10. package/dist/index.cjs +996 -167
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +288 -11
  13. package/dist/index.d.ts +288 -11
  14. package/dist/index.js +975 -168
  15. package/dist/index.js.map +1 -1
  16. package/dist/server.cjs +563 -173
  17. package/dist/server.cjs.map +1 -1
  18. package/dist/server.d.cts +3 -474
  19. package/dist/server.d.ts +3 -474
  20. package/dist/server.js +558 -174
  21. package/dist/server.js.map +1 -1
  22. package/dist/submit-protocol-CFi-0Qd0.d.ts +840 -0
  23. package/dist/submit-protocol-D0cOckmK.d.cts +840 -0
  24. package/dist/testing.cjs +250 -154
  25. package/dist/testing.cjs.map +1 -1
  26. package/dist/testing.d.cts +4 -4
  27. package/dist/testing.d.ts +4 -4
  28. package/dist/testing.js +250 -154
  29. package/dist/testing.js.map +1 -1
  30. package/dist/{types-oAckGR_Z.d.cts → types-C0pUfew3.d.cts} +8 -2
  31. package/dist/{types-oAckGR_Z.d.ts → types-C0pUfew3.d.ts} +8 -2
  32. package/dist/{validate-BFpNJbjf.d.cts → validate-DpmBssFk.d.ts} +258 -140
  33. package/dist/{validate-qwkdcr3H.d.ts → validate-U5LoW3eR.d.cts} +258 -140
  34. package/package.json +2 -2
  35. package/dist/upload-protocol-Bw0CWRJc.d.cts +0 -122
  36. package/dist/upload-protocol-PX0jroTV.d.ts +0 -122
package/dist/index.d.cts CHANGED
@@ -1,11 +1,11 @@
1
- import { a as FieldBlock, C as ContentBlock, P as PageBreakBlock, O as OptionsSource, F as FormDefinition, H as HiddenInput, b as FormLayout, L as LogicRule, E as Ending, c as FormSettings, J as Json, d as Condition, B as Block } from './types-oAckGR_Z.cjs';
2
- export { e as FormEvent, f as Operand, g as Operator } from './types-oAckGR_Z.cjs';
3
- import { P as PhotoFormat, a as PhotoRef } from './upload-protocol-Bw0CWRJc.cjs';
4
- export { D as DEFAULT_PHOTO_FILES, b as DEFAULT_PHOTO_SIZE_MB, M as MAX_PHOTO_FILES, c as MAX_PHOTO_ID_LENGTH, d as MAX_PHOTO_NAME_LENGTH, e as MAX_PHOTO_SIZE_MB, f as MAX_UPLOAD_REQUEST_BYTES, g as PHOTO_FORMATS, h as PHOTO_TYPE, i as PhotoLimits, S as SignedUpload, U as UPLOAD_FIELD_TYPES, j as UploadGrant, k as UploadRefusal, l as UploadRequest, m as isPhotoFormat, n as isPhotoRef, p as photoCountMessage, o as photoFormatList, q as photoLimits, r as photoRefs, s as photoSizeMessage, t as photoTypeMessage } from './upload-protocol-Bw0CWRJc.cjs';
5
- import { F as FormEngine, a as FormState } from './engine-C5tb3mkf.cjs';
6
- export { C as CreateFormOptions, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-C5tb3mkf.cjs';
7
- import { F as FieldValidator } from './validate-BFpNJbjf.cjs';
8
- export { A as AnswerSnapshot, C as CHOICE_FIELD_TYPES, a as ChoiceOption, b as FieldTypeManifest, H as HostOptions, L as LinkParams, M as MAX_PREFILL_PARAM_LENGTH, O as OPTION_LIMITS, c as OTHER_MAX_LENGTH, d as OptionSnapshot, P as PrefillSetting, e as PrefillState, R as REQUIRED_MESSAGE, f as RestoredAnswers, S as SEARCHABLE_OPTION_COUNT, g as StandardSchemaV1, h as answerSnapshots, i as answersFromSnapshots, j as answersOnPath, k as choiceOptions, l as clearFilteredAnswers, m as dateLimits, n as filteredChoiceError, o as getFieldType, p as hasOptionFilters, q as hiddenByPrefill, r as isEmptyValue, s as isHttpsUrl, t as isIsoDate, u as isOtherValue, v as isPrefillParam, w as isSearchable, x as isSensitive, y as listFieldTypes, z as normalizeValue, B as numberLimits, D as optionFilter, E as optionJson, G as optionMatches, I as optionsSource, J as otherOption, K as prefillFromLink, N as prefillSetting, Q as ratingDisplay, T as readOption, U as registerFieldType, V as respondentPath, W as scaleLabels, X as searchOptions, Y as summarizeValue, Z as validateField, _ as visibleOptions, $ as withHostOptions } from './validate-BFpNJbjf.cjs';
1
+ import { a as FieldBlock, b as ContentBlock, P as PageBreakBlock, O as OptionsSource, F as FormDefinition, H as HiddenInput, c as FormLayout, L as LogicRule, E as Ending, d as FormSettings, J as Json, C as Condition, B as Block } from './types-C0pUfew3.cjs';
2
+ export { e as FormEvent, f as Operand, g as Operator } from './types-C0pUfew3.cjs';
3
+ import { P as PhotoFormat, a as PhotoRef, S as SubmissionInput, b as SubmitRequest, c as SubmitFailureReason, d as SubmitResult } from './submit-protocol-D0cOckmK.cjs';
4
+ export { D as DEFAULT_PHOTO_FILES, e as DEFAULT_PHOTO_SIZE_MB, I as IDEMPOTENCY_KEY_PATTERN, M as MAX_PHOTO_FILES, f as MAX_PHOTO_ID_LENGTH, g as MAX_PHOTO_NAME_LENGTH, h as MAX_PHOTO_SIZE_MB, i as MAX_UPLOAD_REQUEST_BYTES, j as MergeOptions, k as PHOTO_FORMATS, l as PHOTO_TYPE, m as PhotoLimits, n as SignedUpload, o as SubmitOutcome, U as UPLOAD_FIELD_TYPES, p as UploadGrant, q as UploadRefusal, r as UploadRequest, s as isIdempotencyKey, t as isPhotoFormat, u as isPhotoRef, v as isSubmitResult, w as mergeResponses, x as photoCountMessage, y as photoFormatList, z as photoLimits, A as photoRefs, B as photoSizeMessage, C as photoTypeMessage, E as submitOutcome } from './submit-protocol-D0cOckmK.cjs';
5
+ import { F as FormEngine, a as FormState } from './engine-BaHPC2-1.cjs';
6
+ export { C as CreateFormOptions, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-BaHPC2-1.cjs';
7
+ import { F as FieldValidator } from './validate-U5LoW3eR.cjs';
8
+ export { A as AnswerSnapshot, C as CHOICE_FIELD_TYPES, a as ChoiceOption, b as ContentTypeManifest, c as FieldTypeManifest, H as HostOptions, L as LinkParams, d as LiteralType, M as MAX_PREFILL_PARAM_LENGTH, O as OPTION_LIMITS, e as OTHER_MAX_LENGTH, f as OptionSnapshot, P as PrefillSetting, g as PrefillState, h as PropsCheck, i as PropsChecker, R as REQUIRED_MESSAGE, j as RegisterOptions, k as RestoredAnswers, S as SEARCHABLE_OPTION_COUNT, l as StandardSchemaV1, V as VALIDATOR_FAILED_MESSAGE, m as answerSnapshots, n as answersFromSnapshots, o as answersOnPath, p as choiceKind, q as choiceOptions, r as clearFilteredAnswers, s as contentText, t as dateLimits, u as filteredChoiceError, v as getContentType, w as getFieldType, x as hasOptionFilters, y as hiddenByPrefill, z as isBuiltInFieldType, B as isChoiceField, D as isEmptyValue, E as isHttpsUrl, G as isIsoDate, I as isOtherValue, J as isPrefillParam, K as isSearchable, N as isSensitive, Q as listContentTypes, T as listFieldTypes, U as normalizeValue, W as numberLimits, X as optionFilter, Y as optionJson, Z as optionMatches, _ as optionsSource, $ as otherOption, a0 as prefillFromLink, a1 as prefillSetting, a2 as ratingDisplay, a3 as readOption, a4 as registerContentType, a5 as registerFieldType, a6 as respondentPath, a7 as scaleLabels, a8 as searchOptions, a9 as summarizeValue, aa as validateField, ab as visibleOptions, ac as withHostOptions } from './validate-U5LoW3eR.cjs';
9
9
 
10
10
  /** A field block that remembers its key and value type, for answer inference. */
11
11
  type FieldSpec<K extends string = string, V = unknown> = FieldBlock & {
@@ -300,8 +300,9 @@ type ParseResult = {
300
300
  /**
301
301
  * Check a definition before it is stored or rendered: structure, the types of
302
302
  * text fields, unique ids and keys, known field types, references that
303
- * resolve, operators that fit the field type. Warnings flag things that work
304
- * but are probably mistakes. Never throws, whatever the input.
303
+ * resolve, operators that fit the field type. Each field and content kind
304
+ * checks its own `props` (`checkProps` in the registry). Warnings flag things
305
+ * that work but are probably mistakes. Never throws, whatever the input.
305
306
  */
306
307
  declare function parseForm(input: unknown): ParseResult;
307
308
 
@@ -686,4 +687,280 @@ declare function describeCondition(condition: Condition, def: FormDefinition, fi
686
687
  */
687
688
  declare function carryAnswers(from: FormDefinition, to: FormDefinition, answers: Record<string, Json>, extra?: Record<string, Json | null | undefined>): Record<string, Json>;
688
689
 
689
- export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, type FormProgress, FormSettings, FormState, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, OptionsSource, PageBreakBlock, type ParseResult, type PathResult, PhotoFormat, PhotoRef, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, carryAnswers, coerceHidden, coerceHiddenValue, computePath, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, listLimits, parseForm, parseFormula, parseTimestamp, recall, renameFormulaKey, ruleVisible, steps, tableColumns, tableLimits, when };
690
+ /**
691
+ * The key-value storage drafts and the submit outbox keep their data in. One small shape
692
+ * that `localStorage` (web) and React Native's AsyncStorage both are already; MMKV and
693
+ * others take a three-line wrapper:
694
+ *
695
+ * ```ts
696
+ * const storage: KeyValueStorage = {
697
+ * getItem: (key) => mmkv.getString(key) ?? null,
698
+ * setItem: (key, value) => mmkv.set(key, value),
699
+ * removeItem: (key) => mmkv.delete(key),
700
+ * };
701
+ * ```
702
+ */
703
+ type MaybePromise<T> = T | Promise<T>;
704
+ /** Strings by key, kept across restarts. Each method may answer at once or with a promise; a throw (or a rejection) is a failure the caller reports. */
705
+ interface KeyValueStorage {
706
+ getItem(key: string): MaybePromise<string | null | undefined>;
707
+ setItem(key: string, value: string): MaybePromise<void>;
708
+ removeItem(key: string): MaybePromise<void>;
709
+ }
710
+ /** Storage that lasts as long as the page or app does: for tests, and where nothing may be kept on the device. */
711
+ declare function memoryStorage(): KeyValueStorage & {
712
+ keys(): string[];
713
+ };
714
+ /**
715
+ * The browser's `localStorage` (or another `Storage`, such as `sessionStorage`), looked up on
716
+ * each call, so creating it during server rendering is safe. Where there is none (a server, a
717
+ * locked-down private window) every call throws, and the caller reports it.
718
+ */
719
+ declare function webStorage(storage?: () => Pick<Storage, "getItem" | "setItem" | "removeItem"> | undefined): KeyValueStorage;
720
+
721
+ /** Whose draft of which form, at which version of it. */
722
+ interface DraftKey {
723
+ formId: string;
724
+ /** The definition's version (a published version label, or `fingerprint(definition)`). */
725
+ version: string;
726
+ /** Who is answering: a user id, or a session id made once on this device for anonymous visits. */
727
+ respondent: string;
728
+ }
729
+ /** A saved draft. Plain JSON. */
730
+ interface FormDraft extends DraftKey {
731
+ /** The answers given, by field id, including ones logic hides now (so going back keeps them). Calculated fields are left out. */
732
+ values: Record<string, Json>;
733
+ /** Each saved field's type when it was saved, by field id, to tell whether the form still fits. */
734
+ types: Record<string, string>;
735
+ /** The furthest question reached (`formProgress(engine).fieldId`): `createForm`'s `resumeAt`. */
736
+ resumeAt: string | null;
737
+ /** When it was saved, in milliseconds since 1970. */
738
+ savedAt: number;
739
+ }
740
+ /** Where drafts are kept. One draft per form and respondent: saving another version replaces it. */
741
+ interface DraftStore {
742
+ /** The draft of this form by this respondent, of any version, or null. */
743
+ get(key: DraftKey): MaybePromise<FormDraft | null>;
744
+ set(draft: FormDraft): MaybePromise<void>;
745
+ remove(key: DraftKey): MaybePromise<void>;
746
+ }
747
+ declare const DRAFT_KEY_PREFIX = "genesis-forms:draft:";
748
+ /**
749
+ * A `DraftStore` over key-value storage, one entry per form and respondent (the version is
750
+ * inside it, so a new version finds the old draft and `restoreDraft` decides whether it still
751
+ * fits). A stored entry that doesn't read as a draft is answered null.
752
+ */
753
+ declare function createDraftStore(storage: KeyValueStorage, options?: {
754
+ prefix?: string | undefined;
755
+ }): DraftStore;
756
+ interface DraftOptions {
757
+ /**
758
+ * Keep answers to sensitive fields (`isSensitive`) in the draft. Default false: they are
759
+ * left out, so a health detail or an ID number never sits in the device's storage, and the
760
+ * respondent answers them again after a restore.
761
+ */
762
+ keepSensitive?: boolean | undefined;
763
+ }
764
+ /** The engine's answers so far as a draft (null when nothing is answered). */
765
+ declare function draftOf(engine: Pick<FormEngine, "definition" | "getState">, key: DraftKey, options?: DraftOptions & {
766
+ now?: (() => number) | undefined;
767
+ }): FormDraft | null;
768
+ /** 30 days: the default for how long a draft is kept. */
769
+ declare const DRAFT_MAX_AGE_MS: number;
770
+ type DraftRestore = {
771
+ ok: true;
772
+ /** For `createForm`'s `initialValues` (over the host's own prefill). */
773
+ values: Record<string, Json>;
774
+ /** For `createForm`'s `resumeAt`. */
775
+ resumeAt: string | null;
776
+ } | {
777
+ ok: false;
778
+ /**
779
+ * `version`: the form changed and an answer no longer fits (its question is gone, has
780
+ * another type, or the answer isn't valid for it now). `expired`: older than `maxAgeMs`.
781
+ */
782
+ reason: "version" | "expired";
783
+ };
784
+ /**
785
+ * Whether a saved draft can be put back into this definition. The same version: yes. Another
786
+ * version: only when every saved answer still fits (its question is there, of the same type,
787
+ * and the answer passes its format checks); otherwise the whole draft is refused, never half
788
+ * put back. Pass the definition with the host's options filled in (`engine.definition`, or
789
+ * `withHostOptions`), so a picked option that's gone counts.
790
+ */
791
+ declare function restoreDraft(definition: FormDefinition, draft: FormDraft, options: {
792
+ version: string;
793
+ now?: (() => number) | undefined;
794
+ maxAgeMs?: number | undefined;
795
+ }): DraftRestore;
796
+ interface DraftAutosaveOptions extends DraftOptions {
797
+ /** How long the respondent pauses before the draft is saved, in milliseconds. Default 800. */
798
+ delayMs?: number | undefined;
799
+ now?: (() => number) | undefined;
800
+ /** A save or removal that failed (storage full, unavailable). Default `console.warn`. Autosave never throws. */
801
+ onError?: ((error: unknown) => void) | undefined;
802
+ /** Each save that landed, with its time. */
803
+ onSaved?: ((draft: FormDraft) => void) | undefined;
804
+ /**
805
+ * A draft is stored for this key already (it was just restored). Answers cleared to nothing
806
+ * remove a stored draft; without this, only one this autosave wrote. Default false, so a
807
+ * draft that couldn't be read is never removed before the respondent answers anything.
808
+ */
809
+ stored?: boolean | undefined;
810
+ }
811
+ interface DraftAutosave {
812
+ /** Save now whatever is waiting. Resolves when it is written (or failed, through `onError`). */
813
+ flush(): Promise<void>;
814
+ /**
815
+ * Stop saving and remove the draft: call it once the submission is safe (queued in the
816
+ * outbox, or stored). Nothing waiting is written after it.
817
+ */
818
+ clear(): Promise<void>;
819
+ /** Stop listening. Whatever is waiting is saved first (`flush`). */
820
+ dispose(): Promise<void>;
821
+ }
822
+ /**
823
+ * Save the engine's answers to `store` as they change, `delayMs` after the last change, one
824
+ * write at a time and in order. Answers cleared to nothing remove the draft (one this
825
+ * autosave wrote, or the one restored: `stored`), so an emptied form doesn't come back filled.
826
+ */
827
+ declare function createDraftAutosave(engine: Pick<FormEngine, "definition" | "getState" | "subscribe">, store: DraftStore, key: DraftKey, options?: DraftAutosaveOptions): DraftAutosave;
828
+
829
+ /**
830
+ * The submit outbox: submissions wait on the device until the server has them. Each is
831
+ * queued with an idempotency key (so a retry is stored once), kept in storage across
832
+ * restarts, sent when online with backoff between tries, and never dropped silently: one the
833
+ * server refuses, or that runs out of tries, stays as `failed` until the respondent retries
834
+ * or discards it.
835
+ *
836
+ * Transport-agnostic: `send` is the app's, typically a `fetch` to its endpoint that runs
837
+ * `submitResponse` and answers its `SubmitResult` as JSON.
838
+ */
839
+
840
+ /** One queued submission, as stored. Plain JSON. */
841
+ interface OutboxEntry {
842
+ /** The submission's idempotency key. */
843
+ id: string;
844
+ /** Which form it is for: `send` picks the endpoint by it. */
845
+ formId: string;
846
+ /** The body sent, every time the same. */
847
+ request: SubmitRequest;
848
+ /** The app's own details to show in a list (a form's title), never sent. */
849
+ meta: Record<string, Json> | null;
850
+ createdAt: number;
851
+ /** Tries so far (since the last `retry`). */
852
+ attempts: number;
853
+ /** When it may be tried next. */
854
+ nextAttemptAt: number;
855
+ /** `pending`: will be sent. `failed`: refused, or out of tries; waits for `retry` or `discard`. */
856
+ state: "pending" | "failed";
857
+ /** Why the last try didn't deliver it, for the respondent or a log. Never holds an answer. */
858
+ lastError: string | null;
859
+ /** When the server refused it (`invalid` or `closed`), what it said. */
860
+ rejection: {
861
+ reason: SubmitFailureReason;
862
+ errors?: Record<string, string>;
863
+ } | null;
864
+ }
865
+ /** A queued submission as the outbox shows it. */
866
+ interface OutboxItem extends OutboxEntry {
867
+ /** Being sent now. */
868
+ sending: boolean;
869
+ }
870
+ interface OutboxSnapshot {
871
+ /** Whether what was stored has been read. Until then `items` may be missing some. */
872
+ loaded: boolean;
873
+ online: boolean;
874
+ /** Oldest first. */
875
+ items: readonly OutboxItem[];
876
+ pending: number;
877
+ failed: number;
878
+ /** Reading or writing the storage failed; null once it works again. */
879
+ storageError: string | null;
880
+ /** Stored submissions that couldn't be read back, by id: left in storage, never sent or removed. */
881
+ unreadable: readonly string[];
882
+ }
883
+ interface EnqueueInput {
884
+ formId: string;
885
+ submission: SubmissionInput;
886
+ /** Default: a new UUID. Pass the same key again and the queued submission is returned, not queued twice. */
887
+ idempotencyKey?: string | undefined;
888
+ /** Default: now. */
889
+ clientSubmittedAt?: number | undefined;
890
+ /** The visit's attempt id, for a form with upload fields (`SubmitRequest.attemptId`). */
891
+ attemptId?: string | undefined;
892
+ meta?: Record<string, Json> | undefined;
893
+ }
894
+ interface SubmitOutboxOptions {
895
+ storage: KeyValueStorage;
896
+ /**
897
+ * Send one submission and answer what the server said: its `SubmitResult` (an endpoint's
898
+ * JSON body, whatever its status code). Throw, or answer anything else, and it is tried
899
+ * again later. Abort on `signal` (sent after `timeoutMs`).
900
+ */
901
+ send(item: OutboxItem, context: {
902
+ signal: AbortSignal;
903
+ }): Promise<unknown>;
904
+ /** Milliseconds since 1970. Default `Date.now`. */
905
+ now?: (() => number) | undefined;
906
+ /** Storage keys start with this. Default `genesis-forms:outbox`. One outbox per prefix. */
907
+ prefix?: string | undefined;
908
+ /**
909
+ * Tries before a submission is marked `failed` (it is kept). Default 10. A submission that
910
+ * ran out of tries (not one the server refused) is tried again from the start each time the
911
+ * outbox starts and each time the device comes back online.
912
+ */
913
+ maxAttempts?: number | undefined;
914
+ /** Wait before try n+1: about `baseMs * 2^(n-1)`, at most `maxMs`, with jitter. Default 2 seconds and 15 minutes. */
915
+ backoff?: {
916
+ baseMs?: number | undefined;
917
+ maxMs?: number | undefined;
918
+ } | undefined;
919
+ /** How long one send may take before it is aborted and tried again. Default 30 seconds. */
920
+ timeoutMs?: number | undefined;
921
+ /** Whether the device is online to begin with. Default true. Keep it current with `setOnline`. */
922
+ online?: boolean | undefined;
923
+ /**
924
+ * Makes idempotency keys. Default `crypto.randomUUID`; where there is none (React Native
925
+ * without a polyfill) pass one (expo-crypto's `randomUUID`), or `createSubmitOutbox` throws.
926
+ */
927
+ createKey?: (() => string) | undefined;
928
+ /** 0 to 1, for jitter. Default `Math.random`. */
929
+ random?: (() => number) | undefined;
930
+ /** A submission the server now has (stored now, or before: a duplicate). */
931
+ onDelivered?: ((item: OutboxItem, result: SubmitResult) => void) | undefined;
932
+ /** Something went wrong that the app may want to log. Default `console.warn`. */
933
+ log?: ((message: string) => void) | undefined;
934
+ }
935
+ interface SubmitOutbox {
936
+ /** Resolves once stored submissions are read (or reading failed: see `storageError`). Never rejects. */
937
+ readonly ready: Promise<void>;
938
+ /**
939
+ * Queue a submission. Resolves once it is in storage: only then is it safe to clear the
940
+ * draft. Rejects when storage can't take it (keep the draft, and tell the respondent).
941
+ * Sends it at once when the outbox is started and online.
942
+ */
943
+ enqueue(input: EnqueueInput): Promise<OutboxItem>;
944
+ /** Send every pending submission that is due, oldest first, one at a time. */
945
+ flush(): Promise<void>;
946
+ /** Try a failed submission again from its first try, now. */
947
+ retry(id: string): Promise<void>;
948
+ /** Remove a queued submission for good. False while it is being sent, or when it isn't there. */
949
+ discard(id: string): Promise<boolean>;
950
+ /** Tell the outbox whether the device is online. Coming online sends what's pending at once. */
951
+ setOnline(online: boolean): void;
952
+ /** Send as submissions come due (timers). `stop` ends it. */
953
+ start(): void;
954
+ stop(): void;
955
+ getSnapshot(): OutboxSnapshot;
956
+ subscribe(listener: () => void): () => void;
957
+ }
958
+ /**
959
+ * Each submission is stored under its own key, with an index of keys beside it. The index is
960
+ * read again and merged on every change, so two outboxes on one storage (two tabs) don't
961
+ * write each other's submissions out; both may send one, and its idempotency key makes the
962
+ * server store it once.
963
+ */
964
+ declare function createSubmitOutbox(options: SubmitOutboxOptions): SubmitOutbox;
965
+
966
+ export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, DRAFT_KEY_PREFIX, DRAFT_MAX_AGE_MS, type DraftAutosave, type DraftAutosaveOptions, type DraftKey, type DraftOptions, type DraftRestore, type DraftStore, Ending, type EnqueueInput, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldValidation, FieldValidator, type Form, FormDefinition, type FormDraft, FormEngine, type FormInput, type FormIssue, FormLayout, type FormProgress, FormSettings, FormState, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, type KeyValueStorage, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MaybePromise, type MediaNote, OptionsSource, type OutboxEntry, type OutboxItem, type OutboxSnapshot, PageBreakBlock, type ParseResult, type PathResult, PhotoFormat, PhotoRef, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, SubmitFailureReason, type SubmitOutbox, type SubmitOutboxOptions, SubmitRequest, SubmitResult, type TableColumn, calculatedProps, calculatedValue, canonicalJson, carryAnswers, coerceHidden, coerceHiddenValue, computePath, createDraftAutosave, createDraftStore, createSubmitOutbox, defineForm, describeCondition, draftOf, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, listLimits, memoryStorage, parseForm, parseFormula, parseTimestamp, recall, renameFormulaKey, restoreDraft, ruleVisible, steps, tableColumns, tableLimits, webStorage, when };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { a as FieldBlock, C as ContentBlock, P as PageBreakBlock, O as OptionsSource, F as FormDefinition, H as HiddenInput, b as FormLayout, L as LogicRule, E as Ending, c as FormSettings, J as Json, d as Condition, B as Block } from './types-oAckGR_Z.js';
2
- export { e as FormEvent, f as Operand, g as Operator } from './types-oAckGR_Z.js';
3
- import { P as PhotoFormat, a as PhotoRef } from './upload-protocol-PX0jroTV.js';
4
- export { D as DEFAULT_PHOTO_FILES, b as DEFAULT_PHOTO_SIZE_MB, M as MAX_PHOTO_FILES, c as MAX_PHOTO_ID_LENGTH, d as MAX_PHOTO_NAME_LENGTH, e as MAX_PHOTO_SIZE_MB, f as MAX_UPLOAD_REQUEST_BYTES, g as PHOTO_FORMATS, h as PHOTO_TYPE, i as PhotoLimits, S as SignedUpload, U as UPLOAD_FIELD_TYPES, j as UploadGrant, k as UploadRefusal, l as UploadRequest, m as isPhotoFormat, n as isPhotoRef, p as photoCountMessage, o as photoFormatList, q as photoLimits, r as photoRefs, s as photoSizeMessage, t as photoTypeMessage } from './upload-protocol-PX0jroTV.js';
5
- import { F as FormEngine, a as FormState } from './engine-DcNQ-6oJ.js';
6
- export { C as CreateFormOptions, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-DcNQ-6oJ.js';
7
- import { F as FieldValidator } from './validate-qwkdcr3H.js';
8
- export { A as AnswerSnapshot, C as CHOICE_FIELD_TYPES, a as ChoiceOption, b as FieldTypeManifest, H as HostOptions, L as LinkParams, M as MAX_PREFILL_PARAM_LENGTH, O as OPTION_LIMITS, c as OTHER_MAX_LENGTH, d as OptionSnapshot, P as PrefillSetting, e as PrefillState, R as REQUIRED_MESSAGE, f as RestoredAnswers, S as SEARCHABLE_OPTION_COUNT, g as StandardSchemaV1, h as answerSnapshots, i as answersFromSnapshots, j as answersOnPath, k as choiceOptions, l as clearFilteredAnswers, m as dateLimits, n as filteredChoiceError, o as getFieldType, p as hasOptionFilters, q as hiddenByPrefill, r as isEmptyValue, s as isHttpsUrl, t as isIsoDate, u as isOtherValue, v as isPrefillParam, w as isSearchable, x as isSensitive, y as listFieldTypes, z as normalizeValue, B as numberLimits, D as optionFilter, E as optionJson, G as optionMatches, I as optionsSource, J as otherOption, K as prefillFromLink, N as prefillSetting, Q as ratingDisplay, T as readOption, U as registerFieldType, V as respondentPath, W as scaleLabels, X as searchOptions, Y as summarizeValue, Z as validateField, _ as visibleOptions, $ as withHostOptions } from './validate-qwkdcr3H.js';
1
+ import { a as FieldBlock, b as ContentBlock, P as PageBreakBlock, O as OptionsSource, F as FormDefinition, H as HiddenInput, c as FormLayout, L as LogicRule, E as Ending, d as FormSettings, J as Json, C as Condition, B as Block } from './types-C0pUfew3.js';
2
+ export { e as FormEvent, f as Operand, g as Operator } from './types-C0pUfew3.js';
3
+ import { P as PhotoFormat, a as PhotoRef, S as SubmissionInput, b as SubmitRequest, c as SubmitFailureReason, d as SubmitResult } from './submit-protocol-CFi-0Qd0.js';
4
+ export { D as DEFAULT_PHOTO_FILES, e as DEFAULT_PHOTO_SIZE_MB, I as IDEMPOTENCY_KEY_PATTERN, M as MAX_PHOTO_FILES, f as MAX_PHOTO_ID_LENGTH, g as MAX_PHOTO_NAME_LENGTH, h as MAX_PHOTO_SIZE_MB, i as MAX_UPLOAD_REQUEST_BYTES, j as MergeOptions, k as PHOTO_FORMATS, l as PHOTO_TYPE, m as PhotoLimits, n as SignedUpload, o as SubmitOutcome, U as UPLOAD_FIELD_TYPES, p as UploadGrant, q as UploadRefusal, r as UploadRequest, s as isIdempotencyKey, t as isPhotoFormat, u as isPhotoRef, v as isSubmitResult, w as mergeResponses, x as photoCountMessage, y as photoFormatList, z as photoLimits, A as photoRefs, B as photoSizeMessage, C as photoTypeMessage, E as submitOutcome } from './submit-protocol-CFi-0Qd0.js';
5
+ import { F as FormEngine, a as FormState } from './engine-Da99Tv-E.js';
6
+ export { C as CreateFormOptions, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-Da99Tv-E.js';
7
+ import { F as FieldValidator } from './validate-DpmBssFk.js';
8
+ export { A as AnswerSnapshot, C as CHOICE_FIELD_TYPES, a as ChoiceOption, b as ContentTypeManifest, c as FieldTypeManifest, H as HostOptions, L as LinkParams, d as LiteralType, M as MAX_PREFILL_PARAM_LENGTH, O as OPTION_LIMITS, e as OTHER_MAX_LENGTH, f as OptionSnapshot, P as PrefillSetting, g as PrefillState, h as PropsCheck, i as PropsChecker, R as REQUIRED_MESSAGE, j as RegisterOptions, k as RestoredAnswers, S as SEARCHABLE_OPTION_COUNT, l as StandardSchemaV1, V as VALIDATOR_FAILED_MESSAGE, m as answerSnapshots, n as answersFromSnapshots, o as answersOnPath, p as choiceKind, q as choiceOptions, r as clearFilteredAnswers, s as contentText, t as dateLimits, u as filteredChoiceError, v as getContentType, w as getFieldType, x as hasOptionFilters, y as hiddenByPrefill, z as isBuiltInFieldType, B as isChoiceField, D as isEmptyValue, E as isHttpsUrl, G as isIsoDate, I as isOtherValue, J as isPrefillParam, K as isSearchable, N as isSensitive, Q as listContentTypes, T as listFieldTypes, U as normalizeValue, W as numberLimits, X as optionFilter, Y as optionJson, Z as optionMatches, _ as optionsSource, $ as otherOption, a0 as prefillFromLink, a1 as prefillSetting, a2 as ratingDisplay, a3 as readOption, a4 as registerContentType, a5 as registerFieldType, a6 as respondentPath, a7 as scaleLabels, a8 as searchOptions, a9 as summarizeValue, aa as validateField, ab as visibleOptions, ac as withHostOptions } from './validate-DpmBssFk.js';
9
9
 
10
10
  /** A field block that remembers its key and value type, for answer inference. */
11
11
  type FieldSpec<K extends string = string, V = unknown> = FieldBlock & {
@@ -300,8 +300,9 @@ type ParseResult = {
300
300
  /**
301
301
  * Check a definition before it is stored or rendered: structure, the types of
302
302
  * text fields, unique ids and keys, known field types, references that
303
- * resolve, operators that fit the field type. Warnings flag things that work
304
- * but are probably mistakes. Never throws, whatever the input.
303
+ * resolve, operators that fit the field type. Each field and content kind
304
+ * checks its own `props` (`checkProps` in the registry). Warnings flag things
305
+ * that work but are probably mistakes. Never throws, whatever the input.
305
306
  */
306
307
  declare function parseForm(input: unknown): ParseResult;
307
308
 
@@ -686,4 +687,280 @@ declare function describeCondition(condition: Condition, def: FormDefinition, fi
686
687
  */
687
688
  declare function carryAnswers(from: FormDefinition, to: FormDefinition, answers: Record<string, Json>, extra?: Record<string, Json | null | undefined>): Record<string, Json>;
688
689
 
689
- export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, type FormProgress, FormSettings, FormState, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, OptionsSource, PageBreakBlock, type ParseResult, type PathResult, PhotoFormat, PhotoRef, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, carryAnswers, coerceHidden, coerceHiddenValue, computePath, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, listLimits, parseForm, parseFormula, parseTimestamp, recall, renameFormulaKey, ruleVisible, steps, tableColumns, tableLimits, when };
690
+ /**
691
+ * The key-value storage drafts and the submit outbox keep their data in. One small shape
692
+ * that `localStorage` (web) and React Native's AsyncStorage both are already; MMKV and
693
+ * others take a three-line wrapper:
694
+ *
695
+ * ```ts
696
+ * const storage: KeyValueStorage = {
697
+ * getItem: (key) => mmkv.getString(key) ?? null,
698
+ * setItem: (key, value) => mmkv.set(key, value),
699
+ * removeItem: (key) => mmkv.delete(key),
700
+ * };
701
+ * ```
702
+ */
703
+ type MaybePromise<T> = T | Promise<T>;
704
+ /** Strings by key, kept across restarts. Each method may answer at once or with a promise; a throw (or a rejection) is a failure the caller reports. */
705
+ interface KeyValueStorage {
706
+ getItem(key: string): MaybePromise<string | null | undefined>;
707
+ setItem(key: string, value: string): MaybePromise<void>;
708
+ removeItem(key: string): MaybePromise<void>;
709
+ }
710
+ /** Storage that lasts as long as the page or app does: for tests, and where nothing may be kept on the device. */
711
+ declare function memoryStorage(): KeyValueStorage & {
712
+ keys(): string[];
713
+ };
714
+ /**
715
+ * The browser's `localStorage` (or another `Storage`, such as `sessionStorage`), looked up on
716
+ * each call, so creating it during server rendering is safe. Where there is none (a server, a
717
+ * locked-down private window) every call throws, and the caller reports it.
718
+ */
719
+ declare function webStorage(storage?: () => Pick<Storage, "getItem" | "setItem" | "removeItem"> | undefined): KeyValueStorage;
720
+
721
+ /** Whose draft of which form, at which version of it. */
722
+ interface DraftKey {
723
+ formId: string;
724
+ /** The definition's version (a published version label, or `fingerprint(definition)`). */
725
+ version: string;
726
+ /** Who is answering: a user id, or a session id made once on this device for anonymous visits. */
727
+ respondent: string;
728
+ }
729
+ /** A saved draft. Plain JSON. */
730
+ interface FormDraft extends DraftKey {
731
+ /** The answers given, by field id, including ones logic hides now (so going back keeps them). Calculated fields are left out. */
732
+ values: Record<string, Json>;
733
+ /** Each saved field's type when it was saved, by field id, to tell whether the form still fits. */
734
+ types: Record<string, string>;
735
+ /** The furthest question reached (`formProgress(engine).fieldId`): `createForm`'s `resumeAt`. */
736
+ resumeAt: string | null;
737
+ /** When it was saved, in milliseconds since 1970. */
738
+ savedAt: number;
739
+ }
740
+ /** Where drafts are kept. One draft per form and respondent: saving another version replaces it. */
741
+ interface DraftStore {
742
+ /** The draft of this form by this respondent, of any version, or null. */
743
+ get(key: DraftKey): MaybePromise<FormDraft | null>;
744
+ set(draft: FormDraft): MaybePromise<void>;
745
+ remove(key: DraftKey): MaybePromise<void>;
746
+ }
747
+ declare const DRAFT_KEY_PREFIX = "genesis-forms:draft:";
748
+ /**
749
+ * A `DraftStore` over key-value storage, one entry per form and respondent (the version is
750
+ * inside it, so a new version finds the old draft and `restoreDraft` decides whether it still
751
+ * fits). A stored entry that doesn't read as a draft is answered null.
752
+ */
753
+ declare function createDraftStore(storage: KeyValueStorage, options?: {
754
+ prefix?: string | undefined;
755
+ }): DraftStore;
756
+ interface DraftOptions {
757
+ /**
758
+ * Keep answers to sensitive fields (`isSensitive`) in the draft. Default false: they are
759
+ * left out, so a health detail or an ID number never sits in the device's storage, and the
760
+ * respondent answers them again after a restore.
761
+ */
762
+ keepSensitive?: boolean | undefined;
763
+ }
764
+ /** The engine's answers so far as a draft (null when nothing is answered). */
765
+ declare function draftOf(engine: Pick<FormEngine, "definition" | "getState">, key: DraftKey, options?: DraftOptions & {
766
+ now?: (() => number) | undefined;
767
+ }): FormDraft | null;
768
+ /** 30 days: the default for how long a draft is kept. */
769
+ declare const DRAFT_MAX_AGE_MS: number;
770
+ type DraftRestore = {
771
+ ok: true;
772
+ /** For `createForm`'s `initialValues` (over the host's own prefill). */
773
+ values: Record<string, Json>;
774
+ /** For `createForm`'s `resumeAt`. */
775
+ resumeAt: string | null;
776
+ } | {
777
+ ok: false;
778
+ /**
779
+ * `version`: the form changed and an answer no longer fits (its question is gone, has
780
+ * another type, or the answer isn't valid for it now). `expired`: older than `maxAgeMs`.
781
+ */
782
+ reason: "version" | "expired";
783
+ };
784
+ /**
785
+ * Whether a saved draft can be put back into this definition. The same version: yes. Another
786
+ * version: only when every saved answer still fits (its question is there, of the same type,
787
+ * and the answer passes its format checks); otherwise the whole draft is refused, never half
788
+ * put back. Pass the definition with the host's options filled in (`engine.definition`, or
789
+ * `withHostOptions`), so a picked option that's gone counts.
790
+ */
791
+ declare function restoreDraft(definition: FormDefinition, draft: FormDraft, options: {
792
+ version: string;
793
+ now?: (() => number) | undefined;
794
+ maxAgeMs?: number | undefined;
795
+ }): DraftRestore;
796
+ interface DraftAutosaveOptions extends DraftOptions {
797
+ /** How long the respondent pauses before the draft is saved, in milliseconds. Default 800. */
798
+ delayMs?: number | undefined;
799
+ now?: (() => number) | undefined;
800
+ /** A save or removal that failed (storage full, unavailable). Default `console.warn`. Autosave never throws. */
801
+ onError?: ((error: unknown) => void) | undefined;
802
+ /** Each save that landed, with its time. */
803
+ onSaved?: ((draft: FormDraft) => void) | undefined;
804
+ /**
805
+ * A draft is stored for this key already (it was just restored). Answers cleared to nothing
806
+ * remove a stored draft; without this, only one this autosave wrote. Default false, so a
807
+ * draft that couldn't be read is never removed before the respondent answers anything.
808
+ */
809
+ stored?: boolean | undefined;
810
+ }
811
+ interface DraftAutosave {
812
+ /** Save now whatever is waiting. Resolves when it is written (or failed, through `onError`). */
813
+ flush(): Promise<void>;
814
+ /**
815
+ * Stop saving and remove the draft: call it once the submission is safe (queued in the
816
+ * outbox, or stored). Nothing waiting is written after it.
817
+ */
818
+ clear(): Promise<void>;
819
+ /** Stop listening. Whatever is waiting is saved first (`flush`). */
820
+ dispose(): Promise<void>;
821
+ }
822
+ /**
823
+ * Save the engine's answers to `store` as they change, `delayMs` after the last change, one
824
+ * write at a time and in order. Answers cleared to nothing remove the draft (one this
825
+ * autosave wrote, or the one restored: `stored`), so an emptied form doesn't come back filled.
826
+ */
827
+ declare function createDraftAutosave(engine: Pick<FormEngine, "definition" | "getState" | "subscribe">, store: DraftStore, key: DraftKey, options?: DraftAutosaveOptions): DraftAutosave;
828
+
829
+ /**
830
+ * The submit outbox: submissions wait on the device until the server has them. Each is
831
+ * queued with an idempotency key (so a retry is stored once), kept in storage across
832
+ * restarts, sent when online with backoff between tries, and never dropped silently: one the
833
+ * server refuses, or that runs out of tries, stays as `failed` until the respondent retries
834
+ * or discards it.
835
+ *
836
+ * Transport-agnostic: `send` is the app's, typically a `fetch` to its endpoint that runs
837
+ * `submitResponse` and answers its `SubmitResult` as JSON.
838
+ */
839
+
840
+ /** One queued submission, as stored. Plain JSON. */
841
+ interface OutboxEntry {
842
+ /** The submission's idempotency key. */
843
+ id: string;
844
+ /** Which form it is for: `send` picks the endpoint by it. */
845
+ formId: string;
846
+ /** The body sent, every time the same. */
847
+ request: SubmitRequest;
848
+ /** The app's own details to show in a list (a form's title), never sent. */
849
+ meta: Record<string, Json> | null;
850
+ createdAt: number;
851
+ /** Tries so far (since the last `retry`). */
852
+ attempts: number;
853
+ /** When it may be tried next. */
854
+ nextAttemptAt: number;
855
+ /** `pending`: will be sent. `failed`: refused, or out of tries; waits for `retry` or `discard`. */
856
+ state: "pending" | "failed";
857
+ /** Why the last try didn't deliver it, for the respondent or a log. Never holds an answer. */
858
+ lastError: string | null;
859
+ /** When the server refused it (`invalid` or `closed`), what it said. */
860
+ rejection: {
861
+ reason: SubmitFailureReason;
862
+ errors?: Record<string, string>;
863
+ } | null;
864
+ }
865
+ /** A queued submission as the outbox shows it. */
866
+ interface OutboxItem extends OutboxEntry {
867
+ /** Being sent now. */
868
+ sending: boolean;
869
+ }
870
+ interface OutboxSnapshot {
871
+ /** Whether what was stored has been read. Until then `items` may be missing some. */
872
+ loaded: boolean;
873
+ online: boolean;
874
+ /** Oldest first. */
875
+ items: readonly OutboxItem[];
876
+ pending: number;
877
+ failed: number;
878
+ /** Reading or writing the storage failed; null once it works again. */
879
+ storageError: string | null;
880
+ /** Stored submissions that couldn't be read back, by id: left in storage, never sent or removed. */
881
+ unreadable: readonly string[];
882
+ }
883
+ interface EnqueueInput {
884
+ formId: string;
885
+ submission: SubmissionInput;
886
+ /** Default: a new UUID. Pass the same key again and the queued submission is returned, not queued twice. */
887
+ idempotencyKey?: string | undefined;
888
+ /** Default: now. */
889
+ clientSubmittedAt?: number | undefined;
890
+ /** The visit's attempt id, for a form with upload fields (`SubmitRequest.attemptId`). */
891
+ attemptId?: string | undefined;
892
+ meta?: Record<string, Json> | undefined;
893
+ }
894
+ interface SubmitOutboxOptions {
895
+ storage: KeyValueStorage;
896
+ /**
897
+ * Send one submission and answer what the server said: its `SubmitResult` (an endpoint's
898
+ * JSON body, whatever its status code). Throw, or answer anything else, and it is tried
899
+ * again later. Abort on `signal` (sent after `timeoutMs`).
900
+ */
901
+ send(item: OutboxItem, context: {
902
+ signal: AbortSignal;
903
+ }): Promise<unknown>;
904
+ /** Milliseconds since 1970. Default `Date.now`. */
905
+ now?: (() => number) | undefined;
906
+ /** Storage keys start with this. Default `genesis-forms:outbox`. One outbox per prefix. */
907
+ prefix?: string | undefined;
908
+ /**
909
+ * Tries before a submission is marked `failed` (it is kept). Default 10. A submission that
910
+ * ran out of tries (not one the server refused) is tried again from the start each time the
911
+ * outbox starts and each time the device comes back online.
912
+ */
913
+ maxAttempts?: number | undefined;
914
+ /** Wait before try n+1: about `baseMs * 2^(n-1)`, at most `maxMs`, with jitter. Default 2 seconds and 15 minutes. */
915
+ backoff?: {
916
+ baseMs?: number | undefined;
917
+ maxMs?: number | undefined;
918
+ } | undefined;
919
+ /** How long one send may take before it is aborted and tried again. Default 30 seconds. */
920
+ timeoutMs?: number | undefined;
921
+ /** Whether the device is online to begin with. Default true. Keep it current with `setOnline`. */
922
+ online?: boolean | undefined;
923
+ /**
924
+ * Makes idempotency keys. Default `crypto.randomUUID`; where there is none (React Native
925
+ * without a polyfill) pass one (expo-crypto's `randomUUID`), or `createSubmitOutbox` throws.
926
+ */
927
+ createKey?: (() => string) | undefined;
928
+ /** 0 to 1, for jitter. Default `Math.random`. */
929
+ random?: (() => number) | undefined;
930
+ /** A submission the server now has (stored now, or before: a duplicate). */
931
+ onDelivered?: ((item: OutboxItem, result: SubmitResult) => void) | undefined;
932
+ /** Something went wrong that the app may want to log. Default `console.warn`. */
933
+ log?: ((message: string) => void) | undefined;
934
+ }
935
+ interface SubmitOutbox {
936
+ /** Resolves once stored submissions are read (or reading failed: see `storageError`). Never rejects. */
937
+ readonly ready: Promise<void>;
938
+ /**
939
+ * Queue a submission. Resolves once it is in storage: only then is it safe to clear the
940
+ * draft. Rejects when storage can't take it (keep the draft, and tell the respondent).
941
+ * Sends it at once when the outbox is started and online.
942
+ */
943
+ enqueue(input: EnqueueInput): Promise<OutboxItem>;
944
+ /** Send every pending submission that is due, oldest first, one at a time. */
945
+ flush(): Promise<void>;
946
+ /** Try a failed submission again from its first try, now. */
947
+ retry(id: string): Promise<void>;
948
+ /** Remove a queued submission for good. False while it is being sent, or when it isn't there. */
949
+ discard(id: string): Promise<boolean>;
950
+ /** Tell the outbox whether the device is online. Coming online sends what's pending at once. */
951
+ setOnline(online: boolean): void;
952
+ /** Send as submissions come due (timers). `stop` ends it. */
953
+ start(): void;
954
+ stop(): void;
955
+ getSnapshot(): OutboxSnapshot;
956
+ subscribe(listener: () => void): () => void;
957
+ }
958
+ /**
959
+ * Each submission is stored under its own key, with an index of keys beside it. The index is
960
+ * read again and merged on every change, so two outboxes on one storage (two tabs) don't
961
+ * write each other's submissions out; both may send one, and its idempotency key makes the
962
+ * server store it once.
963
+ */
964
+ declare function createSubmitOutbox(options: SubmitOutboxOptions): SubmitOutbox;
965
+
966
+ export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, DRAFT_KEY_PREFIX, DRAFT_MAX_AGE_MS, type DraftAutosave, type DraftAutosaveOptions, type DraftKey, type DraftOptions, type DraftRestore, type DraftStore, Ending, type EnqueueInput, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldValidation, FieldValidator, type Form, FormDefinition, type FormDraft, FormEngine, type FormInput, type FormIssue, FormLayout, type FormProgress, FormSettings, FormState, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, type KeyValueStorage, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MaybePromise, type MediaNote, OptionsSource, type OutboxEntry, type OutboxItem, type OutboxSnapshot, PageBreakBlock, type ParseResult, type PathResult, PhotoFormat, PhotoRef, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, SubmitFailureReason, type SubmitOutbox, type SubmitOutboxOptions, SubmitRequest, SubmitResult, type TableColumn, calculatedProps, calculatedValue, canonicalJson, carryAnswers, coerceHidden, coerceHiddenValue, computePath, createDraftAutosave, createDraftStore, createSubmitOutbox, defineForm, describeCondition, draftOf, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, listLimits, memoryStorage, parseForm, parseFormula, parseTimestamp, recall, renameFormulaKey, restoreDraft, ruleVisible, steps, tableColumns, tableLimits, webStorage, when };