@nodii/onboarding 0.0.1 → 0.1.0-dev.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 (50) hide show
  1. package/dist/chunk-34BQX2KR.js +164 -0
  2. package/dist/chunk-34BQX2KR.js.map +1 -0
  3. package/dist/chunk-4SWOVHZ2.js +434 -0
  4. package/dist/chunk-4SWOVHZ2.js.map +1 -0
  5. package/dist/chunk-7NS7ENJV.js +232 -0
  6. package/dist/chunk-7NS7ENJV.js.map +1 -0
  7. package/dist/chunk-AFCJLUPK.js +92 -0
  8. package/dist/chunk-AFCJLUPK.js.map +1 -0
  9. package/dist/chunk-AW7W3HAC.js +154 -0
  10. package/dist/chunk-AW7W3HAC.js.map +1 -0
  11. package/dist/chunk-BIFANV7M.js +22 -0
  12. package/dist/chunk-BIFANV7M.js.map +1 -0
  13. package/dist/chunk-BQCTDU5V.js +327 -0
  14. package/dist/chunk-BQCTDU5V.js.map +1 -0
  15. package/dist/chunk-JFIONONR.js +3 -0
  16. package/dist/chunk-JFIONONR.js.map +1 -0
  17. package/dist/chunk-SWMIIUXR.js +1162 -0
  18. package/dist/chunk-SWMIIUXR.js.map +1 -0
  19. package/dist/chunk-VH6FWDWI.js +3 -0
  20. package/dist/chunk-VH6FWDWI.js.map +1 -0
  21. package/dist/chunk-Z4PA6C2Z.js +3 -0
  22. package/dist/chunk-Z4PA6C2Z.js.map +1 -0
  23. package/dist/chunk-ZDY2I6KY.js +3 -0
  24. package/dist/chunk-ZDY2I6KY.js.map +1 -0
  25. package/dist/dynamic/index.d.ts +184 -0
  26. package/dist/dynamic/index.js +4 -0
  27. package/dist/dynamic/index.js.map +1 -0
  28. package/dist/form/index.d.ts +64 -0
  29. package/dist/form/index.js +5 -0
  30. package/dist/form/index.js.map +1 -0
  31. package/dist/index.d.ts +10 -2
  32. package/dist/index.js +14 -2
  33. package/dist/index.js.map +1 -0
  34. package/dist/model-IEyDI54w.d.ts +487 -0
  35. package/dist/recaptcha/index.d.ts +102 -0
  36. package/dist/recaptcha/index.js +4 -0
  37. package/dist/recaptcha/index.js.map +1 -0
  38. package/dist/schema-TfyBU6JW.d.ts +107 -0
  39. package/dist/signup-client-Bl6eUrxR.d.ts +259 -0
  40. package/dist/spec/index.d.ts +88 -0
  41. package/dist/spec/index.js +4 -0
  42. package/dist/spec/index.js.map +1 -0
  43. package/dist/submit/index.d.ts +106 -0
  44. package/dist/submit/index.js +6 -0
  45. package/dist/submit/index.js.map +1 -0
  46. package/dist/widget/index.d.ts +219 -0
  47. package/dist/widget/index.js +10 -0
  48. package/dist/widget/index.js.map +1 -0
  49. package/package.json +77 -6
  50. package/README.md +0 -5
@@ -0,0 +1,164 @@
1
+ import { DEFAULT_REQUEST_TIMEOUT_MS, fetchWithTimeout, OnboardingNetworkError, OnboardingRateLimitError, OnboardingServerError, OnboardingSpecInvalidError } from './chunk-AW7W3HAC.js';
2
+ import { z } from 'zod/v3';
3
+
4
+ var FIELD_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
5
+ var specFieldSchema = z.object({
6
+ key: z.string().min(1).regex(
7
+ FIELD_KEY_PATTERN,
8
+ "field key must contain only letters, digits, underscore, or hyphen"
9
+ ),
10
+ type: z.string().min(1),
11
+ label: z.string(),
12
+ required: z.boolean(),
13
+ validation: z.record(z.unknown()).optional(),
14
+ options: z.array(z.unknown()).optional(),
15
+ serviceSelection: z.boolean().optional(),
16
+ piiClass: z.enum(["none", "context", "always", "reference"]).optional()
17
+ });
18
+ var specStepSchema = z.object({
19
+ stage: z.enum(["lead", "prospect", "activation"]),
20
+ fields: z.array(specFieldSchema)
21
+ });
22
+ var onboardingSpecSchema = z.object({
23
+ specVersion: z.string().min(1),
24
+ onboarderTier: z.enum(["platform", "tenant"]),
25
+ botChallenge: z.object({
26
+ provider: z.literal("recaptcha"),
27
+ siteKey: z.string()
28
+ }),
29
+ forms: z.object({
30
+ // ADVISORY-ONLY (by design, plan §12.2 / D540): the Contact-Us form is
31
+ // INTENTIONALLY a fixed barebones shape (name/email/phone/message) and is
32
+ // NOT onboarder-configurable. `mountContactForm` renders that fixed set
33
+ // directly and does NOT read forms.contact.fields — the field is modeled
34
+ // (and validated) so the SDK can round-trip the full spec, but it drives
35
+ // no rendering. Only forms.signup is spec-driven (the dynamic stepper).
36
+ contact: z.object({ fields: z.array(specFieldSchema) }),
37
+ signup: z.object({ steps: z.array(specStepSchema) })
38
+ })
39
+ });
40
+ function validateOnboardingSpec(value) {
41
+ return onboardingSpecSchema.safeParse(value);
42
+ }
43
+
44
+ // src/spec/client.ts
45
+ var SPEC_PATH = "/api/v1/public/onboarding/spec";
46
+ var OnboardingSpecCache = class _OnboardingSpecCache {
47
+ byVersion = /* @__PURE__ */ new Map();
48
+ latest = /* @__PURE__ */ new Map();
49
+ /**
50
+ * Collision-proof composite key. Uses JSON.stringify of the tuple so an
51
+ * onboarder or version containing the delimiter can never alias another
52
+ * entry — and, critically, contains NO control characters (an earlier
53
+ * NUL-separator made git treat this whole file as binary/undiffable).
54
+ */
55
+ static versionKey(onboarder, specVersion) {
56
+ return JSON.stringify([onboarder, specVersion]);
57
+ }
58
+ get(onboarder, specVersion) {
59
+ return this.byVersion.get(
60
+ _OnboardingSpecCache.versionKey(onboarder, specVersion)
61
+ );
62
+ }
63
+ getLatest(onboarder) {
64
+ return this.latest.get(onboarder);
65
+ }
66
+ set(onboarder, spec) {
67
+ this.byVersion.set(
68
+ _OnboardingSpecCache.versionKey(onboarder, spec.specVersion),
69
+ spec
70
+ );
71
+ this.latest.set(onboarder, spec);
72
+ }
73
+ clear() {
74
+ this.byVersion.clear();
75
+ this.latest.clear();
76
+ }
77
+ };
78
+ var defaultSpecCache = new OnboardingSpecCache();
79
+ function signupFieldMap(spec) {
80
+ const map = /* @__PURE__ */ new Map();
81
+ for (const step of spec.forms.signup.steps) {
82
+ for (const field of step.fields) map.set(field.key, field);
83
+ }
84
+ return map;
85
+ }
86
+ function diffSpecFields(prev, next) {
87
+ const prevMap = signupFieldMap(prev);
88
+ const nextMap = signupFieldMap(next);
89
+ const added = [];
90
+ const removed = [];
91
+ const changed = [];
92
+ for (const key of nextMap.keys()) {
93
+ if (!prevMap.has(key)) added.push(key);
94
+ }
95
+ for (const [key, prevField] of prevMap) {
96
+ const nextField = nextMap.get(key);
97
+ if (!nextField) {
98
+ removed.push(key);
99
+ continue;
100
+ }
101
+ if (JSON.stringify(prevField) !== JSON.stringify(nextField)) {
102
+ changed.push(key);
103
+ }
104
+ }
105
+ return {
106
+ added: added.sort(),
107
+ removed: removed.sort(),
108
+ changed: changed.sort()
109
+ };
110
+ }
111
+ function buildUrl(baseUrl, path) {
112
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
113
+ return `${base}${path}`;
114
+ }
115
+ async function fetchOnboardingSpec(options) {
116
+ const cache = options.cache ?? defaultSpecCache;
117
+ if (options.preferCached) {
118
+ const cached = cache.getLatest(options.onboarder);
119
+ if (cached) return cached;
120
+ }
121
+ const fetchImpl = options.fetch ?? globalThis.fetch;
122
+ const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
123
+ const url = buildUrl(options.baseUrl, SPEC_PATH);
124
+ let response;
125
+ try {
126
+ response = await fetchWithTimeout(url, {
127
+ method: "GET",
128
+ timeoutMs,
129
+ fetchImpl
130
+ });
131
+ } catch (err) {
132
+ throw new OnboardingNetworkError(err);
133
+ }
134
+ if (response.status === 429) {
135
+ const retryAfterHeader = response.headers.get("retry-after");
136
+ const retryAfter = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : Number.NaN;
137
+ throw new OnboardingRateLimitError(
138
+ Number.isNaN(retryAfter) ? null : retryAfter
139
+ );
140
+ }
141
+ if (response.status < 200 || response.status >= 300) {
142
+ throw new OnboardingServerError(
143
+ response.status,
144
+ `Spec fetch failed with status ${response.status}`
145
+ );
146
+ }
147
+ let body;
148
+ try {
149
+ body = await response.json();
150
+ } catch {
151
+ throw new OnboardingSpecInvalidError("response body is not JSON");
152
+ }
153
+ const parsed = validateOnboardingSpec(body);
154
+ if (!parsed.success) {
155
+ const detail = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
156
+ throw new OnboardingSpecInvalidError(detail);
157
+ }
158
+ cache.set(options.onboarder, parsed.data);
159
+ return parsed.data;
160
+ }
161
+
162
+ export { FIELD_KEY_PATTERN, OnboardingSpecCache, defaultSpecCache, diffSpecFields, fetchOnboardingSpec, onboardingSpecSchema, specFieldSchema, specStepSchema, validateOnboardingSpec };
163
+ //# sourceMappingURL=chunk-34BQX2KR.js.map
164
+ //# sourceMappingURL=chunk-34BQX2KR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/model.ts","../src/spec/client.ts"],"names":[],"mappings":"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","file":"chunk-34BQX2KR.js","sourcesContent":["/**\n * D540 §1 onboarding-spec model — mirrors the LOCKED hub REST contract:\n * GET /api/v1/public/onboarding/spec (PublicOnboardingSpecResponse)\n *\n * Response shape (hub JSON Schema, both tiers identical):\n * {\n * specVersion: string (monotonic; echo on submit; stale ⇒ 409),\n * onboarderTier: \"platform\" | \"tenant\",\n * botChallenge: { provider: \"recaptcha\", siteKey: string },\n * forms: {\n * contact: { fields: SpecField[] },\n * signup: { steps: [{ stage: \"lead\"|\"prospect\"|\"activation\",\n * fields: SpecField[] }] }\n * }\n * }\n *\n * SpecField (required: key, type, label, required):\n * { key, type, label, required, validation?, options?, serviceSelection?,\n * piiClass?: \"none\"|\"context\"|\"always\"|\"reference\" }\n *\n * The Zod schema below validates a fetched spec BEFORE the SDK renders\n * anything from it — a malformed spec throws OnboardingSpecInvalidError\n * (a half-parsed spec could silently drop required fields).\n */\n\nimport { z } from \"zod/v3\";\n\nexport type OnboarderTier = \"platform\" | \"tenant\";\nexport type PiiClass = \"none\" | \"context\" | \"always\" | \"reference\";\nexport type SpecStage = \"lead\" | \"prospect\" | \"activation\";\nexport type TerminalStage = \"prospect\" | \"inactive\" | \"active\";\n\n/**\n * Field keys are interpolated into DOM element ids and (historically) CSS\n * selectors by the widget layer. Constrain them to a strict identifier\n * alphabet so a MALICIOUS or compromised onboarder spec cannot smuggle CSS\n * metacharacters or selector-breakout payloads (e.g. `evil], [onclick=…`)\n * through the fetched spec. A spec with an out-of-alphabet key is REJECTED\n * at parse time → OnboardingSpecInvalidError (fail-closed; never rendered).\n * This is layer 1; the widget also CSS.escapes ids as defense-in-depth.\n */\nexport const FIELD_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;\n\nexport const specFieldSchema = z.object({\n key: z\n .string()\n .min(1)\n .regex(\n FIELD_KEY_PATTERN,\n \"field key must contain only letters, digits, underscore, or hyphen\",\n ),\n type: z.string().min(1),\n label: z.string(),\n required: z.boolean(),\n validation: z.record(z.unknown()).optional(),\n options: z.array(z.unknown()).optional(),\n serviceSelection: z.boolean().optional(),\n piiClass: z.enum([\"none\", \"context\", \"always\", \"reference\"]).optional(),\n});\n\nexport type SpecField = z.infer<typeof specFieldSchema>;\n\nexport const specStepSchema = z.object({\n stage: z.enum([\"lead\", \"prospect\", \"activation\"]),\n fields: z.array(specFieldSchema),\n});\n\nexport type SpecStep = z.infer<typeof specStepSchema>;\n\nexport const onboardingSpecSchema = z.object({\n specVersion: z.string().min(1),\n onboarderTier: z.enum([\"platform\", \"tenant\"]),\n botChallenge: z.object({\n provider: z.literal(\"recaptcha\"),\n siteKey: z.string(),\n }),\n forms: z.object({\n // ADVISORY-ONLY (by design, plan §12.2 / D540): the Contact-Us form is\n // INTENTIONALLY a fixed barebones shape (name/email/phone/message) and is\n // NOT onboarder-configurable. `mountContactForm` renders that fixed set\n // directly and does NOT read forms.contact.fields — the field is modeled\n // (and validated) so the SDK can round-trip the full spec, but it drives\n // no rendering. Only forms.signup is spec-driven (the dynamic stepper).\n contact: z.object({ fields: z.array(specFieldSchema) }),\n signup: z.object({ steps: z.array(specStepSchema) }),\n }),\n});\n\nexport type OnboardingSpec = z.infer<typeof onboardingSpecSchema>;\n\n/**\n * Parse an unknown value as an OnboardingSpec.\n * Returns a SafeParseReturnType (inferred).\n */\nexport function validateOnboardingSpec(value: unknown) {\n return onboardingSpecSchema.safeParse(value);\n}\n","/**\n * Config-fetch client for GET /api/v1/public/onboarding/spec (D540 §1/§3).\n *\n * - The onboarder is derived SERVER-side from the request Host (D536) —\n * it is NOT sent by the client. The `onboarder` value here is purely the\n * CLIENT-side cache key (D540 §3: \"cache key (onboarder, specVersion)\").\n * - The spec response is validated against the LOCKED D540 shape before\n * it is returned; a malformed spec throws OnboardingSpecInvalidError.\n * - Cache semantics: every successful fetch is stored under\n * (onboarder, specVersion) and as the onboarder's \"latest\". The versioned\n * entries let the 409 spec_stale flow diff the previously-served version\n * against the refetched one (see `diffSpecFields` + the signup widget's\n * handleSpecStale, which reads the cached prior version before refetching).\n */\n\nimport {\n OnboardingNetworkError,\n OnboardingRateLimitError,\n OnboardingServerError,\n OnboardingSpecInvalidError,\n} from \"../submit/errors.js\";\nimport {\n DEFAULT_REQUEST_TIMEOUT_MS,\n fetchWithTimeout,\n} from \"../submit/http.js\";\nimport {\n validateOnboardingSpec,\n type OnboardingSpec,\n type SpecField,\n} from \"./model.js\";\n\nconst SPEC_PATH = \"/api/v1/public/onboarding/spec\";\n\n/**\n * In-memory spec cache keyed by (onboarder, specVersion), with a \"latest\"\n * pointer per onboarder. Purely client-side; no TTL — the reject-stale\n * policy (409 spec_stale on submit) is the authoritative staleness signal,\n * and a refetch overwrites \"latest\".\n */\nexport class OnboardingSpecCache {\n private readonly byVersion = new Map<string, OnboardingSpec>();\n private readonly latest = new Map<string, OnboardingSpec>();\n\n /**\n * Collision-proof composite key. Uses JSON.stringify of the tuple so an\n * onboarder or version containing the delimiter can never alias another\n * entry — and, critically, contains NO control characters (an earlier\n * NUL-separator made git treat this whole file as binary/undiffable).\n */\n private static versionKey(onboarder: string, specVersion: string): string {\n return JSON.stringify([onboarder, specVersion]);\n }\n\n get(onboarder: string, specVersion: string): OnboardingSpec | undefined {\n return this.byVersion.get(\n OnboardingSpecCache.versionKey(onboarder, specVersion),\n );\n }\n\n getLatest(onboarder: string): OnboardingSpec | undefined {\n return this.latest.get(onboarder);\n }\n\n set(onboarder: string, spec: OnboardingSpec): void {\n this.byVersion.set(\n OnboardingSpecCache.versionKey(onboarder, spec.specVersion),\n spec,\n );\n this.latest.set(onboarder, spec);\n }\n\n clear(): void {\n this.byVersion.clear();\n this.latest.clear();\n }\n}\n\n/** Module-level default cache (one per page). Injectable for tests/apps. */\nexport const defaultSpecCache = new OnboardingSpecCache();\n\n/**\n * Diff two spec versions' signup field sets — used by the 409 spec_stale\n * flow to tell the user WHAT changed. Compares by field key across all\n * signup steps.\n */\nexport interface SpecFieldDiff {\n added: string[];\n removed: string[];\n changed: string[];\n}\n\nfunction signupFieldMap(spec: OnboardingSpec): Map<string, SpecField> {\n const map = new Map<string, SpecField>();\n for (const step of spec.forms.signup.steps) {\n for (const field of step.fields) map.set(field.key, field);\n }\n return map;\n}\n\nexport function diffSpecFields(\n prev: OnboardingSpec,\n next: OnboardingSpec,\n): SpecFieldDiff {\n const prevMap = signupFieldMap(prev);\n const nextMap = signupFieldMap(next);\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n for (const key of nextMap.keys()) {\n if (!prevMap.has(key)) added.push(key);\n }\n for (const [key, prevField] of prevMap) {\n const nextField = nextMap.get(key);\n if (!nextField) {\n removed.push(key);\n continue;\n }\n if (JSON.stringify(prevField) !== JSON.stringify(nextField)) {\n changed.push(key);\n }\n }\n return {\n added: added.sort(),\n removed: removed.sort(),\n changed: changed.sort(),\n };\n}\n\nexport interface SpecFetchOptions {\n baseUrl: string;\n /**\n * Client-side cache key ONLY (never transmitted; trust is host-derived\n * per D536). Use the onboarder slug/id your embed is configured with.\n */\n onboarder: string;\n /** Custom fetch implementation (defaults to globalThis.fetch). */\n fetch?: typeof globalThis.fetch;\n /** Cache to read/write (defaults to the module-level cache). */\n cache?: OnboardingSpecCache;\n /**\n * Request timeout in ms (default 15s). A stalled intake aborts with\n * OnboardingNetworkError so the boot doesn't hang on \"Loading signup form…\".\n */\n timeoutMs?: number;\n /**\n * When true, return the cached \"latest\" for this onboarder if present\n * WITHOUT a network round-trip. Default false (spec fetches are cheap\n * and the server bumps specVersion on any change). The signup widget's\n * boot passes preferCached:true so a remount reuses the loaded spec.\n */\n preferCached?: boolean;\n}\n\nfunction buildUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n}\n\n/**\n * Fetch (and validate, and cache) the onboarding spec.\n *\n * @throws {OnboardingSpecInvalidError} response does not match D540 §1\n * @throws {OnboardingRateLimitError} on 429\n * @throws {OnboardingServerError} on any other non-2xx status\n * @throws {OnboardingNetworkError} on network failure OR timeout\n */\nexport async function fetchOnboardingSpec(\n options: SpecFetchOptions,\n): Promise<OnboardingSpec> {\n const cache = options.cache ?? defaultSpecCache;\n\n if (options.preferCached) {\n const cached = cache.getLatest(options.onboarder);\n if (cached) return cached;\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = buildUrl(options.baseUrl, SPEC_PATH);\n\n let response: Response;\n try {\n response = await fetchWithTimeout(url, {\n method: \"GET\",\n timeoutMs,\n fetchImpl,\n });\n } catch (err) {\n throw new OnboardingNetworkError(err);\n }\n\n if (response.status === 429) {\n const retryAfterHeader = response.headers.get(\"retry-after\");\n const retryAfter = retryAfterHeader\n ? Number.parseInt(retryAfterHeader, 10)\n : Number.NaN;\n throw new OnboardingRateLimitError(\n Number.isNaN(retryAfter) ? null : retryAfter,\n );\n }\n if (response.status < 200 || response.status >= 300) {\n throw new OnboardingServerError(\n response.status,\n `Spec fetch failed with status ${response.status}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new OnboardingSpecInvalidError(\"response body is not JSON\");\n }\n\n const parsed = validateOnboardingSpec(body);\n if (!parsed.success) {\n const detail = parsed.error.issues\n .slice(0, 5)\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \");\n throw new OnboardingSpecInvalidError(detail);\n }\n\n cache.set(options.onboarder, parsed.data);\n return parsed.data;\n}\n"]}
@@ -0,0 +1,434 @@
1
+ import { z } from 'zod/v3';
2
+
3
+ // src/dynamic/derive.ts
4
+ function optionValue(option) {
5
+ if (option !== null && typeof option === "object") {
6
+ const o = option;
7
+ if ("value" in o) return String(o["value"]);
8
+ if ("key" in o) return String(o["key"]);
9
+ }
10
+ return String(option);
11
+ }
12
+ function knownValidation(field) {
13
+ const v = field.validation ?? {};
14
+ const out = {};
15
+ if (typeof v["minLength"] === "number") out.minLength = v["minLength"];
16
+ if (typeof v["maxLength"] === "number") out.maxLength = v["maxLength"];
17
+ if (typeof v["min"] === "number") out.min = v["min"];
18
+ if (typeof v["max"] === "number") out.max = v["max"];
19
+ if (typeof v["pattern"] === "string") out.pattern = v["pattern"];
20
+ if (typeof v["format"] === "string") out.format = v["format"];
21
+ return out;
22
+ }
23
+ function stringSchema(field) {
24
+ const v = knownValidation(field);
25
+ let s = z.string();
26
+ if (field.type === "email" || v.format === "email") {
27
+ s = s.email(`${field.label} must be a valid email`);
28
+ }
29
+ if (v.minLength !== void 0) {
30
+ s = s.min(
31
+ v.minLength,
32
+ `${field.label} must be at least ${v.minLength} characters`
33
+ );
34
+ }
35
+ if (v.maxLength !== void 0) {
36
+ s = s.max(
37
+ v.maxLength,
38
+ `${field.label} must be at most ${v.maxLength} characters`
39
+ );
40
+ }
41
+ if (v.pattern !== void 0) {
42
+ try {
43
+ s = s.regex(
44
+ new RegExp(v.pattern),
45
+ `${field.label} has an invalid format`
46
+ );
47
+ } catch {
48
+ }
49
+ }
50
+ return s;
51
+ }
52
+ function numberSchema(field) {
53
+ const v = knownValidation(field);
54
+ let s = z.coerce.number({
55
+ invalid_type_error: `${field.label} must be a number`
56
+ });
57
+ if (v.min !== void 0) {
58
+ s = s.min(v.min, `${field.label} must be at least ${v.min}`);
59
+ }
60
+ if (v.max !== void 0) {
61
+ s = s.max(v.max, `${field.label} must be at most ${v.max}`);
62
+ }
63
+ return s;
64
+ }
65
+ function enumSchema(field) {
66
+ const allowed = (field.options ?? []).map(optionValue);
67
+ if (allowed.length === 0) return z.string();
68
+ return z.string().refine((val) => allowed.includes(val), {
69
+ message: `${field.label} must be one of the offered options`
70
+ });
71
+ }
72
+ function multiEnumSchema(field) {
73
+ const allowed = (field.options ?? []).map(optionValue);
74
+ const item = allowed.length === 0 ? z.string() : z.string().refine((val) => allowed.includes(val), {
75
+ message: `${field.label} contains a value outside the offered options`
76
+ });
77
+ return z.array(item);
78
+ }
79
+ function dateSchema(field) {
80
+ return z.string().refine((val) => !Number.isNaN(Date.parse(val)), {
81
+ message: `${field.label} must be a valid date`
82
+ });
83
+ }
84
+ function baseSchema(field) {
85
+ switch (field.type) {
86
+ case "email":
87
+ return stringSchema(field);
88
+ case "number":
89
+ return numberSchema(field);
90
+ case "checkbox":
91
+ case "boolean":
92
+ return z.boolean();
93
+ case "select":
94
+ case "radio":
95
+ return enumSchema(field);
96
+ case "multiselect":
97
+ return multiEnumSchema(field);
98
+ case "date":
99
+ return dateSchema(field);
100
+ default:
101
+ return stringSchema(field);
102
+ }
103
+ }
104
+ function deriveFieldSchema(field) {
105
+ const base = baseSchema(field);
106
+ if (field.required) {
107
+ if (field.type !== "checkbox" && field.type !== "boolean" && field.type !== "number" && field.type !== "multiselect") {
108
+ return z.string({ required_error: `${field.label} is required` }).min(1, `${field.label} is required`).pipe(base);
109
+ }
110
+ if (field.type === "number") {
111
+ return z.any().refine(
112
+ (val) => val !== "" && val !== void 0 && val !== null,
113
+ `${field.label} is required`
114
+ ).pipe(base);
115
+ }
116
+ if (field.type === "multiselect") {
117
+ return base.refine(
118
+ (val) => Array.isArray(val) && val.length > 0,
119
+ `${field.label} requires at least one selection`
120
+ );
121
+ }
122
+ return base;
123
+ }
124
+ return z.preprocess(
125
+ (val) => val === "" || val === void 0 || val === null ? void 0 : val,
126
+ base.optional()
127
+ );
128
+ }
129
+ function deriveStepSchema(step) {
130
+ const shape = {};
131
+ for (const field of step.fields) {
132
+ shape[field.key] = deriveFieldSchema(field);
133
+ }
134
+ return z.object(shape).strict();
135
+ }
136
+ function deriveAnswersSchema(steps) {
137
+ const shape = {};
138
+ for (const step of steps) {
139
+ for (const field of step.fields) {
140
+ shape[field.key] = deriveFieldSchema(field);
141
+ }
142
+ }
143
+ return z.object(shape).strict();
144
+ }
145
+ var credentialsSchema = z.object({
146
+ email: z.string().email("A valid account email is required"),
147
+ password: z.string().min(12, "Password must be at least 12 characters").max(256, "Password must be at most 256 characters")
148
+ });
149
+ function zodIssuesToFieldErrors(issues) {
150
+ return issues.map((issue) => ({
151
+ path: issue.path.join("."),
152
+ message: issue.message
153
+ }));
154
+ }
155
+ function serviceIdsOf(key, value) {
156
+ if (typeof value === "string" && value !== "") return [value];
157
+ if (Array.isArray(value)) return value.map((v) => String(v));
158
+ if (value === true) return [key];
159
+ return [];
160
+ }
161
+ function createSignupStepper(spec) {
162
+ let steps = spec.forms.signup.steps;
163
+ let specVersion = spec.specVersion;
164
+ let state = {
165
+ stepIndex: 0,
166
+ stepCount: steps.length,
167
+ currentStep: steps[0] ?? null,
168
+ answers: {},
169
+ selectedServices: [],
170
+ errors: [],
171
+ accountErrors: [],
172
+ consent: { tosAccepted: false, privacyAccepted: false },
173
+ credentials: { email: "", password: "" },
174
+ status: "editing",
175
+ serverError: null,
176
+ specStale: false,
177
+ specVersion,
178
+ terminalStage: null
179
+ };
180
+ let validatedSteps = /* @__PURE__ */ new Set();
181
+ const listeners = /* @__PURE__ */ new Set();
182
+ function emit() {
183
+ for (const listener of listeners) {
184
+ listener(structuredClone(state));
185
+ }
186
+ }
187
+ function knownFieldKeys() {
188
+ const keys = /* @__PURE__ */ new Set();
189
+ for (const step of steps) {
190
+ for (const field of step.fields) keys.add(field.key);
191
+ }
192
+ return keys;
193
+ }
194
+ function stepAnswers(step) {
195
+ const out = {};
196
+ for (const field of step.fields) {
197
+ if (field.key in state.answers) out[field.key] = state.answers[field.key];
198
+ }
199
+ return out;
200
+ }
201
+ function deriveSelectedServices() {
202
+ const services = [];
203
+ for (const step of steps) {
204
+ for (const field of step.fields) {
205
+ if (field.serviceSelection === true) {
206
+ services.push(...serviceIdsOf(field.key, state.answers[field.key]));
207
+ }
208
+ }
209
+ }
210
+ return [...new Set(services)];
211
+ }
212
+ function runStepValidation(step) {
213
+ const result = deriveStepSchema(step).safeParse(stepAnswers(step));
214
+ return result.success ? [] : zodIssuesToFieldErrors(result.error.issues);
215
+ }
216
+ function firstStepWithErrors() {
217
+ for (let i = 0; i < steps.length; i++) {
218
+ const step = steps[i];
219
+ if (step && runStepValidation(step).length > 0) return i;
220
+ }
221
+ return 0;
222
+ }
223
+ return {
224
+ getState() {
225
+ return structuredClone(state);
226
+ },
227
+ setAnswer(key, value) {
228
+ state = {
229
+ ...state,
230
+ answers: { ...state.answers, [key]: value }
231
+ };
232
+ state.selectedServices = deriveSelectedServices();
233
+ if (validatedSteps.has(state.stepIndex) && state.currentStep) {
234
+ state = { ...state, errors: runStepValidation(state.currentStep) };
235
+ }
236
+ emit();
237
+ },
238
+ setConsent(consent) {
239
+ state = { ...state, consent: { ...state.consent, ...consent } };
240
+ emit();
241
+ },
242
+ setCredentials(credentials) {
243
+ state = {
244
+ ...state,
245
+ credentials: { ...state.credentials, ...credentials }
246
+ };
247
+ emit();
248
+ },
249
+ validateStep() {
250
+ if (!state.currentStep) return true;
251
+ validatedSteps.add(state.stepIndex);
252
+ const errors = runStepValidation(state.currentStep);
253
+ state = { ...state, errors };
254
+ emit();
255
+ return errors.length === 0;
256
+ },
257
+ next() {
258
+ if (!state.currentStep) return false;
259
+ validatedSteps.add(state.stepIndex);
260
+ const errors = runStepValidation(state.currentStep);
261
+ if (errors.length > 0) {
262
+ state = { ...state, errors };
263
+ emit();
264
+ return false;
265
+ }
266
+ if (state.stepIndex >= steps.length - 1) return false;
267
+ const stepIndex = state.stepIndex + 1;
268
+ state = {
269
+ ...state,
270
+ stepIndex,
271
+ currentStep: steps[stepIndex] ?? null,
272
+ errors: []
273
+ };
274
+ emit();
275
+ return true;
276
+ },
277
+ back() {
278
+ if (state.stepIndex === 0) return;
279
+ const stepIndex = state.stepIndex - 1;
280
+ state = {
281
+ ...state,
282
+ stepIndex,
283
+ currentStep: steps[stepIndex] ?? null,
284
+ errors: []
285
+ };
286
+ emit();
287
+ },
288
+ isFirstStep() {
289
+ return state.stepIndex === 0;
290
+ },
291
+ isLastStep() {
292
+ return state.stepIndex >= steps.length - 1;
293
+ },
294
+ validateAll() {
295
+ for (let i = 0; i < steps.length; i++) validatedSteps.add(i);
296
+ const answersResult = deriveAnswersSchema(steps).safeParse(state.answers);
297
+ const errors = answersResult.success ? [] : zodIssuesToFieldErrors(answersResult.error.issues);
298
+ const accountErrors = [];
299
+ const credResult = credentialsSchema.safeParse(state.credentials);
300
+ if (!credResult.success) {
301
+ accountErrors.push(...zodIssuesToFieldErrors(credResult.error.issues));
302
+ }
303
+ if (state.consent.tosAccepted !== true) {
304
+ accountErrors.push({
305
+ path: "tosAccepted",
306
+ message: "You must accept the Terms of Service"
307
+ });
308
+ }
309
+ if (state.consent.privacyAccepted !== true) {
310
+ accountErrors.push({
311
+ path: "privacyAccepted",
312
+ message: "You must accept the Privacy Policy"
313
+ });
314
+ }
315
+ state = { ...state, errors, accountErrors };
316
+ emit();
317
+ return errors.length === 0 && accountErrors.length === 0;
318
+ },
319
+ buildSubmitPayload() {
320
+ if (state.consent.tosAccepted !== true || state.consent.privacyAccepted !== true) {
321
+ throw new Error(
322
+ "buildSubmitPayload: cannot build a submit payload without genuine user consent (tosAccepted && privacyAccepted must both be true). Call validateAll() and surface the consent errors to the user first."
323
+ );
324
+ }
325
+ const parsed = deriveAnswersSchema(steps).safeParse(state.answers);
326
+ if (!parsed.success) {
327
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(answers)"}: ${i.message}`).join("; ");
328
+ throw new Error(
329
+ `buildSubmitPayload: answers failed validation \u2014 call validateAll() first (${detail}).`
330
+ );
331
+ }
332
+ const known = knownFieldKeys();
333
+ const answers = {};
334
+ for (const [key, value] of Object.entries(parsed.data)) {
335
+ if (known.has(key) && value !== void 0 && value !== "") {
336
+ answers[key] = value;
337
+ }
338
+ }
339
+ const selectedServices = deriveSelectedServices();
340
+ return {
341
+ specVersion,
342
+ answers,
343
+ ...selectedServices.length > 0 ? { selectedServices } : {},
344
+ consent: {
345
+ tosAccepted: state.consent.tosAccepted,
346
+ privacyAccepted: state.consent.privacyAccepted
347
+ },
348
+ credentials: { ...state.credentials }
349
+ };
350
+ },
351
+ applyNewSpec(newSpec) {
352
+ steps = newSpec.forms.signup.steps;
353
+ specVersion = newSpec.specVersion;
354
+ const known = /* @__PURE__ */ new Set();
355
+ for (const step of steps) {
356
+ for (const field of step.fields) known.add(field.key);
357
+ }
358
+ const answers = {};
359
+ for (const [key, value] of Object.entries(state.answers)) {
360
+ if (known.has(key)) answers[key] = value;
361
+ }
362
+ state = {
363
+ ...state,
364
+ answers,
365
+ stepCount: steps.length,
366
+ specVersion,
367
+ specStale: true,
368
+ status: "editing",
369
+ serverError: null
370
+ };
371
+ state.selectedServices = deriveSelectedServices();
372
+ const stepIndex = Math.min(firstStepWithErrors(), steps.length - 1);
373
+ const currentStep = steps[Math.max(stepIndex, 0)] ?? null;
374
+ validatedSteps = /* @__PURE__ */ new Set([Math.max(stepIndex, 0)]);
375
+ state = {
376
+ ...state,
377
+ stepIndex: Math.max(stepIndex, 0),
378
+ currentStep,
379
+ errors: currentStep ? runStepValidation(currentStep) : []
380
+ };
381
+ emit();
382
+ },
383
+ acknowledgeSpecStale() {
384
+ state = { ...state, specStale: false };
385
+ emit();
386
+ },
387
+ setSubmitting() {
388
+ state = {
389
+ ...state,
390
+ status: "submitting",
391
+ serverError: null
392
+ };
393
+ emit();
394
+ },
395
+ setSuccess(terminalStage) {
396
+ state = { ...state, status: "success", terminalStage, serverError: null };
397
+ emit();
398
+ },
399
+ setError(message) {
400
+ state = { ...state, status: "error", serverError: message };
401
+ emit();
402
+ },
403
+ reset() {
404
+ validatedSteps = /* @__PURE__ */ new Set();
405
+ state = {
406
+ stepIndex: 0,
407
+ stepCount: steps.length,
408
+ currentStep: steps[0] ?? null,
409
+ answers: {},
410
+ selectedServices: [],
411
+ errors: [],
412
+ accountErrors: [],
413
+ consent: { tosAccepted: false, privacyAccepted: false },
414
+ credentials: { email: "", password: "" },
415
+ status: "editing",
416
+ serverError: null,
417
+ specStale: false,
418
+ specVersion,
419
+ terminalStage: null
420
+ };
421
+ emit();
422
+ },
423
+ subscribe(listener) {
424
+ listeners.add(listener);
425
+ return () => {
426
+ listeners.delete(listener);
427
+ };
428
+ }
429
+ };
430
+ }
431
+
432
+ export { createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue };
433
+ //# sourceMappingURL=chunk-4SWOVHZ2.js.map
434
+ //# sourceMappingURL=chunk-4SWOVHZ2.js.map