@marktiderman/genesis-forms 0.8.0 → 0.9.1

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 +299 -10
  2. package/dist/destinations.cjs +331 -114
  3. package/dist/destinations.cjs.map +1 -1
  4. package/dist/destinations.d.cts +67 -45
  5. package/dist/destinations.d.ts +67 -45
  6. package/dist/destinations.js +327 -111
  7. package/dist/destinations.js.map +1 -1
  8. package/dist/{engine-Bj23YJlB.d.cts → engine-BaHPC2-1.d.cts} +2 -2
  9. package/dist/{engine-CzinTJph.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-jqDKcmP4.d.cts → types-C0pUfew3.d.cts} +7 -1
  31. package/dist/{types-jqDKcmP4.d.ts → types-C0pUfew3.d.ts} +7 -1
  32. package/dist/{validate-DvfQHYnu.d.ts → validate-DpmBssFk.d.ts} +258 -140
  33. package/dist/{validate-irIPubxc.d.cts → validate-U5LoW3eR.d.cts} +258 -140
  34. package/package.json +3 -2
  35. package/dist/upload-protocol-BOe018kL.d.cts +0 -122
  36. package/dist/upload-protocol-BUO74WkS.d.ts +0 -122
@@ -1,146 +1,32 @@
1
- import { J as Json, g as Operator, a as FieldBlock, B as Block, O as OptionsSource, F as FormDefinition, e as FormEvent } from './types-jqDKcmP4.cjs';
1
+ import { J as Json, e as FormEvent, F as FormDefinition, a as FieldBlock, b as ContentBlock, g as Operator, B as Block, O as OptionsSource } from './types-C0pUfew3.cjs';
2
2
 
3
3
  /**
4
- * A field type, without any UI. Renderers are registered separately per
5
- * platform (`genesis-forms-react`), so a manifest runs anywhere.
4
+ * Checking a block's `props` for `parseForm`: the shared helpers every content and field kind's
5
+ * `checkProps` uses, so each kind states its own settings once and they are reported the same way.
6
6
  */
7
- interface FieldTypeManifest {
8
- type: string;
9
- label: string;
10
- category: "text" | "choice" | "scale" | "date" | "media" | "advanced";
11
- /** `json: false` types must convert, so drafts and server replay never lose an answer. */
12
- value: {
13
- json: true;
14
- } | {
15
- json: false;
16
- serialize: (value: unknown) => Json;
17
- deserialize: (json: Json) => unknown;
18
- };
19
- operators: Operator[];
20
- a11y: {
21
- semantics: "control" | "group";
22
- /** The HTML `autocomplete` token, such as `"email"` or `"tel"`. */
23
- autocomplete?: string;
24
- /** The HTML `inputmode` hint for on-screen keyboards, such as `"email"` or `"tel"`. */
25
- inputMode?: "text" | "email" | "tel" | "numeric" | "decimal" | "url" | "search";
26
- };
27
- isEmpty?: (value: Json | undefined) => boolean;
28
- /** Return an error message, or null when the (non-empty) value is valid. */
29
- validate?: (value: Json, field: FieldBlock) => string | null;
30
- /** Plain text for recall, exports and emails. */
31
- summarize?: (value: Json, field: FieldBlock) => string;
32
- /**
33
- * The form a (valid, non-empty) value is stored in, such as an email
34
- * without surrounding spaces. Applied by `engine.answers()` and the server.
35
- */
36
- normalize?: (value: Json, field: FieldBlock) => Json;
37
- /** Valid example answers, used by `coverage()` and other test tools to explore a path. */
38
- samples?: (field: FieldBlock) => Json[];
39
- }
40
- declare function registerFieldType(manifest: FieldTypeManifest): void;
41
- declare function getFieldType(type: string): FieldTypeManifest | undefined;
42
- declare function listFieldTypes(): FieldTypeManifest[];
43
- declare function isEmptyValue(value: Json | undefined, field?: FieldBlock): boolean;
44
- /** A value in its stored form (see `FieldTypeManifest.normalize`). */
45
- declare function normalizeValue(value: Json, field: FieldBlock): Json;
46
- /** Whether a block holds personal or secret data: `sensitive: true`, or the `props.sensitive: true` alias. */
47
- declare function isSensitive(block: Block): boolean;
48
- declare function summarizeValue(value: Json | undefined, field: FieldBlock): string;
49
- interface ChoiceOption {
50
- value: string;
51
- label?: string;
52
- /** A muted line under the label, such as a location's address. At most `OPTION_LIMITS.description` characters. */
53
- description?: string;
54
- /** A small picture beside the label: an `https://` URL, at most `OPTION_LIMITS.image` characters. */
55
- image?: string;
56
- /**
57
- * Facts about the option for filtering and recall, such as `{ location: ["riverside", "hilltop"] }`
58
- * for a coach who works at two locations (see `props.optionsFrom.filter`). Names and values are
59
- * text; a value is one text or a list of them.
60
- */
61
- attributes?: Record<string, string | string[]>;
62
- }
63
- /** Bounds on an option's extra details. A longer or malformed detail is dropped when options are read. */
64
- declare const OPTION_LIMITS: {
65
- /** Characters in a description. */
66
- readonly description: 500;
67
- /** Characters in an image URL. */
68
- readonly image: 2048;
69
- /** Attributes on one option. */
70
- readonly attributes: 20;
71
- /** Characters in an attribute's name. */
72
- readonly attributeName: 64;
73
- /** Characters in one attribute value. */
74
- readonly attributeValue: 200;
75
- /** Values in one attribute's list. */
76
- readonly attributeItems: 50;
77
- };
78
7
  /**
79
- * Whether `url` is an `https://` link with a host and no spaces or control characters, within
80
- * `OPTION_LIMITS.image`. A pattern rather than `new URL`, so it behaves the same in React Native.
8
+ * A kind's check of its `props` (an object, `{}` when the block has none). Report through
9
+ * `check`, with paths under `props` (`"max"`, `"columns[0].key"`); `parseForm` prefixes each
10
+ * with the block's (`blocks[3].props.`).
81
11
  */
82
- declare function isHttpsUrl(url: unknown): url is string;
83
- /**
84
- * An option from untrusted JSON (a definition, the host's list): its string `value`, and its
85
- * `label`, `description`, `image` and `attributes` when each is well formed and within
86
- * `OPTION_LIMITS`; anything else is left out. Null when there is no string value.
87
- */
88
- declare function readOption(raw: unknown): ChoiceOption | null;
89
- /** An option as JSON, with only the details it has. */
90
- declare function optionJson(option: ChoiceOption): Json;
91
- /** The longest answer an "Other" option takes. */
92
- declare const OTHER_MAX_LENGTH = 200;
93
- /**
94
- * A choice question's "Other" option (`props.other: true` or `{ label }`): the respondent types
95
- * their own answer, stored as the answer's value itself (single choice, dropdown) or as one more
96
- * item (multiple choice). Null when the question has none.
97
- */
98
- declare function otherOption(field: FieldBlock): {
99
- label: string;
100
- } | null;
101
- /** Whether a value is a typed "Other" answer: text that isn't one of the question's options. */
102
- declare function isOtherValue(field: FieldBlock, value: unknown): value is string;
103
- declare function choiceOptions(field: FieldBlock): ChoiceOption[];
104
- /**
105
- * Option lists the app showing a form supplies, by key (`OptionsSource.key`):
106
- * `{ coaches: [{ value: "8c1…", label: "Ada Lovelace" }] }`.
107
- */
108
- type HostOptions = Readonly<Record<string, readonly ChoiceOption[]>>;
109
- /**
110
- * A choice field's `props.optionsFrom`, when it is a well-formed `{ source: "host", key }`, with its
111
- * `filter` when that is well formed too (`{ attribute, field }`, both non-empty text).
112
- */
113
- declare function optionsSource(field: FieldBlock): OptionsSource | undefined;
114
- /**
115
- * The definition with each `optionsFrom: { source: "host" }` field's options
116
- * filled in from `hostOptions`: its own `props.options` first, then the host's
117
- * list for its key (a value the field already has is not added twice). A key
118
- * the host does not supply adds nothing, so the field offers its own options
119
- * only. `createForm` and `validateSubmission` call it with their `hostOptions`,
120
- * so a respondent sees, and a submit accepts, exactly these. Returns
121
- * `definition` itself when no field takes options from the host.
122
- */
123
- declare function withHostOptions(definition: FormDefinition, hostOptions: HostOptions | undefined): FormDefinition;
124
- /** The optional words under the ends of a rating or NPS scale ("Poor", "Great"). */
125
- declare function scaleLabels(field: FieldBlock): {
126
- low?: string;
127
- high?: string;
128
- };
129
- /** How a rating is drawn: `"numbers"` (default) or `"stars"`. */
130
- declare function ratingDisplay(field: FieldBlock): "numbers" | "stars";
131
- /** A `number` field's limits: `min`, `max` and `step` (each optional; `step` is above 0). */
132
- declare function numberLimits(field: FieldBlock): {
133
- min?: number;
134
- max?: number;
135
- step?: number;
136
- };
137
- /** `YYYY-MM-DD`, a real calendar day (no 2026-02-30), years 0001 to 9999. */
138
- declare function isIsoDate(value: string): boolean;
139
- /** A `date` field's earliest and latest days (`YYYY-MM-DD`), when set. */
140
- declare function dateLimits(field: FieldBlock): {
141
- min?: string;
142
- max?: string;
143
- };
12
+ type PropsCheck = (props: Readonly<Record<string, unknown>>, check: PropsChecker) => void;
13
+ /** The helpers a `PropsCheck` reports through. Paths are relative to `props`. */
14
+ interface PropsChecker {
15
+ /** Report a problem with the setting at `path`. */
16
+ issue(path: string, message: string): void;
17
+ /** A required string (non-empty with `nonEmpty`). */
18
+ text(v: unknown, path: string, nonEmpty?: boolean): void;
19
+ /** An optional string. */
20
+ optionalText(v: unknown, path: string): void;
21
+ /** An optional true or false. */
22
+ optionalBoolean(v: unknown, path: string): void;
23
+ /** An optional whole number of at least `floor`. */
24
+ optionalInt(v: unknown, path: string, floor: number): void;
25
+ /** `low` may not exceed `high` when both are whole numbers. */
26
+ ordered(low: unknown, high: unknown, path: string, what: string): void;
27
+ /** An identifier: a non-empty string that is not an `Object.prototype` name. */
28
+ name(v: unknown, path: string, what: string): v is string;
29
+ }
144
30
 
145
31
  /**
146
32
  * Which options a choice question shows, and how they are picked: lists filtered by another
@@ -149,7 +35,10 @@ declare function dateLimits(field: FieldBlock): {
149
35
  * decide the same way.
150
36
  */
151
37
 
152
- /** Field types whose answer is one or more of their options. */
38
+ /**
39
+ * The built-in field types whose answer is one or more of their options. A host's own choice
40
+ * kinds (`FieldTypeManifest.choice`) aren't in it: ask `isChoiceField` or `choiceKind`.
41
+ */
153
42
  declare const CHOICE_FIELD_TYPES: ReadonlySet<string>;
154
43
  /** A choice field's `props.optionsFrom.filter` (`{ attribute, field }`), when it is well formed. */
155
44
  declare function optionFilter(field: FieldBlock): {
@@ -292,6 +181,235 @@ interface RestoredAnswers {
292
181
  */
293
182
  declare function answersFromSnapshots(definition: FormDefinition, snapshots: Readonly<Record<string, unknown>>): RestoredAnswers;
294
183
 
184
+ /** The JSON type a comparison literal must have to match an answer (`parseForm` warns otherwise). */
185
+ type LiteralType = "string" | "number" | "boolean";
186
+ /**
187
+ * A field kind, without any UI: the one definition the engine, `parseForm`, validation, the
188
+ * server and snapshots all read, so a kind is described once instead of in a switch in each.
189
+ * Renderers are registered separately per platform (`genesis-forms-react`), so a manifest runs
190
+ * anywhere. Every hook is optional except where noted; a missing one means "nothing special".
191
+ * `validate`, `checkProps`, `literal`, `snapshot` and `fromSnapshot` may throw (each fails safe,
192
+ * as its doc says); `isEmpty`, `normalize` and `summarize` must not.
193
+ */
194
+ interface FieldTypeManifest {
195
+ type: string;
196
+ label: string;
197
+ category: "text" | "choice" | "scale" | "date" | "media" | "advanced";
198
+ /** `json: false` types must convert, so drafts and server replay never lose an answer. */
199
+ value: {
200
+ json: true;
201
+ } | {
202
+ json: false;
203
+ serialize: (value: unknown) => Json;
204
+ deserialize: (json: Json) => unknown;
205
+ };
206
+ operators: Operator[];
207
+ a11y: {
208
+ semantics: "control" | "group";
209
+ /** The HTML `autocomplete` token, such as `"email"` or `"tel"`. */
210
+ autocomplete?: string;
211
+ /** The HTML `inputmode` hint for on-screen keyboards, such as `"email"` or `"tel"`. */
212
+ inputMode?: "text" | "email" | "tel" | "numeric" | "decimal" | "url" | "search";
213
+ };
214
+ /**
215
+ * The props schema: `parseForm` calls it with the field's `props` (`{}` when it has none) and
216
+ * reports what it finds as issues at `blocks[i].props.<path>`. A check that throws is reported
217
+ * as one issue, never thrown.
218
+ */
219
+ checkProps?: PropsCheck;
220
+ /**
221
+ * The answer's shape. `choice`: the answer is one of `props.options` (`multiple: false`, a
222
+ * text) or several (`multiple: true`, a list of texts), so the field takes `optionsFrom`,
223
+ * `prefill`, `other` and filters, and its answers are snapshotted with their labels. Without
224
+ * its own `validate` (or `summarize`) it gets the built-in one for its shape, so an answer is
225
+ * always one of its options (or a typed "Other" when `props.other` allows it).
226
+ */
227
+ choice?: {
228
+ multiple: boolean;
229
+ };
230
+ /**
231
+ * The JSON type a logic literal compared with this field must have, or a function of its props
232
+ * (`undefined`: not known). `parseForm` warns about a literal that can never match.
233
+ */
234
+ literal?: LiteralType | ((props: Readonly<Record<string, unknown>>) => LiteralType | undefined);
235
+ /** The empty-check: whether a value counts as no answer. Default: nothing, null, blank text or `[]`. */
236
+ isEmpty?: (value: Json | undefined) => boolean;
237
+ /**
238
+ * The validator: an error message, or null when the (non-empty) value is valid. A validator
239
+ * that throws refuses the answer (`VALIDATOR_FAILED_MESSAGE`) rather than throwing.
240
+ */
241
+ validate?: (value: Json, field: FieldBlock) => string | null;
242
+ /** The display text: plain text for recall, exports and emails. */
243
+ summarize?: (value: Json, field: FieldBlock) => string;
244
+ /**
245
+ * The form a (valid, non-empty) value is stored in, such as an email
246
+ * without surrounding spaces. Applied by `engine.answers()` and the server.
247
+ */
248
+ normalize?: (value: Json, field: FieldBlock) => Json;
249
+ /**
250
+ * The snapshot of a (non-empty) answer as it was when stored, for `answerSnapshots`: the
251
+ * labels it showed, so history keeps them when the source changes. Choice kinds have one
252
+ * built in (for fields with `optionsFrom`); give another kind one here.
253
+ */
254
+ snapshot?: (value: Json, field: FieldBlock) => AnswerSnapshot | undefined;
255
+ /**
256
+ * The reverse of `snapshot`, for `answersFromSnapshots`: the answer to prefill from a stored
257
+ * snapshot (or none), and the picks that no longer exist.
258
+ */
259
+ fromSnapshot?: (snapshot: unknown, field: FieldBlock) => {
260
+ value?: Json;
261
+ missing: OptionSnapshot[];
262
+ };
263
+ /** Valid example answers, used by `coverage()` and other test tools to explore a path. */
264
+ samples?: (field: FieldBlock) => Json[];
265
+ }
266
+ /** The message an answer gets when its kind's validator throws. */
267
+ declare const VALIDATOR_FAILED_MESSAGE = "This answer couldn't be checked.";
268
+ interface RegisterOptions {
269
+ /**
270
+ * Replace a built-in kind (`email`, `single-choice`…). Off by default: the registry is shared
271
+ * by every form in the process, the server's checks included, so changing what a built-in
272
+ * accepts must be deliberate.
273
+ */
274
+ replace?: boolean;
275
+ }
276
+ /**
277
+ * Register a field kind, or replace one a host registered before (re-registering is allowed,
278
+ * so hot reload works). Throws on a malformed manifest, and on a built-in kind's name unless
279
+ * `{ replace: true }`. Registered kinds are frozen copies.
280
+ */
281
+ declare function registerFieldType(manifest: FieldTypeManifest, options?: RegisterOptions): void;
282
+ declare function getFieldType(type: string): FieldTypeManifest | undefined;
283
+ declare function listFieldTypes(): FieldTypeManifest[];
284
+ /** Whether `type` is one of the field kinds the package ships. */
285
+ declare function isBuiltInFieldType(type: string): boolean;
286
+ /** A choice kind's answer shape (`FieldTypeManifest.choice`), or undefined for any other kind. */
287
+ declare function choiceKind(type: string): {
288
+ multiple: boolean;
289
+ } | undefined;
290
+ /** Whether a block is a field of a choice kind: its answer is one or more of its options. */
291
+ declare function isChoiceField(block: unknown): block is FieldBlock;
292
+ /**
293
+ * A content kind (a block that shows something and takes no answer): `heading` and `text` are
294
+ * built in; a host registers others (a message screen, a media block) with
295
+ * `registerContentType`, plus a renderer per platform.
296
+ */
297
+ interface ContentTypeManifest {
298
+ type: string;
299
+ label: string;
300
+ /** The props schema, as for a field kind; the block's `props`, `{}` when it has none. */
301
+ checkProps?: PropsCheck;
302
+ /** The display text (recall, exports, a renderer with no special drawing). Default: `block.text`. */
303
+ text?: (block: ContentBlock) => string;
304
+ }
305
+ /** Register a content kind. Throws on a malformed manifest or a built-in kind's name (unless `{ replace: true }`). */
306
+ declare function registerContentType(manifest: ContentTypeManifest, options?: RegisterOptions): void;
307
+ declare function getContentType(type: string): ContentTypeManifest | undefined;
308
+ declare function listContentTypes(): ContentTypeManifest[];
309
+ /** A content block's display text: its kind's `text` (when it gives a string without throwing), or `block.text`. */
310
+ declare function contentText(block: ContentBlock): string;
311
+ declare function isEmptyValue(value: Json | undefined, field?: FieldBlock): boolean;
312
+ /** A value in its stored form (see `FieldTypeManifest.normalize`). */
313
+ declare function normalizeValue(value: Json, field: FieldBlock): Json;
314
+ /** Whether a block holds personal or secret data: `sensitive: true`, or the `props.sensitive: true` alias. */
315
+ declare function isSensitive(block: Block): boolean;
316
+ declare function summarizeValue(value: Json | undefined, field: FieldBlock): string;
317
+ interface ChoiceOption {
318
+ value: string;
319
+ label?: string;
320
+ /** A muted line under the label, such as a location's address. At most `OPTION_LIMITS.description` characters. */
321
+ description?: string;
322
+ /** A small picture beside the label: an `https://` URL, at most `OPTION_LIMITS.image` characters. */
323
+ image?: string;
324
+ /**
325
+ * Facts about the option for filtering and recall, such as `{ location: ["riverside", "hilltop"] }`
326
+ * for a coach who works at two locations (see `props.optionsFrom.filter`). Names and values are
327
+ * text; a value is one text or a list of them.
328
+ */
329
+ attributes?: Record<string, string | string[]>;
330
+ }
331
+ /** Bounds on an option's extra details. A longer or malformed detail is dropped when options are read. */
332
+ declare const OPTION_LIMITS: {
333
+ /** Characters in a description. */
334
+ readonly description: 500;
335
+ /** Characters in an image URL. */
336
+ readonly image: 2048;
337
+ /** Attributes on one option. */
338
+ readonly attributes: 20;
339
+ /** Characters in an attribute's name. */
340
+ readonly attributeName: 64;
341
+ /** Characters in one attribute value. */
342
+ readonly attributeValue: 200;
343
+ /** Values in one attribute's list. */
344
+ readonly attributeItems: 50;
345
+ };
346
+ /**
347
+ * Whether `url` is an `https://` link with a host and no spaces or control characters, within
348
+ * `OPTION_LIMITS.image`. A pattern rather than `new URL`, so it behaves the same in React Native.
349
+ */
350
+ declare function isHttpsUrl(url: unknown): url is string;
351
+ /**
352
+ * An option from untrusted JSON (a definition, the host's list): its string `value`, and its
353
+ * `label`, `description`, `image` and `attributes` when each is well formed and within
354
+ * `OPTION_LIMITS`; anything else is left out. Null when there is no string value.
355
+ */
356
+ declare function readOption(raw: unknown): ChoiceOption | null;
357
+ /** An option as JSON, with only the details it has. */
358
+ declare function optionJson(option: ChoiceOption): Json;
359
+ /** The longest answer an "Other" option takes. */
360
+ declare const OTHER_MAX_LENGTH = 200;
361
+ /**
362
+ * A choice question's "Other" option (`props.other: true` or `{ label }`): the respondent types
363
+ * their own answer, stored as the answer's value itself (single choice, dropdown) or as one more
364
+ * item (multiple choice). Null when the question has none.
365
+ */
366
+ declare function otherOption(field: FieldBlock): {
367
+ label: string;
368
+ } | null;
369
+ /** Whether a value is a typed "Other" answer: text that isn't one of the question's options. */
370
+ declare function isOtherValue(field: FieldBlock, value: unknown): value is string;
371
+ declare function choiceOptions(field: FieldBlock): ChoiceOption[];
372
+ /**
373
+ * Option lists the app showing a form supplies, by key (`OptionsSource.key`):
374
+ * `{ coaches: [{ value: "8c1…", label: "Ada Lovelace" }] }`.
375
+ */
376
+ type HostOptions = Readonly<Record<string, readonly ChoiceOption[]>>;
377
+ /**
378
+ * A choice field's `props.optionsFrom`, when it is a well-formed `{ source: "host", key }`, with its
379
+ * `filter` when that is well formed too (`{ attribute, field }`, both non-empty text).
380
+ */
381
+ declare function optionsSource(field: FieldBlock): OptionsSource | undefined;
382
+ /**
383
+ * The definition with each `optionsFrom: { source: "host" }` field's options
384
+ * filled in from `hostOptions`: its own `props.options` first, then the host's
385
+ * list for its key (a value the field already has is not added twice). A key
386
+ * the host does not supply adds nothing, so the field offers its own options
387
+ * only. `createForm` and `validateSubmission` call it with their `hostOptions`,
388
+ * so a respondent sees, and a submit accepts, exactly these. Returns
389
+ * `definition` itself when no field takes options from the host.
390
+ */
391
+ declare function withHostOptions(definition: FormDefinition, hostOptions: HostOptions | undefined): FormDefinition;
392
+ /** The optional words under the ends of a rating or NPS scale ("Poor", "Great"). */
393
+ declare function scaleLabels(field: FieldBlock): {
394
+ low?: string;
395
+ high?: string;
396
+ };
397
+ /** How a rating is drawn: `"numbers"` (default) or `"stars"`. */
398
+ declare function ratingDisplay(field: FieldBlock): "numbers" | "stars";
399
+ /** A `number` field's limits: `min`, `max` and `step` (each optional; `step` is above 0). */
400
+ declare function numberLimits(field: FieldBlock): {
401
+ min?: number;
402
+ max?: number;
403
+ step?: number;
404
+ };
405
+ /** `YYYY-MM-DD`, a real calendar day (no 2026-02-30), years 0001 to 9999. */
406
+ declare function isIsoDate(value: string): boolean;
407
+ /** A `date` field's earliest and latest days (`YYYY-MM-DD`), when set. */
408
+ declare function dateLimits(field: FieldBlock): {
409
+ min?: string;
410
+ max?: string;
411
+ };
412
+
295
413
  /** The Standard Schema interface (standardschema.dev), as far as validation needs it. */
296
414
  interface StandardSchemaV1<T = unknown> {
297
415
  readonly "~standard": {
@@ -319,4 +437,4 @@ declare function validateField(field: FieldBlock, value: Json | undefined, optio
319
437
  custom?: FieldValidator;
320
438
  }): string | null;
321
439
 
322
- export { withHostOptions as $, type AnswerSnapshot as A, numberLimits as B, CHOICE_FIELD_TYPES as C, optionFilter as D, optionJson as E, type FieldValidator as F, optionMatches as G, type HostOptions as H, optionsSource as I, otherOption as J, prefillFromLink as K, type LinkParams as L, MAX_PREFILL_PARAM_LENGTH as M, prefillSetting as N, OPTION_LIMITS as O, type PrefillSetting as P, ratingDisplay as Q, REQUIRED_MESSAGE as R, SEARCHABLE_OPTION_COUNT as S, readOption as T, registerFieldType as U, respondentPath as V, scaleLabels as W, searchOptions as X, summarizeValue as Y, validateField as Z, visibleOptions as _, type ChoiceOption as a, type FieldTypeManifest as b, OTHER_MAX_LENGTH as c, type OptionSnapshot as d, type PrefillState as e, type RestoredAnswers as f, type StandardSchemaV1 as g, answerSnapshots as h, answersFromSnapshots as i, answersOnPath as j, choiceOptions as k, clearFilteredAnswers as l, dateLimits as m, filteredChoiceError as n, getFieldType as o, hasOptionFilters as p, hiddenByPrefill as q, isEmptyValue as r, isHttpsUrl as s, isIsoDate as t, isOtherValue as u, isPrefillParam as v, isSearchable as w, isSensitive as x, listFieldTypes as y, normalizeValue as z };
440
+ export { otherOption as $, type AnswerSnapshot as A, isChoiceField as B, CHOICE_FIELD_TYPES as C, isEmptyValue as D, isHttpsUrl as E, type FieldValidator as F, isIsoDate as G, type HostOptions as H, isOtherValue as I, isPrefillParam as J, isSearchable as K, type LinkParams as L, MAX_PREFILL_PARAM_LENGTH as M, isSensitive as N, OPTION_LIMITS as O, type PrefillSetting as P, listContentTypes as Q, REQUIRED_MESSAGE as R, SEARCHABLE_OPTION_COUNT as S, listFieldTypes as T, normalizeValue as U, VALIDATOR_FAILED_MESSAGE as V, numberLimits as W, optionFilter as X, optionJson as Y, optionMatches as Z, optionsSource as _, type ChoiceOption as a, prefillFromLink as a0, prefillSetting as a1, ratingDisplay as a2, readOption as a3, registerContentType as a4, registerFieldType as a5, respondentPath as a6, scaleLabels as a7, searchOptions as a8, summarizeValue as a9, validateField as aa, visibleOptions as ab, withHostOptions as ac, type ContentTypeManifest as b, type FieldTypeManifest as c, type LiteralType as d, OTHER_MAX_LENGTH as e, type OptionSnapshot as f, type PrefillState as g, type PropsCheck as h, type PropsChecker as i, type RegisterOptions as j, type RestoredAnswers as k, type StandardSchemaV1 as l, answerSnapshots as m, answersFromSnapshots as n, answersOnPath as o, choiceKind as p, choiceOptions as q, clearFilteredAnswers as r, contentText as s, dateLimits as t, filteredChoiceError as u, getContentType as v, getFieldType as w, hasOptionFilters as x, hiddenByPrefill as y, isBuiltInFieldType as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marktiderman/genesis-forms",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -78,9 +78,10 @@
78
78
  ],
79
79
  "devDependencies": {
80
80
  "@fast-check/vitest": "^0.2.4",
81
+ "@marktiderman/genesis-notifications": "^0.1.1",
81
82
  "fast-check": "^4.10.2",
82
83
  "tsup": "^8.3.0",
83
- "typescript": "^5.9.3",
84
+ "typescript": "~6.0.3",
84
85
  "vitest": "^4.1.12"
85
86
  },
86
87
  "scripts": {
@@ -1,122 +0,0 @@
1
- import { a as FieldBlock, J as Json } from './types-jqDKcmP4.cjs';
2
-
3
- /**
4
- * The `photo` field type: a respondent attaches up to a few photos, such as a
5
- * picture of broken equipment. The answer holds references the host's storage
6
- * handed back (`PhotoRef`), never the bytes: the renderer uploads through the
7
- * host (`onUpload` on genesis-forms-web's `Form`), so the engine and the
8
- * server stay storage-agnostic. A host's server should check that each
9
- * reference is a file it stored for this respondent before saving.
10
- *
11
- * Opt-in in a builder palette (`UPLOAD_FIELD_TYPES`), since a host must be
12
- * able to store the files.
13
- */
14
- declare const PHOTO_TYPE = "photo";
15
- /** Field types that need the host to store files; a builder offers them only when the host names them. */
16
- declare const UPLOAD_FIELD_TYPES: readonly ["photo"];
17
- /** One stored photo, as the host's upload returned it. */
18
- type PhotoRef = {
19
- /** The host's id for the stored file. */
20
- id: string;
21
- /** The file's name on the respondent's device. */
22
- name: string;
23
- /** Its MIME type, such as `image/jpeg`. */
24
- type: string;
25
- /** Bytes. */
26
- size: number;
27
- };
28
- type PhotoFormat = "jpeg" | "png" | "heic" | "webp";
29
- /** The MIME types each format covers. */
30
- declare const PHOTO_FORMATS: Record<PhotoFormat, readonly string[]>;
31
- declare const DEFAULT_PHOTO_FILES = 3;
32
- declare const MAX_PHOTO_FILES = 5;
33
- declare const DEFAULT_PHOTO_SIZE_MB = 10;
34
- declare const MAX_PHOTO_SIZE_MB = 25;
35
- /** The longest reference id and file name a photo answer may carry. */
36
- declare const MAX_PHOTO_ID_LENGTH = 200;
37
- declare const MAX_PHOTO_NAME_LENGTH = 255;
38
- declare const isPhotoFormat: (v: unknown) => v is PhotoFormat;
39
- interface PhotoLimits {
40
- maxFiles: number;
41
- maxSizeMb: number;
42
- maxBytes: number;
43
- formats: PhotoFormat[];
44
- /** Every MIME type the formats cover. */
45
- mimeTypes: string[];
46
- }
47
- /** A photo field's limits; a prop that is missing or out of range uses its default. */
48
- declare function photoLimits(field: FieldBlock): PhotoLimits;
49
- /** Whether `v` has a photo reference's shape (any type and size). */
50
- declare function isPhotoRef(v: unknown): v is PhotoRef;
51
- /** The well-formed references in a photo answer, as `{ id, name, type, size }` only. */
52
- declare function photoRefs(value: Json | undefined): PhotoRef[];
53
- /** "JPEG, PNG or WebP". */
54
- declare function photoFormatList(formats: readonly PhotoFormat[]): string;
55
- /** The message for a file of a type the field doesn't take. */
56
- declare const photoTypeMessage: (field: FieldBlock) => string;
57
- /** The message for a file over the field's size limit. */
58
- declare const photoSizeMessage: (field: FieldBlock) => string;
59
- /** The message for more photos than the field takes. */
60
- declare const photoCountMessage: (field: FieldBlock) => string;
61
-
62
- /**
63
- * The upload handshake's JSON, shared by the server (`createUploadHandler` in
64
- * `/server`) and the browser (genesis-forms-web's `signedUploadHost`). A
65
- * respondent's browser asks the app's server for one signed upload per file,
66
- * then sends the bytes straight to storage with it; the answer keeps only the
67
- * reference the server handed back.
68
- *
69
- * Moved up from the host app's feedback photo uploads; the field names and
70
- * status codes are the ones it used, so its browsers and server keep talking.
71
- */
72
-
73
- /**
74
- * What the browser POSTs (`content-type: application/json`, at most
75
- * `MAX_UPLOAD_REQUEST_BYTES`). Exactly one of `attemptId` (a new response: the
76
- * visit's attempt id, a UUID) and `edit` (an edit link's token, while changing
77
- * a sent response).
78
- */
79
- interface UploadRequest {
80
- /** The upload field's id. */
81
- fieldId: string;
82
- /** The file's name on the respondent's device. */
83
- name: string;
84
- /** Its MIME type. */
85
- type: string;
86
- /** Bytes. */
87
- size: number;
88
- attemptId?: string | undefined;
89
- edit?: string | undefined;
90
- }
91
- /**
92
- * Where the browser sends the bytes, as the app's storage signed it. Supabase
93
- * Storage hands back `path` and `token` (with `bucket`, for
94
- * `uploadToSignedUrl`); a store that signs a plain URL sets `url`, which
95
- * `signedUploadHost` PUTs the file to when no `send` is given.
96
- */
97
- interface SignedUpload {
98
- path: string;
99
- token: string;
100
- bucket?: string | undefined;
101
- url?: string | undefined;
102
- }
103
- /** 200: the reference the answer keeps, and where to send the bytes. */
104
- interface UploadGrant {
105
- ref: PhotoRef;
106
- upload: SignedUpload;
107
- }
108
- /**
109
- * Any other status: words to show the respondent, and for some a `code`:
110
- * `closed` (409, an edit while the form takes no changes), `bad_link` (403, an
111
- * edit link that doesn't check out) or `too_many_photos` (429, the attempt or
112
- * edit has as many uploads as the store allows; the name the host app's
113
- * browsers already know, kept for every upload field type).
114
- */
115
- interface UploadRefusal {
116
- error: string;
117
- code?: string | undefined;
118
- }
119
- /** The longest upload request body: a few fields of JSON. */
120
- declare const MAX_UPLOAD_REQUEST_BYTES = 4000;
121
-
122
- export { DEFAULT_PHOTO_FILES as D, MAX_PHOTO_FILES as M, type PhotoFormat as P, type SignedUpload as S, UPLOAD_FIELD_TYPES as U, type PhotoRef as a, DEFAULT_PHOTO_SIZE_MB as b, MAX_PHOTO_ID_LENGTH as c, MAX_PHOTO_NAME_LENGTH as d, MAX_PHOTO_SIZE_MB as e, MAX_UPLOAD_REQUEST_BYTES as f, PHOTO_FORMATS as g, PHOTO_TYPE as h, type PhotoLimits as i, type UploadGrant as j, type UploadRefusal as k, type UploadRequest as l, isPhotoFormat as m, isPhotoRef as n, photoFormatList as o, photoCountMessage as p, photoLimits as q, photoRefs as r, photoSizeMessage as s, photoTypeMessage as t };