@marktiderman/genesis-forms 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +146 -2
- package/conformance/after-class-back-after-a-jump.scenario.json +217 -0
- package/conformance/after-class-contact-yes-completed.scenario.json +230 -0
- package/conformance/after-class-contact-yes-requires-name-and-email.scenario.json +218 -0
- package/conformance/after-class-happy-path.scenario.json +237 -0
- package/conformance/after-class-invalid-email-rejected.scenario.json +227 -0
- package/conformance/after-class-low-rating-skips-went-well.scenario.json +223 -0
- package/conformance/after-class-server-drops-unknown-and-hidden-answers.scenario.json +236 -0
- package/conformance/after-class-server-ignores-a-client-sent-coach.scenario.json +219 -0
- package/conformance/contact-details-anonymous-form-skips-identifying-fields.scenario.json +125 -0
- package/conformance/contact-details-named-form-asks-for-identifying-fields.scenario.json +129 -0
- package/conformance/monthly-pulse-default-ending.scenario.json +148 -0
- package/conformance/monthly-pulse-promoter-ending-via-jump.scenario.json +128 -0
- package/dist/engine-Cp5t2_Am.d.ts +75 -0
- package/dist/engine-LsPRTzt3.d.cts +75 -0
- package/dist/index.cjs +1205 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +374 -0
- package/dist/index.d.ts +374 -0
- package/dist/index.js +1174 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +988 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +52 -0
- package/dist/server.d.ts +52 -0
- package/dist/server.js +984 -0
- package/dist/server.js.map +1 -0
- package/dist/testing.cjs +1299 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +129 -0
- package/dist/testing.d.ts +129 -0
- package/dist/testing.js +1293 -0
- package/dist/testing.js.map +1 -0
- package/dist/validate-j8Z188mF.d.cts +175 -0
- package/dist/validate-j8Z188mF.d.ts +175 -0
- package/package.json +79 -3
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The form definition format. Everything here is plain JSON: a definition can
|
|
3
|
+
* be stored, diffed, versioned and evaluated identically in a browser, in
|
|
4
|
+
* React Native and on a server.
|
|
5
|
+
*/
|
|
6
|
+
type Json = string | number | boolean | null | Json[] | {
|
|
7
|
+
[key: string]: Json;
|
|
8
|
+
};
|
|
9
|
+
interface FieldBlock {
|
|
10
|
+
kind: "field";
|
|
11
|
+
/** Immutable. Answers are keyed by this. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Human / export name. Renamable. Used by recall and the builders. */
|
|
14
|
+
key: string;
|
|
15
|
+
/** Registry key: "rating", "short-text", "acme/signature". */
|
|
16
|
+
type: string;
|
|
17
|
+
/** Supports recall: `{{field:name|there}}`. */
|
|
18
|
+
label: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Personal or secret data (an SSN, a health detail). Tools such as devtools
|
|
24
|
+
* mask it. `props.sensitive: true` is read as an alias; see `isSensitive`.
|
|
25
|
+
*/
|
|
26
|
+
sensitive?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Identifies the respondent (name, email, phone). When the form's
|
|
29
|
+
* `settings.anonymous` is true the field is treated as hidden: off the
|
|
30
|
+
* path, not validated, and its value is never stored.
|
|
31
|
+
*/
|
|
32
|
+
identifying?: boolean;
|
|
33
|
+
props?: Record<string, Json>;
|
|
34
|
+
}
|
|
35
|
+
interface ContentBlock {
|
|
36
|
+
kind: "content";
|
|
37
|
+
id: string;
|
|
38
|
+
type: "heading" | "text";
|
|
39
|
+
text: string;
|
|
40
|
+
}
|
|
41
|
+
interface PageBreakBlock {
|
|
42
|
+
kind: "page-break";
|
|
43
|
+
id: string;
|
|
44
|
+
title?: string;
|
|
45
|
+
}
|
|
46
|
+
type Block = FieldBlock | ContentBlock | PageBreakBlock;
|
|
47
|
+
/**
|
|
48
|
+
* A typed value that does not come from a question.
|
|
49
|
+
* - `url`: read from the query string. Untrusted.
|
|
50
|
+
* - `host`: passed by the app hosting the form. Untrusted.
|
|
51
|
+
* - `server`: resolved by the server when the form loads and again on
|
|
52
|
+
* submit. Never read from the client.
|
|
53
|
+
*/
|
|
54
|
+
interface HiddenInput {
|
|
55
|
+
name: string;
|
|
56
|
+
type: "string" | "number" | "boolean";
|
|
57
|
+
source: "url" | "host" | "server";
|
|
58
|
+
/** Masked in devtools unless revealed, like a sensitive field. */
|
|
59
|
+
sensitive?: boolean;
|
|
60
|
+
}
|
|
61
|
+
type Operator = "eq" | "neq" | "lt" | "lte" | "gt" | "gte" | "in" | "contains" | "answered" | "empty";
|
|
62
|
+
type Operand = {
|
|
63
|
+
field: string;
|
|
64
|
+
} | {
|
|
65
|
+
hidden: string;
|
|
66
|
+
} | {
|
|
67
|
+
value: Json;
|
|
68
|
+
};
|
|
69
|
+
type Condition = {
|
|
70
|
+
all: Condition[];
|
|
71
|
+
} | {
|
|
72
|
+
any: Condition[];
|
|
73
|
+
} | {
|
|
74
|
+
not: Condition;
|
|
75
|
+
} | {
|
|
76
|
+
op: Operator;
|
|
77
|
+
left: Operand;
|
|
78
|
+
right?: Operand;
|
|
79
|
+
};
|
|
80
|
+
type LogicRule = {
|
|
81
|
+
id: string;
|
|
82
|
+
when: Condition;
|
|
83
|
+
effect: "show" | "hide";
|
|
84
|
+
target: string;
|
|
85
|
+
} | {
|
|
86
|
+
id: string;
|
|
87
|
+
when: Condition;
|
|
88
|
+
effect: "jump";
|
|
89
|
+
from: string;
|
|
90
|
+
to: string | {
|
|
91
|
+
ending: string;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
interface Ending {
|
|
95
|
+
id: string;
|
|
96
|
+
title: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
}
|
|
99
|
+
interface FormSettings {
|
|
100
|
+
/**
|
|
101
|
+
* A per-form admin setting, off by default. When true, fields marked
|
|
102
|
+
* `identifying` are not asked or stored, events carry no timestamps, and
|
|
103
|
+
* the app's store keeps no identifying metadata with responses.
|
|
104
|
+
*/
|
|
105
|
+
anonymous?: boolean;
|
|
106
|
+
/** Clear the values of fields hidden by logic on submit. Default true. */
|
|
107
|
+
clearHiddenValues?: boolean;
|
|
108
|
+
}
|
|
109
|
+
type FormLayout = {
|
|
110
|
+
mode: "classic";
|
|
111
|
+
} | {
|
|
112
|
+
mode: "focus";
|
|
113
|
+
};
|
|
114
|
+
interface FormDefinition {
|
|
115
|
+
/** Version of the definition format itself. */
|
|
116
|
+
schemaVersion: 1;
|
|
117
|
+
id: string;
|
|
118
|
+
title: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
layout?: FormLayout;
|
|
121
|
+
blocks: Block[];
|
|
122
|
+
hidden?: HiddenInput[];
|
|
123
|
+
logic?: LogicRule[];
|
|
124
|
+
endings?: Ending[];
|
|
125
|
+
settings?: FormSettings;
|
|
126
|
+
/** Free for apps and plugins; preserved round-trip. */
|
|
127
|
+
meta?: Record<string, Json>;
|
|
128
|
+
}
|
|
129
|
+
/** One thing a respondent did. A response is the replay of its events. */
|
|
130
|
+
type FormEvent = {
|
|
131
|
+
type: "answer";
|
|
132
|
+
field: string;
|
|
133
|
+
value: Json | undefined;
|
|
134
|
+
at?: number;
|
|
135
|
+
/** `"prefill"`: set by `initialValues` when the form was created, not typed by the respondent. */
|
|
136
|
+
source?: "prefill";
|
|
137
|
+
} | {
|
|
138
|
+
type: "next";
|
|
139
|
+
at?: number;
|
|
140
|
+
} | {
|
|
141
|
+
type: "back";
|
|
142
|
+
at?: number;
|
|
143
|
+
} | {
|
|
144
|
+
type: "submit";
|
|
145
|
+
at?: number;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** The Standard Schema interface (standardschema.dev), as far as validation needs it. */
|
|
149
|
+
interface StandardSchemaV1<T = unknown> {
|
|
150
|
+
readonly "~standard": {
|
|
151
|
+
readonly version: 1;
|
|
152
|
+
readonly vendor: string;
|
|
153
|
+
readonly validate: (value: unknown) => {
|
|
154
|
+
value: T;
|
|
155
|
+
issues?: undefined;
|
|
156
|
+
} | {
|
|
157
|
+
issues: ReadonlyArray<{
|
|
158
|
+
message: string;
|
|
159
|
+
}>;
|
|
160
|
+
} | Promise<unknown>;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** A custom check for one field: a function, or any Standard Schema (Zod, Valibot, ArkType…). */
|
|
164
|
+
type FieldValidator = ((value: Json) => string | null) | StandardSchemaV1;
|
|
165
|
+
declare const REQUIRED_MESSAGE = "This question needs an answer.";
|
|
166
|
+
/**
|
|
167
|
+
* Validate one field's value. `requireAnswers: false` (drafts) checks the
|
|
168
|
+
* format of what was answered without requiring the rest.
|
|
169
|
+
*/
|
|
170
|
+
declare function validateField(field: FieldBlock, value: Json | undefined, options?: {
|
|
171
|
+
requireAnswers?: boolean;
|
|
172
|
+
custom?: FieldValidator;
|
|
173
|
+
}): string | null;
|
|
174
|
+
|
|
175
|
+
export { type Block as B, type ContentBlock as C, type Ending as E, type FieldBlock as F, type HiddenInput as H, type Json as J, type LogicRule as L, type Operator as O, type PageBreakBlock as P, REQUIRED_MESSAGE as R, type StandardSchemaV1 as S, type FormDefinition as a, type FormLayout as b, type FormSettings as c, type Condition as d, type FieldValidator as e, type FormEvent as f, type Operand as g, validateField as v };
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marktiderman/genesis-forms",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"./dist/index.js",
|
|
8
|
+
"./dist/index.cjs",
|
|
9
|
+
"./dist/server.js",
|
|
10
|
+
"./dist/server.cjs",
|
|
11
|
+
"./dist/testing.js",
|
|
12
|
+
"./dist/testing.cjs"
|
|
13
|
+
],
|
|
14
|
+
"description": "Genesis Forms engine: a serializable form definition, logic, navigation, validation and server-side checking. Zero dependencies; runs in browsers, React Native, Node and edge runtimes. Part of the Genesis framework.",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/marktiderman/genesis.git",
|
|
18
|
+
"directory": "packages/forms"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": false
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
"./package.json": "./package.json",
|
|
30
|
+
".": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"./server": {
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/server.d.ts",
|
|
43
|
+
"default": "./dist/server.js"
|
|
44
|
+
},
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "./dist/server.d.cts",
|
|
47
|
+
"default": "./dist/server.cjs"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"./testing": {
|
|
51
|
+
"import": {
|
|
52
|
+
"types": "./dist/testing.d.ts",
|
|
53
|
+
"default": "./dist/testing.js"
|
|
54
|
+
},
|
|
55
|
+
"require": {
|
|
56
|
+
"types": "./dist/testing.d.cts",
|
|
57
|
+
"default": "./dist/testing.cjs"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"./conformance/*": "./conformance/*"
|
|
61
|
+
},
|
|
62
|
+
"files": [
|
|
63
|
+
"dist",
|
|
64
|
+
"conformance",
|
|
65
|
+
"README.md"
|
|
66
|
+
],
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@fast-check/vitest": "^0.2.4",
|
|
69
|
+
"fast-check": "^4.10.2",
|
|
70
|
+
"tsup": "^8.3.0",
|
|
71
|
+
"typescript": "^5.9.3",
|
|
72
|
+
"vitest": "^4.1.12"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsup && node ../../scripts/check-exports-resolve.mjs && tsc -p type-tests",
|
|
76
|
+
"clean": "rm -rf dist",
|
|
77
|
+
"typecheck": "tsc --noEmit",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:watch": "vitest",
|
|
80
|
+
"conformance:build": "vite-node scripts/build-conformance.ts"
|
|
81
|
+
}
|
|
6
82
|
}
|