@embeddables/forms 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -119,6 +119,7 @@ functions, pass validators via `customValidations` on `initForm`.
119
119
  | ------ | ------- |
120
120
  | `set({ … })` | Validate and persist a patch atomically; optional analytics; best-effort durable R2 write when configured |
121
121
  | `get(key)` / `getAll()` | Read declared fields from storage |
122
+ | `getValueByProtocolFieldId(protocolFieldId)` | Read by schema `protocolFieldId` (typed like `get()` for the backing field; `undefined` if unknown or unset) |
122
123
  | `validate({ … })` | Check values without writing or tracking |
123
124
  | `submit()` | Validate all fields, best-effort durable R2 write, and emit `form:submitted` when analytics is configured |
124
125
  | `errors()` | Current validation messages |
@@ -141,7 +142,6 @@ backend writes are also best-effort: a failing persistence request never rejects
141
142
  | ------ | ------- |
142
143
  | `core` | Initialized `@embeddables/core` instance (required) |
143
144
  | `analyticsInstance` | Optional analytics client for event tracking only |
144
- | `baseUrl` | Optional backend URL for durable R2 persistence writes |
145
145
 
146
146
  **`initForm`**
147
147
 
@@ -31,6 +31,7 @@ var ValidatorError = class extends FormsError {
31
31
  this.name = "ValidatorError";
32
32
  }
33
33
  };
34
+ const DEVELOPMENT_BASE_URL = void 0;
34
35
  /**
35
36
  * Returns null when persistence cannot be configured (missing publishable key
36
37
  * or fetch). The SDK keeps the no-op default in that case.
@@ -49,7 +50,7 @@ function resolvePersistenceConfig(config) {
49
50
  projectId,
50
51
  appUserId,
51
52
  publishableKey,
52
- baseUrl: config.baseUrl ?? "https://backend-worker.heysavvy.workers.dev",
53
+ baseUrl: config.baseUrl ?? DEVELOPMENT_BASE_URL ?? "https://backend-worker.heysavvy.workers.dev",
53
54
  fetch: fetchImpl,
54
55
  timeoutMs: config.timeoutMs ?? 1e4
55
56
  };
@@ -216,7 +217,7 @@ function writeFields({ storage, formKey, fields, fieldDefinitions }) {
216
217
  type: field.type,
217
218
  label: field.label,
218
219
  ...field.registryId === void 0 ? {} : { registryId: field.registryId },
219
- ...field.protocolQuestionId === void 0 ? {} : { protocolQuestionId: field.protocolQuestionId }
220
+ ...field.protocolFieldId === void 0 ? {} : { protocolFieldId: field.protocolFieldId }
220
221
  };
221
222
  }
222
223
  const next = {
@@ -269,7 +270,7 @@ function isStoredField(value) {
269
270
  if (!isSerializable({ value: value["value"] })) return false;
270
271
  if (typeof value["type"] !== "string" || !FIELD_TYPES$1.includes(value["type"]) || typeof value["label"] !== "string") return false;
271
272
  if (value["registryId"] !== void 0 && typeof value["registryId"] !== "string") return false;
272
- if (value["protocolQuestionId"] !== void 0 && typeof value["protocolQuestionId"] !== "string") return false;
273
+ if (value["protocolFieldId"] !== void 0 && typeof value["protocolFieldId"] !== "string") return false;
273
274
  return true;
274
275
  }
275
276
  //#endregion
@@ -318,7 +319,7 @@ function buildFieldUpdatedEvents({ fields, patch }) {
318
319
  field_value: value
319
320
  };
320
321
  if (field?.registryId !== void 0) event.registry_field_id = field.registryId;
321
- if (field?.protocolQuestionId !== void 0) event.protocol_field_id = field.protocolQuestionId;
322
+ if (field?.protocolFieldId !== void 0) event.protocol_field_id = field.protocolFieldId;
322
323
  return event;
323
324
  });
324
325
  }
@@ -408,8 +409,8 @@ function validateField({ field, path, seenKeys, patterns }) {
408
409
  if (typeof type !== "string" || !FIELD_TYPES.includes(type)) throw new SchemaError(`${path}.type: must be one of ${FIELD_TYPES.join(", ")}`);
409
410
  const registryId = field["registryId"];
410
411
  if (registryId !== void 0 && typeof registryId !== "string") throw new SchemaError(`${path}.registryId: must be a string`);
411
- const protocolQuestionId = field["protocolQuestionId"];
412
- if (protocolQuestionId !== void 0 && typeof protocolQuestionId !== "string") throw new SchemaError(`${path}.protocolQuestionId: must be a string`);
412
+ const protocolFieldId = field["protocolFieldId"];
413
+ if (protocolFieldId !== void 0 && typeof protocolFieldId !== "string") throw new SchemaError(`${path}.protocolFieldId: must be a string`);
413
414
  const validations = field["validations"];
414
415
  if (validations === void 0) return;
415
416
  validateValidations({
@@ -688,8 +689,11 @@ function mergeCustomValidations({ schema, customValidations }) {
688
689
  * API — the Miro signature. Consumers pass only `core` and, optionally, an
689
690
  * analytics client.
690
691
  */
691
- function initForms(options) {
692
- return createFormsClient(options);
692
+ function initForms({ core, analyticsInstance }) {
693
+ return createFormsClient({
694
+ core,
695
+ analyticsInstance
696
+ });
693
697
  }
694
698
  function createFormsClient({ core, analyticsInstance, baseUrl, storage, persistence }) {
695
699
  if (!hasRequiredCoreMethods(core)) throw new FormsError("initForms requires an initialized Embeddables core instance.");
@@ -711,6 +715,11 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
711
715
  customValidations
712
716
  }) });
713
717
  const declared = new Map(resolved.fields.map((field) => [field.key, field]));
718
+ const keyByProtocolFieldId = /* @__PURE__ */ new Map();
719
+ for (const field of resolved.fields) {
720
+ const protocolId = field.protocolFieldId;
721
+ if (typeof protocolId === "string" && protocolId.length > 0) keyByProtocolFieldId.set(protocolId, field.key);
722
+ }
714
723
  const resolvedStorage = resolveStorage({ storage });
715
724
  const listeners = /* @__PURE__ */ new Set();
716
725
  const notify = () => {
@@ -741,10 +750,10 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
741
750
  }
742
751
  if (mergedCount > 0) notify();
743
752
  };
744
- const recoverable = resolved.fields.filter((field) => (field.registryId !== void 0 || field.protocolQuestionId !== void 0) && state.bag[field.key] === void 0).map((field) => ({
753
+ const recoverable = resolved.fields.filter((field) => (field.registryId !== void 0 || field.protocolFieldId !== void 0) && state.bag[field.key] === void 0).map((field) => ({
745
754
  key: field.key,
746
755
  ...field.registryId === void 0 ? {} : { registryId: field.registryId },
747
- ...field.protocolQuestionId === void 0 ? {} : { protocolQuestionId: field.protocolQuestionId }
756
+ ...field.protocolFieldId === void 0 ? {} : { protocolFieldId: field.protocolFieldId }
748
757
  }));
749
758
  if (recoverable.length > 0) try {
750
759
  const result = persistence.recoverRegistryFields({
@@ -854,7 +863,7 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
854
863
  key,
855
864
  value,
856
865
  ...field?.registryId === void 0 ? {} : { registryId: field.registryId },
857
- ...field?.protocolQuestionId === void 0 ? {} : { protocolQuestionId: field.protocolQuestionId }
866
+ ...field?.protocolFieldId === void 0 ? {} : { protocolFieldId: field.protocolFieldId }
858
867
  };
859
868
  });
860
869
  firePersistence(() => persistence.savePartial({
@@ -903,6 +912,11 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
903
912
  if (!declared.has(fieldKey)) return void 0;
904
913
  return state.bag[fieldKey];
905
914
  };
915
+ const getValueByProtocolFieldId = ((protocolFieldId) => {
916
+ const fieldKey = keyByProtocolFieldId.get(protocolFieldId);
917
+ if (fieldKey === void 0) return void 0;
918
+ return get(fieldKey);
919
+ });
906
920
  const getAll = () => narrow(state.bag);
907
921
  const submit = () => {
908
922
  const snapshot = narrow(readBag());
@@ -1046,6 +1060,7 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
1046
1060
  key: schema.id,
1047
1061
  set,
1048
1062
  get,
1063
+ getValueByProtocolFieldId,
1049
1064
  getAll,
1050
1065
  submit,
1051
1066
  validate,
@@ -1057,4 +1072,4 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
1057
1072
  //#endregion
1058
1073
  export { ValidatorError as a, SchemaError as i, FORM_DATA_KEY as n, FormsError as r, initForms as t };
1059
1074
 
1060
- //# sourceMappingURL=form-B3vBKRYJ.js.map
1075
+ //# sourceMappingURL=form-CUYofmuL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-CUYofmuL.js","names":["FIELD_TYPES","isObjectLike","items"],"sources":["../src/errors.ts","../src/storage/persistence-config.ts","../src/storage/persistence.ts","../src/storage/persistence-client.ts","../src/storage/storage.ts","../src/core/analytics.ts","../src/core/resolve.ts","../src/core/validation.ts","../src/core/form.ts"],"sourcesContent":["/**\n * Typed error hierarchy. Every failure this SDK raises on its own behalf is an\n * instance of one of these, so consumers branch on the type\n * (`if (e instanceof SchemaError) …`) instead of string-matching messages.\n * Catch `FormsError` to handle them all.\n *\n * An error thrown by a consumer's own custom validator is never wrapped in one\n * of these — it propagates with its original type and stack.\n */\n\n/** Base class for every error the SDK throws. */\nexport class FormsError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'FormsError'\n }\n}\n\n/** The schema is malformed. */\nexport class SchemaError extends FormsError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'SchemaError'\n }\n}\n\n/** A custom validator returned a thenable, or a shape that is not a message. */\nexport class ValidatorError extends FormsError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'ValidatorError'\n }\n}\n","import { isValidPublishableKey } from '@embeddables/core'\nimport type { EmbeddablesInstance } from '@embeddables/core'\n\nexport const DEFAULT_BASE_URL = 'https://backend-worker.heysavvy.workers.dev'\nexport const DEFAULT_TIMEOUT_MS = 10_000\n\ndeclare const __EMBEDDABLES_API_URL_FOR_DEVELOPMENT__: string | undefined\n\nconst DEVELOPMENT_BASE_URL =\n typeof __EMBEDDABLES_API_URL_FOR_DEVELOPMENT__ === 'undefined'\n ? undefined\n : __EMBEDDABLES_API_URL_FOR_DEVELOPMENT__\n\nexport interface PersistenceClientConfig {\n core: EmbeddablesInstance\n /** Takes precedence over the key exposed by the core instance. */\n publishableKey?: string\n baseUrl?: string\n fetch?: typeof fetch\n timeoutMs?: number\n}\n\nexport interface ResolvedPersistenceConfig {\n core: EmbeddablesInstance\n projectId: string\n appUserId: string\n publishableKey: string\n baseUrl: string\n fetch: typeof fetch\n timeoutMs: number\n}\n\n/**\n * Returns null when persistence cannot be configured (missing publishable key\n * or fetch). The SDK keeps the no-op default in that case.\n */\nexport function resolvePersistenceConfig(\n config: PersistenceClientConfig,\n): ResolvedPersistenceConfig | null {\n const core = config.core\n const publishableKey = config.publishableKey ?? core.getPublishableKey()\n if (!publishableKey || !isValidPublishableKey(publishableKey)) {\n return null\n }\n\n const projectId = core.getProjectId()\n const appUserId = core.getAppUserId()\n if (!projectId || !appUserId) {\n return null\n }\n\n const fetchImpl =\n config.fetch ??\n (typeof globalThis.fetch === 'function' ? globalThis.fetch.bind(globalThis) : undefined)\n if (!fetchImpl) {\n return null\n }\n\n return {\n core,\n projectId,\n appUserId,\n publishableKey,\n baseUrl: config.baseUrl ?? DEVELOPMENT_BASE_URL ?? DEFAULT_BASE_URL,\n fetch: fetchImpl,\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n }\n}\n","import type { JsonValue } from '@embeddables/shared-types/json'\n\n// * Provisional payload shapes. The real R2 key scheme and the Supabase table\n// * (and therefore these argument shapes) are blocked on the Jeremy meeting.\n// * Everything here is a drop-in behind this port: when the table lands, the\n// * only edits are a real implementation plus, if the table forces it, these\n// * shapes and their call sites in form.ts together.\n\n/** A changed field plus the registry/protocol ids the persistence layer keys on. */\nexport interface PersistedField {\n readonly key: string\n readonly value: JsonValue\n readonly registryId?: string\n readonly protocolFieldId?: string\n}\n\n/** A field whose stored value the persistence layer may be asked to recover. */\nexport interface RecoverableField {\n readonly key: string\n readonly registryId?: string\n readonly protocolFieldId?: string\n}\n\n/**\n * The seam between the SDK and durable storage (R2 for raw form data, Supabase\n * for the queryable fields table). Every method is best-effort: a form must\n * work with the no-op mock, and a real implementation that throws or rejects\n * must never break `set` / `submit` / `initForm`.\n */\nexport interface FormsPersistence {\n /** Partial save to R2 on every successful `set`. */\n savePartial(args: { formKey: string; values: Record<string, JsonValue> }): void | Promise<void>\n /** Per-field save to the Supabase table on every successful `set`. */\n saveFields(args: { formKey: string; fields: readonly PersistedField[] }): void | Promise<void>\n /** Full submission save to R2 on `submit`. */\n saveSubmission(args: { formKey: string; values: Record<string, JsonValue> }): void | Promise<void>\n /**\n * Cross-form recovery from Supabase, consulted at `initForm` only for\n * registry/protocol fields absent from `localStorage`. localStorage always\n * wins; the returned map is merged only for still-absent keys.\n */\n recoverRegistryFields(args: {\n formKey: string\n fields: readonly RecoverableField[]\n }): Record<string, JsonValue> | Promise<Record<string, JsonValue>>\n}\n\n/**\n * The default: does nothing, never throws, and recovers nothing. With this in\n * place a form is pure local state, exactly as before the port existed.\n */\nexport function createNoopPersistence(): FormsPersistence {\n return {\n savePartial: () => undefined,\n saveFields: () => undefined,\n saveSubmission: () => undefined,\n recoverRegistryFields: () => ({}),\n }\n}\n","import { hc } from 'hono/client'\n\nimport type { FormsApiErrorCode } from '@embeddables/shared-types'\nimport type { ProblemBody } from '@embeddables/shared-types/errors'\n\nimport { resolvePersistenceConfig } from './persistence-config.js'\nimport { createNoopPersistence } from './persistence.js'\n\nimport type { PersistenceClientConfig, ResolvedPersistenceConfig } from './persistence-config.js'\nimport type { FormsPersistence } from './persistence.js'\nimport type { FormsAppType } from 'backend-worker'\nimport type { ClientResponse } from 'hono/client'\n\nconst PUBLISHABLE_KEY_HEADER = 'x-publishable-key'\n\ntype FormsRpc = ReturnType<typeof hc<FormsAppType>>\nconst hcWithType = (...args: Parameters<typeof hc>): FormsRpc => hc<FormsAppType>(...args)\n\ntype SuccessBody<R extends ClientResponse<unknown, number, string>> =\n R extends ClientResponse<infer T, infer _S, infer _F> ? T : never\n\nfunction withTimeout(fetchImpl: typeof fetch, timeoutMs: number): typeof fetch {\n return async (input, init) => {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n const path = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url\n\n try {\n return await fetchImpl(input, { ...init, signal: controller.signal })\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request to ${path} timed out after ${timeoutMs}ms`, { cause: error })\n }\n throw new Error(`Request to ${path} failed to reach the API`, { cause: error })\n } finally {\n clearTimeout(timer)\n }\n }\n}\n\nasync function rpcCall<R extends ClientResponse<unknown, number, string>>(\n fn: () => Promise<R>,\n): Promise<SuccessBody<R>> {\n const res = await fn()\n if (!res.ok) {\n const body: unknown = await res.json().catch(() => null)\n const problem = body as ProblemBody<FormsApiErrorCode> | null\n const message = problem?.detail ?? problem?.title ?? `forms persistence failed: ${res.status}`\n throw new Error(message)\n }\n return res.json() as Promise<SuccessBody<R>>\n}\n\nexport function createApiPersistence(config: ResolvedPersistenceConfig): FormsPersistence {\n const root = config.baseUrl.replace(/\\/+$/, '')\n const rpc = hcWithType(`${root}/forms`, {\n fetch: withTimeout(config.fetch, config.timeoutMs),\n headers: { [PUBLISHABLE_KEY_HEADER]: config.publishableKey },\n })\n\n return {\n savePartial({ formKey, values }) {\n return rpcCall(() =>\n rpc.v1.public.sessions.$post({\n json: {\n project_id: config.projectId,\n app_user_id: config.appUserId,\n form_id: formKey,\n data: values,\n },\n }),\n ).then(() => undefined)\n },\n saveFields: () => undefined,\n saveSubmission({ formKey, values }) {\n return rpcCall(() =>\n rpc.v1.public.submissions.$post({\n json: {\n project_id: config.projectId,\n app_user_id: config.appUserId,\n form_id: formKey,\n values,\n },\n }),\n ).then(() => undefined)\n },\n recoverRegistryFields: () => ({}),\n }\n}\n\nexport function resolveDefaultPersistence(config: PersistenceClientConfig): FormsPersistence {\n const resolved = resolvePersistenceConfig(config)\n if (!resolved) return createNoopPersistence()\n return createApiPersistence(resolved)\n}\n","import type { JsonValue } from '@embeddables/shared-types/json'\n\nimport type { FieldConfig, FieldType } from '../core/config.js'\n\n/** Every form on the origin shares this one entry, indexed by form ID. */\nexport const FORM_DATA_KEY = 'EMBEDDABLES-FORM-DATA'\n\nexport interface FormsStorage {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\ninterface StoredField {\n value: JsonValue\n type: FieldType\n label: string\n registryId?: string\n protocolFieldId?: string\n}\n\n/** Internal. The whole document: form ID → field key → self-describing field. */\ntype StoredForm = Record<string, StoredField>\ntype FormsDocument = Record<string, StoredForm>\n\nconst FIELD_TYPES: readonly FieldType[] = [\n 'text',\n 'email',\n 'number',\n 'boolean',\n 'select',\n 'multiselect',\n 'json',\n]\n\n// * Keyed by storage object identity so two form instances that share one\n// * store share one parsed document. Entries die with the storage object —\n// * this is not a name-guessable registry.\nconst LIVE_DOCUMENTS = new WeakMap<FormsStorage, FormsDocument>()\n\nfunction readDocumentFromStorage({ storage }: { storage: FormsStorage }): FormsDocument {\n try {\n const raw = storage.getItem(FORM_DATA_KEY)\n if (raw === null) return {}\n\n const parsed: unknown = JSON.parse(raw)\n if (!isObjectLike(parsed)) return {}\n\n // * Returned as-is, with no per-form validation: this function's job is to\n // * hand back exactly what is stored so a write can preserve it. A sibling\n // * whose value is a string or `null` is carried through untouched.\n return parsed as FormsDocument\n } catch {\n return {}\n }\n}\n\n/**\n * Load once per storage object; later calls reuse the in-memory document.\n *\n * There is no invalidation: a write from another tab or a user clearing site\n * data is never picked up, and the next write here overwrites it. Recovering\n * from an external mutation means constructing a new storage object.\n */\nfunction loadDocument({ storage }: { storage: FormsStorage }): FormsDocument {\n const cached = LIVE_DOCUMENTS.get(storage)\n if (cached) return cached\n\n const document = readDocumentFromStorage({ storage })\n LIVE_DOCUMENTS.set(storage, document)\n return document\n}\n\nexport function resolveStorage({ storage }: { storage?: FormsStorage }): FormsStorage {\n if (storage) return storage\n\n try {\n const candidate = globalThis.localStorage\n // * Safari private mode throws on access rather than being absent, so\n // * presence alone is not a usable probe — a throwaway read is.\n candidate.getItem(FORM_DATA_KEY)\n return candidate\n } catch {\n return createMemoryStorage()\n }\n}\n\n/**\n * A fresh in-memory shim seeded with the document as last read, for an instance\n * whose real storage started throwing mid-session.\n */\nexport function degradeToMemory({ storage }: { storage: FormsStorage }): FormsStorage {\n const shim = createMemoryStorage()\n // * Prefer the live snapshot: a throwing `getItem` after a quota failure\n // * would otherwise seed an empty shim and drop every sibling this instance\n // * already had in memory.\n const document = LIVE_DOCUMENTS.get(storage) ?? readDocumentFromStorage({ storage })\n const snapshot: FormsDocument = { ...document }\n shim.setItem(FORM_DATA_KEY, JSON.stringify(snapshot))\n LIVE_DOCUMENTS.set(shim, snapshot)\n return shim\n}\n\nexport function readFields({\n storage,\n formKey,\n fieldDefinitions,\n}: {\n storage: FormsStorage\n formKey: string\n fieldDefinitions: readonly FieldConfig[]\n}): Record<string, JsonValue> {\n const bag: unknown = loadDocument({ storage })[formKey]\n if (!isObjectLike(bag)) return {}\n\n const declared = new Set(fieldDefinitions.map((field) => field.key))\n const values: Record<string, JsonValue> = {}\n for (const [key, entry] of Object.entries(bag)) {\n if (!declared.has(key)) continue\n if (isStoredField(entry)) values[key] = entry.value\n }\n return values\n}\n\nexport function writeFields({\n storage,\n formKey,\n fields,\n fieldDefinitions,\n}: {\n storage: FormsStorage\n formKey: string\n fields: Record<string, JsonValue>\n fieldDefinitions: readonly FieldConfig[]\n}): void {\n // * Replaces exactly one subtree and carries every other form key through\n // * verbatim, including keys this page has no config for. Merging *within* the\n // * bag is the caller's job. A throwing `setItem` propagates so the form\n // * instance can degrade and report. The live snapshot is updated only after\n // * `setItem` succeeds, so a quota failure leaves memory matching storage.\n // ! `fields` is copied rather than stored by reference: the caller keeps its\n // ! own handle on that object, and aliasing it into the cached document would\n // ! make a later mutation there visible to every instance on this storage\n // ! without a write.\n const current = loadDocument({ storage })\n const currentForm = isObjectLike(current[formKey]) ? current[formKey] : {}\n const declaredKeys = new Set(fieldDefinitions.map((field) => field.key))\n const nextForm: Record<string, unknown> = {}\n\n for (const [key, entry] of Object.entries(currentForm)) {\n if (!declaredKeys.has(key)) nextForm[key] = entry\n }\n\n for (const field of fieldDefinitions) {\n const value = fields[field.key]\n if (value === undefined) continue\n\n nextForm[field.key] = {\n value,\n type: field.type,\n label: field.label,\n ...(field.registryId === undefined ? {} : { registryId: field.registryId }),\n ...(field.protocolFieldId === undefined ? {} : { protocolFieldId: field.protocolFieldId }),\n } satisfies StoredField\n }\n\n const next = { ...current, [formKey]: nextForm as StoredForm }\n storage.setItem(FORM_DATA_KEY, JSON.stringify(next))\n LIVE_DOCUMENTS.set(storage, next)\n}\n\nexport function removeFields({\n storage,\n formKey,\n}: {\n storage: FormsStorage\n formKey: string\n}): void {\n const { [formKey]: _dropped, ...rest } = loadDocument({ storage })\n\n if (Object.keys(rest).length === 0) {\n // * A missing entry and a stored `{}` are indistinguishable to every\n // * reader, so releasing the entry is strictly better: a leftover key\n // * visible in devtools reads as data that was not deleted.\n storage.removeItem(FORM_DATA_KEY)\n LIVE_DOCUMENTS.set(storage, rest)\n return\n }\n storage.setItem(FORM_DATA_KEY, JSON.stringify(rest))\n LIVE_DOCUMENTS.set(storage, rest)\n}\n\n/**\n * Whether a value survives `JSON.stringify`. `undefined`, a function, and a\n * `Symbol` make it return `undefined`; a circular reference and a `BigInt` make\n * it throw. All five must be rejected before a write, because a value that\n * cannot stringify aborts a write carrying every form's data.\n */\nexport function isSerializable({ value }: { value: unknown }): boolean {\n try {\n return typeof JSON.stringify(value) === 'string'\n } catch {\n return false\n }\n}\n\nfunction createMemoryStorage(): FormsStorage {\n // * Created per call and never module-level, because it holds user data.\n const entries = new Map<string, string>()\n\n return {\n getItem: (key) => entries.get(key) ?? null,\n setItem: (key, value) => {\n entries.set(key, value)\n },\n removeItem: (key) => {\n entries.delete(key)\n },\n }\n}\n\nfunction isObjectLike(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isStoredField(value: unknown): value is StoredField {\n if (!isObjectLike(value) || !Object.hasOwn(value, 'value')) return false\n if (!isSerializable({ value: value['value'] })) return false\n if (\n typeof value['type'] !== 'string' ||\n !FIELD_TYPES.includes(value['type'] as FieldType) ||\n typeof value['label'] !== 'string'\n )\n return false\n if (value['registryId'] !== undefined && typeof value['registryId'] !== 'string') return false\n if (value['protocolFieldId'] !== undefined && typeof value['protocolFieldId'] !== 'string')\n return false\n return true\n}\n","import type {\n DataUpdatedEvent,\n FieldUpdatedEvent,\n FieldUpdatedType,\n FormSubmittedEvent,\n} from '@embeddables/shared-types/analytics-ingest'\nimport type { JsonValue } from '@embeddables/shared-types/json'\n\nimport type { FieldConfig, FieldType } from './config.js'\n\nexport type {\n AnalyticsInstance,\n AnalyticsTrackEvent,\n AnalyticsTrackResult,\n} from '@embeddables/shared-types/analytics-instance'\nexport type {\n DataUpdatedEvent,\n FieldUpdatedEvent,\n FieldUpdatedType,\n FormSubmittedEvent,\n} from '@embeddables/shared-types/analytics-ingest'\n\n/** The ingest bound on a `data:updated` entry's `value`. */\nconst MAX_VALUE_LENGTH = 1024\n\n/** The ingest bound on a `data:updated` entry's `label`. */\nconst MAX_LABEL_LENGTH = 256\n\nexport type FormsAnalyticsEvent = DataUpdatedEvent | FieldUpdatedEvent | FormSubmittedEvent\n\n/** Maps a form field's declared type to the analytics `field:updated` class. */\nexport function mapFieldUpdatedType(type: FieldType): FieldUpdatedType {\n return type\n}\n\n/**\n * Stringifies values for `data:updated` entries. `field:updated` carries the\n * raw `field_value`; only the batch event caps and stringifies for ingest.\n */\nexport function formatFieldValue({ value }: { value: JsonValue; field?: FieldConfig }): string {\n return (typeof value === 'string' ? value : JSON.stringify(value)).slice(0, MAX_VALUE_LENGTH)\n}\n\n/** One event carrying every key in one `.set()` call. */\nexport function buildDataUpdatedEvent({\n fields,\n patch,\n}: {\n fields: readonly FieldConfig[]\n patch: Record<string, JsonValue>\n}): DataUpdatedEvent {\n const byKey = new Map(fields.map((field) => [field.key, field]))\n\n // * The key count is intentionally unbounded: `.set()` only ever applies keys\n // * the config declares, so it can never exceed the form's field count — a\n // * developer-authored, code-reviewed number rather than user input.\n const data = Object.fromEntries(\n Object.entries(patch).map(([key, value]) => {\n const field = byKey.get(key)\n return [\n key,\n {\n value: formatFieldValue({ value, field }),\n label: (field?.label ?? key).slice(0, MAX_LABEL_LENGTH),\n },\n ]\n }),\n )\n\n return { event_name: 'data:updated', data }\n}\n\n/** One `field:updated` per changed key, emitted alongside `data:updated`. */\nexport function buildFieldUpdatedEvents({\n fields,\n patch,\n}: {\n fields: readonly FieldConfig[]\n patch: Record<string, JsonValue>\n}): FieldUpdatedEvent[] {\n const byKey = new Map(fields.map((field) => [field.key, field]))\n\n return Object.entries(patch).map(([key, value]) => {\n const field = byKey.get(key)\n const event: FieldUpdatedEvent = {\n event_name: 'field:updated',\n field_key: key,\n field_type: mapFieldUpdatedType(field?.type ?? 'text'),\n field_value: value,\n }\n if (field?.registryId !== undefined) {\n event.registry_field_id = field.registryId\n }\n if (field?.protocolFieldId !== undefined) {\n event.protocol_field_id = field.protocolFieldId\n }\n return event\n })\n}\n","import { SchemaError } from '../errors.js'\n\nimport type { FieldConfig, FieldType, FormSchema } from './config.js'\n\nconst FIELD_TYPES: readonly FieldType[] = [\n 'text',\n 'email',\n 'number',\n 'boolean',\n 'select',\n 'multiselect',\n 'json',\n]\n\nconst VALIDATION_RULES: readonly string[] = [\n 'required',\n 'minLength',\n 'maxLength',\n 'min',\n 'max',\n 'pattern',\n 'patternFlags',\n 'oneOf',\n 'custom',\n]\n\nconst NUMERIC_RULES: readonly string[] = ['minLength', 'maxLength', 'min', 'max']\n\n/** The ingest `z.string().max(128)` bound on a `data:updated` key. */\nconst MAX_FIELD_KEY_LENGTH = 128\n\n/** The ingest `z.string().max(128)` bound on a `form:submitted` key. */\nconst MAX_FORM_KEY_LENGTH = 128\n\nconst VALID_PATTERN_FLAGS = /^[dgimsuvy]*$/\n\n/** Internal. The snapshot `initForm` holds for the life of an instance. */\nexport interface ResolvedForm {\n readonly formKey: string\n readonly fields: readonly FieldConfig[]\n /** Compiled once per config object, keyed by field key. */\n readonly patterns: ReadonlyMap<string, RegExp>\n}\n\n// ! The only module-level binding in this package. Keyed by schema object\n// ! identity, it holds nothing but data derived from an argument the caller\n// ! already had, and nothing reads it except the call that supplied the key.\n// ! It must never hold user or per-request state. This is not a registry: there\n// ! is no name a caller can guess, and entries are collectable with the schema.\nconst RESOLVED_SCHEMAS = new WeakMap<FormSchema, ResolvedForm>()\n\nexport function resolveForm({ schema }: { schema: FormSchema }): ResolvedForm {\n const memoized = RESOLVED_SCHEMAS.get(schema)\n if (memoized) return memoized\n\n const resolved = validateAndCompile({ schema })\n RESOLVED_SCHEMAS.set(schema, resolved)\n return resolved\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nfunction validateAndCompile({ schema }: { schema: FormSchema }): ResolvedForm {\n // * Every check below reads the schema as `unknown`, because the whole point\n // * is the input the compiler never saw: a JavaScript consumer, a schema that\n // * arrived from the platform, or one whose `as const` was dropped.\n const root: unknown = schema\n\n assertJsonRepresentable({ root })\n\n if (!isObjectLike(root)) throw new SchemaError('schema: must be an object')\n\n const formKey = root['id']\n if (typeof formKey !== 'string' || formKey.trim() === '')\n throw new SchemaError('schema.id: must be a non-empty string')\n // ! The id is the storage key verbatim, so `' signup '` would persist under a\n // ! different key than `'signup'`. Rejected rather than trimmed: silently\n // ! normalizing would orphan whatever a schema had already stored.\n if (formKey !== formKey.trim())\n throw new SchemaError('schema.id: must not have leading or trailing whitespace')\n if (formKey.length > MAX_FORM_KEY_LENGTH)\n throw new SchemaError(\n `schema.id: must be at most ${MAX_FORM_KEY_LENGTH} characters (received ${formKey.length})`,\n )\n\n const name = root['name']\n if (name !== undefined) {\n if (typeof name !== 'string') throw new SchemaError('schema.name: must be a string')\n if (name.trim() === '') throw new SchemaError('schema.name: must be a non-empty string')\n }\n\n if (!Array.isArray(root['fields'])) throw new SchemaError('schema.fields: must be an array')\n\n const fields: unknown[] = root['fields']\n if (fields.length === 0) throw new SchemaError('schema.fields: must declare at least one field')\n\n const patterns = new Map<string, RegExp>()\n const seenKeys = new Set<string>()\n fields.forEach((field, index) => {\n validateField({ field, path: `schema.fields[${index}]`, seenKeys, patterns })\n })\n\n // * The field list is copied, not aliased: an instance holds this snapshot for\n // * its whole life, so a schema array mutated afterwards must not change what\n // * a live form validates and writes against.\n return { formKey, fields: [...fields] as readonly FieldConfig[], patterns }\n}\n\nfunction validateField({\n field,\n path,\n seenKeys,\n patterns,\n}: {\n field: unknown\n path: string\n seenKeys: Set<string>\n patterns: Map<string, RegExp>\n}): void {\n if (!isObjectLike(field)) throw new SchemaError(`${path}: must be an object`)\n\n const key = field['key']\n if (typeof key !== 'string' || key.trim() === '')\n throw new SchemaError(`${path}.key: must be a non-empty string`)\n if (key.length > MAX_FIELD_KEY_LENGTH)\n throw new SchemaError(\n `${path}.key: must be at most ${MAX_FIELD_KEY_LENGTH} characters (received ${key.length})`,\n )\n if (seenKeys.has(key))\n throw new SchemaError(`${path}.key: duplicate field key \"${key}\" in this form`)\n seenKeys.add(key)\n\n const label = field['label']\n if (typeof label !== 'string' || label.trim() === '')\n throw new SchemaError(`${path}.label: must be a non-empty string`)\n\n const type = field['type']\n if (typeof type !== 'string' || !FIELD_TYPES.includes(type as FieldType))\n throw new SchemaError(`${path}.type: must be one of ${FIELD_TYPES.join(', ')}`)\n\n const registryId = field['registryId']\n if (registryId !== undefined && typeof registryId !== 'string')\n throw new SchemaError(`${path}.registryId: must be a string`)\n\n const protocolFieldId = field['protocolFieldId']\n if (protocolFieldId !== undefined && typeof protocolFieldId !== 'string')\n throw new SchemaError(`${path}.protocolFieldId: must be a string`)\n\n // * Unknown keys on the field object itself are tolerated, deliberately\n // * asymmetric with `validations` below: the platform may add presentation\n // * metadata, and an older SDK should not reject a newer config outright.\n // * An unknown key inside `validations` can only be a typo for a rule, and\n // * silently skipping a rule is the worst failure available. The JSON\n // * round-trip check still rejects a *function* on an unknown field key, so\n // * this tolerance opens no second door for functions.\n const validations = field['validations']\n if (validations === undefined) return\n\n validateValidations({ validations, path: `${path}.validations` })\n if (!isObjectLike(validations)) return\n\n const pattern = validations['pattern']\n if (typeof pattern !== 'string') return\n\n const flags = validations['patternFlags']\n patterns.set(\n key,\n compilePattern({\n pattern,\n flags: typeof flags === 'string' ? flags : '',\n path: `${path}.validations.pattern`,\n }),\n )\n}\n\nfunction validateValidations({ validations, path }: { validations: unknown; path: string }): void {\n if (!isObjectLike(validations)) throw new SchemaError(`${path}: must be an object`)\n\n for (const rule of Object.keys(validations)) {\n if (!VALIDATION_RULES.includes(rule))\n throw new SchemaError(\n `${path}.${rule}: unknown validation rule; expected one of ${VALIDATION_RULES.join(', ')}`,\n )\n }\n\n const required = validations['required']\n if (required !== undefined && typeof required !== 'boolean')\n throw new SchemaError(`${path}.required: must be a boolean`)\n\n for (const rule of NUMERIC_RULES) {\n const value = validations[rule]\n // * `Number.isFinite` is also what rejects the `NaN`/`Infinity` pair that\n // * survives TypeScript's `number` but serializes to `null`.\n if (value !== undefined && !(typeof value === 'number' && Number.isFinite(value)))\n throw new SchemaError(`${path}.${rule}: must be a finite number`)\n }\n\n const minLength = validations['minLength']\n const maxLength = validations['maxLength']\n if (typeof minLength === 'number' && typeof maxLength === 'number' && maxLength < minLength)\n throw new SchemaError(`${path}.maxLength: must be greater than or equal to minLength`)\n\n const min = validations['min']\n const max = validations['max']\n if (typeof min === 'number' && typeof max === 'number' && max < min)\n throw new SchemaError(`${path}.max: must be greater than or equal to min`)\n\n const oneOf = validations['oneOf']\n if (oneOf !== undefined && !(Array.isArray(oneOf) && oneOf.length > 0))\n throw new SchemaError(`${path}.oneOf: must be a non-empty array`)\n\n const pattern = validations['pattern']\n if (pattern !== undefined && typeof pattern !== 'string')\n throw new SchemaError(`${path}.pattern: must be a string`)\n\n const patternFlags = validations['patternFlags']\n if (\n patternFlags !== undefined &&\n !(typeof patternFlags === 'string' && VALID_PATTERN_FLAGS.test(patternFlags))\n )\n throw new SchemaError(`${path}.patternFlags: must contain only the characters dgimsuvy`)\n\n // * The runtime half of the one JSON exception: a widened config or a\n // * JavaScript consumer can put anything here, and a non-function would be\n // * called and throw a TypeError from inside validation on the first\n // * keystroke. Arity and async-ness are deliberately not inspected — the\n // * return-shape and thenable checks at call time own that.\n const custom = validations['custom']\n if (custom !== undefined && typeof custom !== 'function')\n throw new SchemaError(`${path}.custom: must be a function (received ${typeof custom})`)\n}\n\nfunction compilePattern({\n pattern,\n flags,\n path,\n}: {\n pattern: string\n flags: string\n path: string\n}): RegExp {\n // * `g` and `y` carry `lastIndex` across `.test()` calls, so a pattern reused\n // * for every keystroke would silently alternate pass and fail.\n try {\n return new RegExp(pattern, flags.replace(/[gy]/g, ''))\n } catch (error) {\n throw new SchemaError(`${path}: invalid pattern — ${errorMessage(error)}`, { cause: error })\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON representability\n// ---------------------------------------------------------------------------\n\n/**\n * Rejects every value in the config that would not survive\n * `JSON.parse(JSON.stringify(x))` — a function included, except at the one\n * permitted location, `validations.custom` on a field.\n */\nfunction assertJsonRepresentable({ root }: { root: unknown }): void {\n const stripped = withoutFieldValidators(root)\n const serialized = stringifyOrUndefined(stripped)\n\n if (serialized === undefined) {\n const path = findNonJsonPath({ value: stripped, path: 'schema', seen: new Set() })\n throw new SchemaError(`${path ?? 'schema'}: value cannot be serialized to JSON`)\n }\n\n const mismatch = firstMismatch({\n actual: stripped,\n expected: JSON.parse(serialized),\n path: 'schema',\n })\n if (mismatch)\n throw new SchemaError(\n `${mismatch}: value does not survive a JSON round trip; only a field's validations.custom may hold a function, and every other value must be JSON-representable`,\n )\n}\n\n// ! Copies along `schema.fields[*].validations` only, so the caller's schema is\n// ! never mutated and no other key named `custom` at any depth is stripped.\n// ! A blanket strip would let a function through anywhere and gut the check.\nfunction withoutFieldValidators(root: unknown): unknown {\n if (!isObjectLike(root)) return root\n if (!Array.isArray(root['fields'])) return root\n\n const fields: unknown[] = root['fields']\n return { ...root, fields: fields.map(stripField) }\n}\n\nfunction stripField(field: unknown): unknown {\n if (!isObjectLike(field)) return field\n const validations = field['validations']\n if (!isObjectLike(validations) || !('custom' in validations)) return field\n\n const { custom: _custom, ...rest } = validations\n return { ...field, validations: rest }\n}\n\nfunction stringifyOrUndefined(value: unknown): string | undefined {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n}\n\n/** The path of the first value `JSON.stringify` cannot handle at all. */\nfunction findNonJsonPath({\n value,\n path,\n seen,\n}: {\n value: unknown\n path: string\n seen: Set<object>\n}): string | undefined {\n if (typeof value === 'bigint' || typeof value === 'symbol') return path\n if (value === null || typeof value !== 'object') return undefined\n if (seen.has(value)) return path\n\n seen.add(value)\n for (const [childPath, child] of childEntries({ value, path })) {\n const found = findNonJsonPath({ value: child, path: childPath, seen })\n if (found) return found\n }\n seen.delete(value)\n return undefined\n}\n\nfunction childEntries({ value, path }: { value: object; path: string }): [string, unknown][] {\n if (Array.isArray(value)) {\n const items: unknown[] = value\n return items.map((item, index) => [`${path}[${index}]`, item])\n }\n return Object.entries(value as Record<string, unknown>).map(([key, item]) => [\n `${path}.${key}`,\n item,\n ])\n}\n\n/** The path of the first value that changed across the round trip. */\nfunction firstMismatch({\n actual,\n expected,\n path,\n}: {\n actual: unknown\n expected: unknown\n path: string\n}): string | undefined {\n if (Array.isArray(actual) || Array.isArray(expected)) {\n if (!Array.isArray(actual) || !Array.isArray(expected)) return path\n\n const actualItems: unknown[] = actual\n const expectedItems: unknown[] = expected\n if (actualItems.length !== expectedItems.length) return path\n\n for (const [index, item] of actualItems.entries()) {\n const found = firstMismatch({\n actual: item,\n expected: expectedItems[index],\n path: `${path}[${index}]`,\n })\n if (found) return found\n }\n return undefined\n }\n\n if (isJsonObject(actual) && isJsonObject(expected)) {\n const actualKeys = Object.keys(actual)\n const expectedKeys = Object.keys(expected)\n // * A key present before and absent after is exactly how `undefined`, a\n // * function, and a `Symbol` value disappear.\n if (actualKeys.length !== expectedKeys.length) {\n const dropped = actualKeys.find((key) => !expectedKeys.includes(key))\n return dropped === undefined ? path : `${path}.${dropped}`\n }\n for (const key of actualKeys) {\n const found = firstMismatch({\n actual: actual[key],\n expected: expected[key],\n path: `${path}.${key}`,\n })\n if (found) return found\n }\n return undefined\n }\n\n // * Catches `NaN`/`Infinity` (both become `null`), a `Date` (becomes a\n // * string), and a `RegExp`, `Map`, `Set`, or class instance (all become\n // * `{}`, which is not the original object).\n return actual === expected ? undefined : path\n}\n\n// ---------------------------------------------------------------------------\n// Shared predicates\n// ---------------------------------------------------------------------------\n\nfunction isObjectLike(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Narrower than `isObjectLike`: a `Date`, `RegExp`, or class instance is not one. */\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n if (!isObjectLike(value)) return false\n const prototype = Object.getPrototypeOf(value)\n return prototype === Object.prototype || prototype === null\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n","import type { JsonValue } from '@embeddables/shared-types/json'\n\nimport { ValidatorError } from '../errors.js'\n\nimport type { FieldConfig, FieldType, FieldValidator } from './config.js'\n\n/** Runtime counterpart to `FieldType`. Exhaustive by construction. */\n// ! Must stay in lockstep with `ValueOfFieldType` in `src/config.ts`: the two\n// ! are the compile-time and runtime halves of one claim. Typing this as\n// ! `Record<FieldType, …>` is what makes a new field type a compile error\n// ! here; only the type tests catch a mismatch between the two.\nconst FIELD_TYPE_PREDICATES: Record<FieldType, (value: JsonValue) => boolean> = {\n text: (value) => typeof value === 'string',\n email: (value) => typeof value === 'string',\n number: (value) => typeof value === 'number',\n boolean: (value) => typeof value === 'boolean',\n select: (value) => typeof value === 'string',\n multiselect: (value) => Array.isArray(value),\n json: () => true,\n}\n\n// * The WHATWG `input[type=email]` production, so a value the browser accepts in\n// * an email input is a value this accepts. It is deliberately narrower than\n// * RFC 5322 (no quoted local parts, no comments) and deliberately wider than\n// * \"must have a dot\": `user@localhost` and intranet hosts are valid. A form that\n// * needs a public TLD adds `pattern` on top.\nconst EMAIL_PATTERN =\n /^[\\w.!#$%&'*+/=?^`{|}~-]+@[a-zA-Z\\d](?:[a-zA-Z\\d-]{0,61}[a-zA-Z\\d])?(?:\\.[a-zA-Z\\d](?:[a-zA-Z\\d-]{0,61}[a-zA-Z\\d])?)*$/\n\n/**\n * Every message a single field's value earns. Empty means valid. Not generic:\n * the per-field types live at the instance boundary, and the cast down to\n * `JsonValue` happens once, in `initForm`.\n */\nexport function validateValue({\n field,\n value,\n values,\n pattern,\n validator,\n}: {\n field: FieldConfig\n value: JsonValue | undefined\n values: Readonly<Record<string, JsonValue>>\n pattern?: RegExp\n validator?: FieldValidator\n}): readonly string[] {\n const rules = field.validations\n const messages: string[] = []\n\n const isAbsent = value === undefined || value === null\n const isBlank = isAbsent || value === '' || (Array.isArray(value) && value.length === 0)\n if (rules?.required === true && isBlank) messages.push(`${field.label} is required`)\n\n // * An absent value earns no further message and never reaches the custom\n // * validator, whether or not it was required.\n if (isAbsent) return messages\n\n if (!FIELD_TYPE_PREDICATES[field.type](value))\n messages.push(\n `${field.label} expects ${/^[aeiou]/.test(field.type) ? 'an' : 'a'} ${field.type} value`,\n )\n\n if (typeof value === 'string') {\n // * Intrinsic to the declared type, so it runs before the author's own rules\n // * and cannot be switched off. That is why the blank case belongs to\n // * `required` alone: an author who wants an optional email cleared has no\n // * way to opt out of this check, so it must not claim `''` is malformed.\n if (field.type === 'email' && value !== '' && !EMAIL_PATTERN.test(value))\n messages.push(`${field.label} must be a valid email address`)\n if (rules?.minLength !== undefined && value.length < rules.minLength)\n messages.push(`${field.label} must be at least ${rules.minLength} characters`)\n if (rules?.maxLength !== undefined && value.length > rules.maxLength)\n messages.push(`${field.label} must be at most ${rules.maxLength} characters`)\n if (pattern && !pattern.test(value))\n messages.push(`${field.label} is not in the expected format`)\n }\n\n if (Array.isArray(value)) {\n if (rules?.minLength !== undefined && value.length < rules.minLength)\n messages.push(`${field.label} must have at least ${rules.minLength} items`)\n if (rules?.maxLength !== undefined && value.length > rules.maxLength)\n messages.push(`${field.label} must have at most ${rules.maxLength} items`)\n }\n\n if (typeof value === 'number') {\n if (rules?.min !== undefined && value < rules.min)\n messages.push(`${field.label} must be at least ${rules.min}`)\n if (rules?.max !== undefined && value > rules.max)\n messages.push(`${field.label} must be at most ${rules.max}`)\n }\n\n if (rules?.oneOf) {\n // * Canonical JSON equality, so an object option matches whatever key order\n // * the stored value happens to carry, while arrays still compare\n // * positionally.\n const encoded = canonicalize(value)\n if (!rules.oneOf.some((option) => canonicalize(option) === encoded))\n messages.push(`${field.label} must be one of the allowed options`)\n }\n\n // ! The validator runs only on a present, type-correct value whose every\n // ! declarative rule passed. Those three conditions are what make the\n // ! declared `value` type honest: a validator body dereferences `value` with\n // ! no guard because the type says it can. Deleting or reordering this check\n // ! hands consumer code a value whose runtime type contradicts its declared\n // ! type, with no error anywhere.\n if (!validator || messages.length > 0) return messages\n\n return normalizeValidatorResult({\n // * Read as `unknown` because a JavaScript consumer, or a widened config,\n // * can return anything at all from here.\n result: validator({ value, values }),\n field,\n })\n}\n\nfunction normalizeValidatorResult({\n result,\n field,\n}: {\n result: unknown\n field: FieldConfig\n}): readonly string[] {\n if (isThenable(result))\n throw new ValidatorError(\n `Validator for \"${field.key}\" returned a promise; custom validators must be synchronous`,\n )\n if (result === null || result === undefined) return []\n if (typeof result === 'string') return [result]\n if (Array.isArray(result))\n return (result as unknown[]).filter((entry): entry is string => typeof entry === 'string')\n\n throw new ValidatorError(\n `Validator for \"${field.key}\" returned ${typeof result}; expected a string, an array of strings, or null`,\n )\n}\n\nfunction isThenable(value: unknown): boolean {\n return typeof (value as { then?: unknown } | null | undefined)?.then === 'function'\n}\n\n/**\n * JSON encoding with object keys sorted at every depth, so two values compare\n * by content rather than by insertion order.\n */\n// * A stored value reaches this through `JSON.parse` of the shared document, so\n// * its key order is whatever was written first, not the order the config\n// * author wrote the option in. Plain `JSON.stringify` equality would reject a\n// * `json` value that differs from its option only by key order.\nfunction canonicalize(value: JsonValue): string {\n const walk = (input: JsonValue): JsonValue => {\n if (Array.isArray(input)) return input.map(walk)\n if (input !== null && typeof input === 'object')\n return Object.fromEntries(\n Object.entries(input)\n .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))\n .map(([key, item]) => [key, walk(item)]),\n )\n return input\n }\n return JSON.stringify(walk(value))\n}\n","import type { EmbeddablesInstance } from '@embeddables/core'\nimport type { JsonValue } from '@embeddables/shared-types/json'\n\nimport { FormsError, SchemaError } from '../errors.js'\nimport { resolveDefaultPersistence } from '../storage/persistence-client.js'\nimport {\n degradeToMemory,\n isSerializable,\n readFields,\n removeFields,\n resolveStorage,\n writeFields,\n} from '../storage/storage.js'\nimport { buildDataUpdatedEvent, buildFieldUpdatedEvents } from './analytics.js'\nimport { resolveForm } from './resolve.js'\nimport { validateValue } from './validation.js'\n\nimport type { FormsPersistence, PersistedField, RecoverableField } from '../storage/persistence.js'\nimport type { FormsStorage } from '../storage/storage.js'\nimport type { AnalyticsInstance } from './analytics.js'\nimport type {\n FieldConfig,\n FieldValidator,\n FormFieldKey,\n FormSchema,\n FormValues,\n ProtocolFieldId,\n} from './config.js'\n\n/** Per-key validation errors. An empty object means the operation succeeded. */\nexport type FieldErrors<TSchema extends FormSchema> = Readonly<\n Partial<Record<FormFieldKey<TSchema>, readonly string[]>>\n>\n\nexport interface SetResult<TSchema extends FormSchema> {\n ok: boolean\n errors: FieldErrors<TSchema>\n /** Set when an `analyticsInstance` was configured and `trackEvent` rejected. Never thrown. */\n trackError?: unknown\n}\n\nexport interface SubmitResult<TSchema extends FormSchema> {\n ok: boolean\n errors: FieldErrors<TSchema>\n values: Partial<FormValues<TSchema>>\n trackError?: unknown\n}\n\nexport interface ValidateResult<TSchema extends FormSchema> {\n ok: boolean\n errors: FieldErrors<TSchema>\n values: Partial<FormValues<TSchema>>\n}\n\nexport interface FormInstance<TSchema extends FormSchema> {\n readonly key: TSchema['id']\n /**\n * Applies every key atomically: all or nothing, one write, one event.\n * Validates and persists synchronously; the returned promise never rejects.\n * Throws synchronously only if a custom validator throws or returns an\n * illegal shape.\n */\n set(patch: Partial<FormValues<TSchema>>): Promise<SetResult<TSchema>>\n /** Typed by the field's declared `type`. Nothing verifies the stored value against it. */\n get<K extends FormFieldKey<TSchema>>(key: K): FormValues<TSchema>[K] | undefined\n /**\n * Read a stored value by the field's declared `protocolFieldId`. Returns\n * `undefined` when no field maps to the id or the value is unset. Typed like\n * `get()` for the backing field.\n */\n getValueByProtocolFieldId<P extends ProtocolFieldId<TSchema>>(\n protocolFieldId: P,\n ):\n | FormValues<TSchema>[Extract<TSchema['fields'][number], { protocolFieldId: P }>['key']]\n | undefined\n getAll(): Partial<FormValues<TSchema>>\n /**\n * Validates every declared field, then emits one `form:submitted` event.\n *\n * Not idempotent: every call emits another event. The caller owns dedupe —\n * disable the button, or guard on a route transition.\n *\n * Same synchronous-throw and never-reject contract as `set`.\n */\n submit(): Promise<SubmitResult<TSchema>>\n /**\n * Runs validation without writing to storage or emitting analytics.\n *\n * With no argument, validates every declared field against stored values and\n * replaces `errors()` wholesale. With a patch, validates only those keys\n * against a merged snapshot and updates errors for those keys only.\n *\n * Same synchronous-throw and never-reject contract as `set` / `submit`.\n */\n validate(patch?: Partial<FormValues<TSchema>>): Promise<ValidateResult<TSchema>>\n errors(): FieldErrors<TSchema>\n clear(): void\n /** Synchronous listener for value and error mutations. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void\n}\n\nconst REQUIRED_CORE_METHODS = ['getAppUserId', 'getProjectId', 'getPublishableKey'] as const\n\nfunction hasRequiredCoreMethods(value: unknown): value is EmbeddablesInstance {\n if (typeof value !== 'object' || value === null) return false\n return REQUIRED_CORE_METHODS.every(\n (method) => typeof (value as Record<string, unknown>)[method] === 'function',\n )\n}\n\nfunction mergeCustomValidations<TSchema extends FormSchema>({\n schema,\n customValidations,\n}: {\n schema: TSchema\n customValidations?: { readonly [K in FormFieldKey<TSchema>]?: FieldValidator }\n}): TSchema {\n if (!customValidations) return schema\n\n const declared = new Set(schema.fields.map((field) => field.key))\n for (const key of Object.keys(customValidations)) {\n if (!declared.has(key)) {\n throw new SchemaError(`customValidations: unknown field key \"${key}\"`)\n }\n const validator = customValidations[key as FormFieldKey<TSchema>]\n if (typeof validator !== 'function') {\n throw new SchemaError(`customValidations.${key}: must be a function`)\n }\n }\n\n const fields = schema.fields.map((field) => {\n const custom = customValidations[field.key as FormFieldKey<TSchema>]\n if (!custom) return field\n return {\n ...field,\n validations: { ...field.validations, custom },\n }\n })\n\n return { ...schema, fields }\n}\n\n// ! The public surface, matching the approved Miro flow: `core` and optional\n// ! analytics client.\n// ! Storage and persistence ports are not consumer options — internal seams\n// ! (see `FormsClientOptions`).\nexport interface InitFormsOptions {\n core: EmbeddablesInstance\n analyticsInstance?: AnalyticsInstance\n}\n\nexport interface FormsClient {\n initForm<const TSchema extends FormSchema>(options: {\n schema: TSchema\n customValidations?: { readonly [K in FormFieldKey<TSchema>]?: FieldValidator }\n }): FormInstance<TSchema>\n}\n\n/**\n * Validates the core instance once, then returns a per-form `initForm`. Public\n * API — the Miro signature. Consumers pass only `core` and, optionally, an\n * analytics client.\n */\nexport function initForms({ core, analyticsInstance }: InitFormsOptions): FormsClient {\n return createFormsClient({ core, analyticsInstance })\n}\n\n// ! Internal, not exported from the package barrel. The storage and persistence\n// ! seams live here so tests can inject a memory store / spy port and the real\n// ! R2/Supabase client can be wired later, without widening the public\n// ! `initForms` signature beyond what the Miro shows.\nexport interface FormsClientOptions extends InitFormsOptions {\n baseUrl?: string\n storage?: FormsStorage\n persistence?: FormsPersistence\n}\n\nexport function createFormsClient({\n core,\n analyticsInstance,\n baseUrl,\n storage,\n persistence,\n}: FormsClientOptions): FormsClient {\n if (!hasRequiredCoreMethods(core)) {\n throw new FormsError('initForms requires an initialized Embeddables core instance.')\n }\n\n const resolvedPersistence = persistence ?? resolveDefaultPersistence({ core, baseUrl })\n\n // * Retained as the composition root; Forms does not persist identity.\n void core\n\n return {\n initForm: <const TSchema extends FormSchema>(options: {\n schema: TSchema\n customValidations?: { readonly [K in FormFieldKey<TSchema>]?: FieldValidator }\n }): FormInstance<TSchema> =>\n createFormInstance({\n analyticsInstance,\n storage,\n persistence: resolvedPersistence,\n schema: options.schema,\n customValidations: options.customValidations,\n }),\n }\n}\n\nfunction createFormInstance<const TSchema extends FormSchema>({\n analyticsInstance,\n storage,\n persistence,\n schema,\n customValidations,\n}: {\n analyticsInstance?: AnalyticsInstance\n storage?: FormsStorage\n persistence: FormsPersistence\n schema: TSchema\n customValidations?: { readonly [K in FormFieldKey<TSchema>]?: FieldValidator }\n}): FormInstance<TSchema> {\n const schemaForResolve = mergeCustomValidations({ schema, customValidations })\n // * Snapshotted for the instance's life, so a schema mutated afterwards\n // * cannot change a live form. Memoized on schema object identity, so a second\n // * instance over the same literal is free — but `customValidations` builds a\n // * new object above and therefore always resolves afresh.\n const resolved = resolveForm({ schema: schemaForResolve })\n const declared = new Map<string, FieldConfig>(resolved.fields.map((field) => [field.key, field]))\n const keyByProtocolFieldId = new Map<string, string>()\n for (const field of resolved.fields) {\n const protocolId = field.protocolFieldId\n if (typeof protocolId === 'string' && protocolId.length > 0) {\n keyByProtocolFieldId.set(protocolId, field.key)\n }\n }\n\n // * Held in one object rather than as bindings, because `storage` is swapped\n // * on degradation and the bag/error map are mutated in place.\n const resolvedStorage = resolveStorage({ storage })\n const listeners = new Set<() => void>()\n const notify = (): void => {\n for (const listener of listeners) {\n try {\n listener()\n } catch {\n // * Subscriber errors must not change form state operations.\n }\n }\n }\n\n const state = {\n storage: resolvedStorage,\n // ! Copied once at init and never refreshed: get/submit read this bag, and\n // ! set/clear persist it without another storage read. One live instance per\n // ! `schema.id` is therefore assumed. A second instance, another tab, or a\n // ! user clearing site data is invisible here, and the next `set` overwrites\n // ! it.\n bag: {\n ...readFields({\n storage: resolvedStorage,\n formKey: resolved.formKey,\n fieldDefinitions: resolved.fields,\n }),\n },\n errors: new Map<string, readonly string[]>(),\n }\n\n // * Runs a persistence call without ever letting it break the caller: a\n // * synchronous throw is caught and a returned promise's rejection is\n // * swallowed. Fire-and-forget by contract — the SDK never awaits a durable\n // * write, so `set` / `submit` keep their never-rejects guarantee.\n const firePersistence = (run: () => void | Promise<void>): void => {\n try {\n const result = run()\n if (result instanceof Promise) {\n void result.then(undefined, () => undefined)\n }\n } catch {\n // best-effort: a failing persistence port never surfaces to set/submit\n }\n }\n\n // * localStorage was seeded above and always wins. Recovery is eligible only\n // * for a field that declares a registry/protocol id and is still absent\n // * locally, and a recovered value is merged only while the local value stays\n // * missing. With the no-op default this whole block is inert.\n const mergeRecovered = (recovered: Record<string, JsonValue>): void => {\n let mergedCount = 0\n for (const [key, value] of Object.entries(recovered)) {\n if (value !== undefined && state.bag[key] === undefined) {\n state.bag[key] = value\n mergedCount++\n }\n }\n if (mergedCount > 0) notify()\n }\n\n const recoverable: RecoverableField[] = resolved.fields\n .filter(\n (field) =>\n (field.registryId !== undefined || field.protocolFieldId !== undefined) &&\n state.bag[field.key] === undefined,\n )\n .map((field) => ({\n key: field.key,\n ...(field.registryId === undefined ? {} : { registryId: field.registryId }),\n ...(field.protocolFieldId === undefined ? {} : { protocolFieldId: field.protocolFieldId }),\n }))\n\n if (recoverable.length > 0) {\n try {\n const result = persistence.recoverRegistryFields({\n formKey: resolved.formKey,\n fields: recoverable,\n })\n if (result instanceof Promise) {\n void result.then(\n (recovered) => mergeRecovered(recovered ?? {}),\n () => undefined,\n )\n } else {\n mergeRecovered(result)\n }\n } catch {\n // best-effort: recovery must never throw out of initForm\n }\n }\n\n const freeze = (entries: Map<string, readonly string[]>): FieldErrors<TSchema> =>\n Object.freeze(Object.fromEntries(entries)) as FieldErrors<TSchema>\n\n const noErrors = (): FieldErrors<TSchema> => freeze(new Map())\n\n /** The stored bag narrowed to the keys the config declares. */\n const narrow = (bag: Record<string, JsonValue>): Record<string, JsonValue> => {\n const narrowed: Record<string, JsonValue> = {}\n for (const field of resolved.fields) {\n const value = bag[field.key]\n if (value !== undefined) narrowed[field.key] = value\n }\n return narrowed\n }\n\n const readBag = (): Record<string, JsonValue> => state.bag\n\n // * The whole type boundary, in one place: a field declares its validator\n // * against its own value type, while `validateValue` is a runtime predicate\n // * over `JsonValue`. Sound because `validateValue` calls the validator only\n // * after the field's type predicate passed.\n const validatorFor = (field: FieldConfig): FieldValidator | undefined =>\n field.validations?.custom as FieldValidator | undefined\n\n // * Validates the given keys against one snapshot. Callers that pass every\n // * declared field — `submit()` and parameterless `validate()` — do so to\n // * catch a field invalidated by an earlier `.set()` on some other key, not\n // * only the keys touched in the latest patch.\n const validateDeclaredFields = ({\n snapshot,\n keys,\n }: {\n snapshot: Record<string, JsonValue>\n keys: readonly string[]\n }): Map<string, readonly string[]> => {\n const errors = new Map<string, readonly string[]>()\n\n for (const key of keys) {\n const field = declared.get(key)\n if (!field) continue\n\n const messages = validateValue({\n field,\n value: snapshot[field.key],\n values: snapshot,\n pattern: resolved.patterns.get(key),\n validator: validatorFor(field),\n })\n if (messages.length > 0) errors.set(key, messages)\n }\n\n return errors\n }\n\n const replaceErrors = (errors: Map<string, readonly string[]>): void => {\n state.errors.clear()\n for (const [key, messages] of errors) state.errors.set(key, messages)\n }\n\n const applyPatchValidationErrors = ({\n errors,\n patchKeys,\n }: {\n errors: Map<string, readonly string[]>\n patchKeys: readonly string[]\n }): void => {\n for (const key of patchKeys) {\n const messages = errors.get(key)\n if (messages) state.errors.set(key, messages)\n else state.errors.delete(key)\n }\n }\n\n // ! Deliberately not `async`. An `async` function turns the synchronous throw\n // ! from a misbehaving custom validator into a rejected promise, which would\n // ! break the never-rejects contract and hide a loud developer error inside\n // ! an unhandled rejection that `void form.set(…)` swallows. Every write and\n // ! every validation below completes before this function returns.\n const set = (patch: Partial<FormValues<TSchema>>): Promise<SetResult<TSchema>> => {\n const changes = patch as Record<string, JsonValue>\n const entries = Object.entries(changes)\n\n // * Mirrors the analytics SDK's `track([])`: no validation, no write, no\n // * event. The in-memory bag is already the source of truth.\n if (entries.length === 0) return Promise.resolve({ ok: true, errors: noErrors() })\n\n const errors = new Map<string, readonly string[]>()\n\n for (const [key, value] of entries) {\n const field = declared.get(key)\n if (!field) {\n // * The compiler rejects this for a typed config, so this is the path a\n // * JavaScript caller or a widened config takes. It is reachable.\n errors.set(key, [`Unknown field: ${key}`])\n continue\n }\n if (!isSerializable({ value }))\n errors.set(key, [`${field.label} value is not JSON-serializable`])\n }\n\n // * The candidate bag is assembled whole and validated whole before a\n // * single byte is written, so a cross-field validator sees every key\n // * arriving in the same call rather than the stale stored one.\n const candidate: Record<string, JsonValue> = { ...readBag(), ...changes }\n const snapshot = narrow(candidate)\n\n for (const [key, value] of entries) {\n const field = declared.get(key)\n if (!field || errors.has(key)) continue\n\n // * A throwing validator propagates out of `set` synchronously. No write\n // * has happened yet, so the batch is trivially atomic — which is why\n // * this is not wrapped in a try/catch.\n const messages = validateValue({\n field,\n value,\n values: snapshot,\n pattern: resolved.patterns.get(key),\n validator: validatorFor(field),\n })\n if (messages.length > 0) errors.set(key, messages)\n }\n\n if (errors.size > 0) {\n for (const [key, messages] of errors) state.errors.set(key, messages)\n // * Not one key of the patch is applied: storage is byte-identical and\n // * nothing is emitted.\n notify()\n return Promise.resolve({ ok: false, errors: freeze(errors) })\n }\n\n for (const [key] of entries) state.errors.delete(key)\n\n try {\n writeFields({\n storage: state.storage,\n formKey: resolved.formKey,\n fields: candidate,\n fieldDefinitions: resolved.fields,\n })\n state.bag = candidate\n } catch {\n return Promise.resolve(degradeAndReport({ entries }))\n }\n\n notify()\n\n // * Best-effort durable persistence, fired after the local write succeeds\n // * and swallowed whole. Every key here is declared (an unknown key would\n // * have failed validation above), so its config carries the ids.\n const persistedFields: PersistedField[] = entries.map(([key, value]) => {\n const field = declared.get(key)\n return {\n key,\n value,\n ...(field?.registryId === undefined ? {} : { registryId: field.registryId }),\n ...(field?.protocolFieldId === undefined ? {} : { protocolFieldId: field.protocolFieldId }),\n }\n })\n firePersistence(() => persistence.savePartial({ formKey: resolved.formKey, values: snapshot }))\n firePersistence(() =>\n persistence.saveFields({ formKey: resolved.formKey, fields: persistedFields }),\n )\n\n if (!analyticsInstance) return Promise.resolve({ ok: true, errors: noErrors() })\n\n return analyticsInstance\n .trackEvent([\n buildDataUpdatedEvent({ fields: resolved.fields, patch: changes }),\n ...buildFieldUpdatedEvents({ fields: resolved.fields, patch: changes }),\n ])\n .then(() => ({ ok: true, errors: noErrors() }))\n .catch((error: unknown) => ({\n // * `ok` stays true only for values that were persisted; here the write\n // * succeeded and only the emission failed.\n ok: true,\n errors: noErrors(),\n trackError: error,\n }))\n }\n\n const degradeAndReport = ({\n entries,\n }: {\n entries: [string, JsonValue][]\n }): SetResult<TSchema> => {\n state.storage = degradeToMemory({ storage: state.storage })\n\n const errors = new Map<string, readonly string[]>()\n for (const [key] of entries) {\n const label = declared.get(key)?.label ?? key\n const message = `${label} could not be persisted; this form is now in-memory only`\n errors.set(key, [message])\n state.errors.set(key, [message])\n }\n notify()\n return { ok: false, errors: freeze(errors) }\n }\n\n const get = <K extends FormFieldKey<TSchema>>(key: K): FormValues<TSchema>[K] | undefined => {\n const fieldKey = key as unknown as string\n // * An undeclared key is never handed back, even when the stored bag holds\n // * one: the config is the source of truth for what a form has.\n if (!declared.has(fieldKey)) return undefined\n\n // ! The one place this package asserts something it has not verified.\n // ! `localStorage` is untrusted, so a hand-edited or stale value comes back\n // ! typed as whatever the config declares. Do not \"fix\" it by returning\n // ! `JsonValue` — that drops the typing this surface exists to provide —\n // ! and do not add a runtime coercion, which would rewrite user data.\n return state.bag[fieldKey] as FormValues<TSchema>[K] | undefined\n }\n\n const getValueByProtocolFieldId = ((protocolFieldId: string): JsonValue | undefined => {\n const fieldKey = keyByProtocolFieldId.get(protocolFieldId)\n if (fieldKey === undefined) return undefined\n return get(fieldKey as FormFieldKey<TSchema>) as JsonValue | undefined\n }) as FormInstance<TSchema>['getValueByProtocolFieldId']\n\n const getAll = (): Partial<FormValues<TSchema>> =>\n narrow(state.bag) as Partial<FormValues<TSchema>>\n\n // ! Not `async`, for the same reason as `set`.\n const submit = (): Promise<SubmitResult<TSchema>> => {\n const snapshot = narrow(readBag())\n const values = snapshot as Partial<FormValues<TSchema>>\n const errors = validateDeclaredFields({\n snapshot,\n keys: resolved.fields.map((field) => field.key),\n })\n\n if (errors.size > 0) {\n replaceErrors(errors)\n notify()\n // * A `form:submitted` row must mean a real submission, so nothing is sent.\n return Promise.resolve({ ok: false, errors: freeze(errors), values })\n }\n\n state.errors.clear()\n notify()\n\n // * Best-effort full-submission save, swallowed like the `set` persistence.\n firePersistence(() =>\n persistence.saveSubmission({ formKey: resolved.formKey, values: snapshot }),\n )\n\n if (!analyticsInstance) return Promise.resolve({ ok: true, errors: noErrors(), values })\n\n return analyticsInstance\n .trackEvent([{ event_name: 'form:submitted', form_key: resolved.formKey }])\n .then(() => ({ ok: true, errors: noErrors(), values }))\n .catch((error: unknown) => ({\n ok: true,\n errors: noErrors(),\n values,\n trackError: error,\n }))\n }\n\n // ! Not `async`, for the same reason as `set`.\n const validate = (patch?: Partial<FormValues<TSchema>>): Promise<ValidateResult<TSchema>> => {\n if (patch === undefined) {\n const snapshot = narrow(readBag())\n const values = snapshot as Partial<FormValues<TSchema>>\n const errors = validateDeclaredFields({\n snapshot,\n keys: resolved.fields.map((field) => field.key),\n })\n\n if (errors.size > 0) {\n replaceErrors(errors)\n notify()\n return Promise.resolve({ ok: false, errors: freeze(errors), values })\n }\n\n state.errors.clear()\n notify()\n return Promise.resolve({ ok: true, errors: noErrors(), values })\n }\n\n const changes = patch as Record<string, JsonValue>\n const entries = Object.entries(changes)\n const values = narrow({ ...readBag(), ...changes }) as Partial<FormValues<TSchema>>\n\n // * Mirrors `set({})`: nothing to validate, no error-state change.\n if (entries.length === 0) return Promise.resolve({ ok: true, errors: noErrors(), values })\n\n const errors = new Map<string, readonly string[]>()\n const patchKeys = entries.map(([key]) => key)\n\n for (const [key, value] of entries) {\n const field = declared.get(key)\n if (!field) {\n errors.set(key, [`Unknown field: ${key}`])\n continue\n }\n if (!isSerializable({ value }))\n errors.set(key, [`${field.label} value is not JSON-serializable`])\n }\n\n const snapshot = narrow({ ...readBag(), ...changes })\n\n for (const [key] of entries) {\n if (errors.has(key)) continue\n\n const field = declared.get(key)\n if (!field) continue\n\n const messages = validateValue({\n field,\n value: snapshot[key],\n values: snapshot,\n pattern: resolved.patterns.get(key),\n validator: validatorFor(field),\n })\n if (messages.length > 0) errors.set(key, messages)\n }\n\n applyPatchValidationErrors({ errors, patchKeys })\n\n if (errors.size > 0) {\n notify()\n return Promise.resolve({ ok: false, errors: freeze(errors), values })\n }\n\n notify()\n return Promise.resolve({ ok: true, errors: noErrors(), values })\n }\n\n // ! All-or-nothing within the form: undeclared field keys in this form's bag\n // ! go too, making this the one operation that does not preserve them. Every\n // ! other form key survives — removing the whole entry would wipe every form\n // ! on the origin.\n const clear = (): void => {\n removeFields({ storage: state.storage, formKey: resolved.formKey })\n state.bag = {}\n state.errors.clear()\n notify()\n }\n\n const subscribe = (listener: () => void): (() => void) => {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n }\n\n return {\n key: schema.id,\n set,\n get,\n getValueByProtocolFieldId,\n getAll,\n submit,\n validate,\n errors: () => freeze(state.errors),\n clear,\n subscribe,\n }\n}\n"],"mappings":"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"}
@@ -44,7 +44,7 @@ interface FieldConfigFor<TType extends FieldType> {
44
44
  readonly type: TType;
45
45
  readonly validations?: FieldValidationsFor<TType>;
46
46
  readonly registryId?: string;
47
- readonly protocolQuestionId?: string;
47
+ readonly protocolFieldId?: string;
48
48
  }
49
49
  type FieldConfig = { [T in FieldType]: FieldConfigFor<T>; }[FieldType];
50
50
  type FieldValidations = { [T in FieldType]: FieldValidationsFor<T>; }[FieldType];
@@ -56,6 +56,10 @@ interface FormSchema {
56
56
  type FieldsOf<TSchema extends FormSchema> = TSchema['fields'][number];
57
57
  type FormFieldKey<TSchema extends FormSchema> = FieldsOf<TSchema>['key'];
58
58
  type FormValues<TSchema extends FormSchema> = { [F in FieldsOf<TSchema> as F['key']]: ValueOfFieldType[F['type']]; };
59
+ /** Protocol ids declared on schema fields (literal union for `as const` schemas). */
60
+ type ProtocolFieldId<TSchema extends FormSchema> = [TSchema] extends [FormSchema] ? string : Extract<FieldsOf<TSchema>, {
61
+ protocolFieldId: string;
62
+ }>['protocolFieldId'];
59
63
  //#endregion
60
64
  //#region ../shared-types/dist/analytics-instance.types.d.ts
61
65
  /**
@@ -122,6 +126,14 @@ interface FormInstance<TSchema extends FormSchema> {
122
126
  set(patch: Partial<FormValues<TSchema>>): Promise<SetResult<TSchema>>;
123
127
  /** Typed by the field's declared `type`. Nothing verifies the stored value against it. */
124
128
  get<K extends FormFieldKey<TSchema>>(key: K): FormValues<TSchema>[K] | undefined;
129
+ /**
130
+ * Read a stored value by the field's declared `protocolFieldId`. Returns
131
+ * `undefined` when no field maps to the id or the value is unset. Typed like
132
+ * `get()` for the backing field.
133
+ */
134
+ getValueByProtocolFieldId<P extends ProtocolFieldId<TSchema>>(protocolFieldId: P): FormValues<TSchema>[Extract<TSchema['fields'][number], {
135
+ protocolFieldId: P;
136
+ }>['key']] | undefined;
125
137
  getAll(): Partial<FormValues<TSchema>>;
126
138
  /**
127
139
  * Validates every declared field, then emits one `form:submitted` event.
@@ -150,8 +162,6 @@ interface FormInstance<TSchema extends FormSchema> {
150
162
  interface InitFormsOptions {
151
163
  core: EmbeddablesInstance;
152
164
  analyticsInstance?: AnalyticsInstance;
153
- /** Overrides the default production backend URL for R2 persistence writes. */
154
- baseUrl?: string;
155
165
  }
156
166
  interface FormsClient {
157
167
  initForm<const TSchema extends FormSchema>(options: {
@@ -164,7 +174,7 @@ interface FormsClient {
164
174
  * API — the Miro signature. Consumers pass only `core` and, optionally, an
165
175
  * analytics client.
166
176
  */
167
- declare function initForms(options: InitFormsOptions): FormsClient;
177
+ declare function initForms({ core, analyticsInstance }: InitFormsOptions): FormsClient;
168
178
  //#endregion
169
- export { FormSchema as _, SetResult as a, initForms as c, AnalyticsTrackResult as d, FieldConfig as f, FormFieldKey as g, FieldValidator as h, InitFormsOptions as i, AnalyticsInstance as l, FieldValidations as m, FormInstance as n, SubmitResult as o, FieldType as p, FormsClient as r, ValidateResult as s, FieldErrors as t, AnalyticsTrackEvent as u, FormValues as v, JsonValue as y };
170
- //# sourceMappingURL=form-Cumzg4Zx.d.ts.map
179
+ export { FormSchema as _, SetResult as a, JsonValue as b, initForms as c, AnalyticsTrackResult as d, FieldConfig as f, FormFieldKey as g, FieldValidator as h, InitFormsOptions as i, AnalyticsInstance as l, FieldValidations as m, FormInstance as n, SubmitResult as o, FieldType as p, FormsClient as r, ValidateResult as s, FieldErrors as t, AnalyticsTrackEvent as u, FormValues as v, ProtocolFieldId as y };
180
+ //# sourceMappingURL=form-CwUR8-8T.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-CwUR8-8T.d.ts","names":[],"sources":["../../shared-types/dist/json.types.d.ts","../src/core/config.ts","../../shared-types/dist/analytics-instance.types.d.ts","../src/core/form.ts"],"mappings":";;;;;;KAIY,+CAA+C;GACtD,cAAc;;;;KCHP;;UAMF;EACR;EACA;EACA;EACA;EACA;EACA;EACA,MAAM;;KAGI,eAAe,eAAe,YAAY,cAAc;EAClE,OAAO;EACP,QAAQ,SAAS,eAAe;;UAUxB,oBAAoB,cAAc;WACjC;WACA;WACA;WACA;WACA;;WAEA;;WAEA;WACA,iBAAiB;;WAEjB,SAAS,eAAe,iBAAiB;;UAG1C,eAAe,cAAc;WAC5B;WACA;WACA,MAAM;WACN,cAAc,oBAAoB;WAGlC;WACA;;KAUC,iBAAiB,KAAK,YAAY,eAAe,MAAK;KAEtD,sBAAsB,KAAK,YAAY,oBAAoB,MAAK;UAE3D;WACN;WACA;WACA,iBAAiB;;KAGvB,SAAS,gBAAgB,cAAc;KAEhC,aAAa,gBAAgB,cAAc,SAAS;KAEpD,WAAW,gBAAgB,iBACpC,KAAK,SAAS,YAAY,WAAW,iBAAiB;;KAI7C,gBAAgB,gBAAgB,eAAe,kBAAkB,uBAEzE,QAAQ,SAAS;EAAY;;;;;;;;;;;;;;;UCzEhB;EACb;GACC;;;UAGY;EACb;EACA;EACA;;;;;;;UAOa,kBAAkB,SAAS;EACxC,WAAW,OAAO,kBAAkB,WAAW,QAAQ;;EAEvD;EACA;;;;;KCAQ,YAAY,gBAAgB,cAAc,SACpD,QAAQ,OAAO,aAAa;UAGb,UAAU,gBAAgB;EACzC;EACA,QAAQ,YAAY;;EAEpB;;UAGe,aAAa,gBAAgB;EAC5C;EACA,QAAQ,YAAY;EACpB,QAAQ,QAAQ,WAAW;EAC3B;;UAGe,eAAe,gBAAgB;EAC9C;EACA,QAAQ,YAAY;EACpB,QAAQ,QAAQ,WAAW;;UAGZ,aAAa,gBAAgB;WACnC,KAAK;;;;;;;EAOd,IAAI,OAAO,QAAQ,WAAW,YAAY,QAAQ,UAAU;;EAE5D,IAAI,UAAU,aAAa,UAAU,KAAK,IAAI,WAAW,SAAS;;;;;;EAMlE,0BAA0B,UAAU,gBAAgB,UAClD,iBAAiB,IAEf,WAAW,SAAS,QAAQ;IAA6B,iBAAiB;;EAE9E,UAAU,QAAQ,WAAW;;;;;;;;;EAS7B,UAAU,QAAQ,aAAa;;;;;;;;;;EAU/B,SAAS,QAAQ,QAAQ,WAAW,YAAY,QAAQ,eAAe;EACvE,UAAU,YAAY;EACtB;;EAEA,UAAU;;UAgDK;EACf,MAAM;EACN,oBAAoB;;UAGL;EACf,eAAe,gBAAgB,YAAY;IACzC,QAAQ;IACR,gCAAgC,KAAK,aAAa,YAAY;MAC5D,aAAa;;;;;;;iBAQH,YAAY,MAAM,qBAAqB,mBAAmB"}
@@ -31,6 +31,7 @@ var ValidatorError = class extends FormsError {
31
31
  this.name = "ValidatorError";
32
32
  }
33
33
  };
34
+ const DEVELOPMENT_BASE_URL = void 0;
34
35
  /**
35
36
  * Returns null when persistence cannot be configured (missing publishable key
36
37
  * or fetch). The SDK keeps the no-op default in that case.
@@ -49,7 +50,7 @@ function resolvePersistenceConfig(config) {
49
50
  projectId,
50
51
  appUserId,
51
52
  publishableKey,
52
- baseUrl: config.baseUrl ?? "https://backend-worker.heysavvy.workers.dev",
53
+ baseUrl: config.baseUrl ?? DEVELOPMENT_BASE_URL ?? "https://backend-worker.heysavvy.workers.dev",
53
54
  fetch: fetchImpl,
54
55
  timeoutMs: config.timeoutMs ?? 1e4
55
56
  };
@@ -216,7 +217,7 @@ function writeFields({ storage, formKey, fields, fieldDefinitions }) {
216
217
  type: field.type,
217
218
  label: field.label,
218
219
  ...field.registryId === void 0 ? {} : { registryId: field.registryId },
219
- ...field.protocolQuestionId === void 0 ? {} : { protocolQuestionId: field.protocolQuestionId }
220
+ ...field.protocolFieldId === void 0 ? {} : { protocolFieldId: field.protocolFieldId }
220
221
  };
221
222
  }
222
223
  const next = {
@@ -269,7 +270,7 @@ function isStoredField(value) {
269
270
  if (!isSerializable({ value: value["value"] })) return false;
270
271
  if (typeof value["type"] !== "string" || !FIELD_TYPES$1.includes(value["type"]) || typeof value["label"] !== "string") return false;
271
272
  if (value["registryId"] !== void 0 && typeof value["registryId"] !== "string") return false;
272
- if (value["protocolQuestionId"] !== void 0 && typeof value["protocolQuestionId"] !== "string") return false;
273
+ if (value["protocolFieldId"] !== void 0 && typeof value["protocolFieldId"] !== "string") return false;
273
274
  return true;
274
275
  }
275
276
  //#endregion
@@ -318,7 +319,7 @@ function buildFieldUpdatedEvents({ fields, patch }) {
318
319
  field_value: value
319
320
  };
320
321
  if (field?.registryId !== void 0) event.registry_field_id = field.registryId;
321
- if (field?.protocolQuestionId !== void 0) event.protocol_field_id = field.protocolQuestionId;
322
+ if (field?.protocolFieldId !== void 0) event.protocol_field_id = field.protocolFieldId;
322
323
  return event;
323
324
  });
324
325
  }
@@ -408,8 +409,8 @@ function validateField({ field, path, seenKeys, patterns }) {
408
409
  if (typeof type !== "string" || !FIELD_TYPES.includes(type)) throw new SchemaError(`${path}.type: must be one of ${FIELD_TYPES.join(", ")}`);
409
410
  const registryId = field["registryId"];
410
411
  if (registryId !== void 0 && typeof registryId !== "string") throw new SchemaError(`${path}.registryId: must be a string`);
411
- const protocolQuestionId = field["protocolQuestionId"];
412
- if (protocolQuestionId !== void 0 && typeof protocolQuestionId !== "string") throw new SchemaError(`${path}.protocolQuestionId: must be a string`);
412
+ const protocolFieldId = field["protocolFieldId"];
413
+ if (protocolFieldId !== void 0 && typeof protocolFieldId !== "string") throw new SchemaError(`${path}.protocolFieldId: must be a string`);
413
414
  const validations = field["validations"];
414
415
  if (validations === void 0) return;
415
416
  validateValidations({
@@ -688,8 +689,11 @@ function mergeCustomValidations({ schema, customValidations }) {
688
689
  * API — the Miro signature. Consumers pass only `core` and, optionally, an
689
690
  * analytics client.
690
691
  */
691
- function initForms(options) {
692
- return createFormsClient(options);
692
+ function initForms({ core, analyticsInstance }) {
693
+ return createFormsClient({
694
+ core,
695
+ analyticsInstance
696
+ });
693
697
  }
694
698
  function createFormsClient({ core, analyticsInstance, baseUrl, storage, persistence }) {
695
699
  if (!hasRequiredCoreMethods(core)) throw new FormsError("initForms requires an initialized Embeddables core instance.");
@@ -711,6 +715,11 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
711
715
  customValidations
712
716
  }) });
713
717
  const declared = new Map(resolved.fields.map((field) => [field.key, field]));
718
+ const keyByProtocolFieldId = /* @__PURE__ */ new Map();
719
+ for (const field of resolved.fields) {
720
+ const protocolId = field.protocolFieldId;
721
+ if (typeof protocolId === "string" && protocolId.length > 0) keyByProtocolFieldId.set(protocolId, field.key);
722
+ }
714
723
  const resolvedStorage = resolveStorage({ storage });
715
724
  const listeners = /* @__PURE__ */ new Set();
716
725
  const notify = () => {
@@ -741,10 +750,10 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
741
750
  }
742
751
  if (mergedCount > 0) notify();
743
752
  };
744
- const recoverable = resolved.fields.filter((field) => (field.registryId !== void 0 || field.protocolQuestionId !== void 0) && state.bag[field.key] === void 0).map((field) => ({
753
+ const recoverable = resolved.fields.filter((field) => (field.registryId !== void 0 || field.protocolFieldId !== void 0) && state.bag[field.key] === void 0).map((field) => ({
745
754
  key: field.key,
746
755
  ...field.registryId === void 0 ? {} : { registryId: field.registryId },
747
- ...field.protocolQuestionId === void 0 ? {} : { protocolQuestionId: field.protocolQuestionId }
756
+ ...field.protocolFieldId === void 0 ? {} : { protocolFieldId: field.protocolFieldId }
748
757
  }));
749
758
  if (recoverable.length > 0) try {
750
759
  const result = persistence.recoverRegistryFields({
@@ -854,7 +863,7 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
854
863
  key,
855
864
  value,
856
865
  ...field?.registryId === void 0 ? {} : { registryId: field.registryId },
857
- ...field?.protocolQuestionId === void 0 ? {} : { protocolQuestionId: field.protocolQuestionId }
866
+ ...field?.protocolFieldId === void 0 ? {} : { protocolFieldId: field.protocolFieldId }
858
867
  };
859
868
  });
860
869
  firePersistence(() => persistence.savePartial({
@@ -903,6 +912,11 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
903
912
  if (!declared.has(fieldKey)) return void 0;
904
913
  return state.bag[fieldKey];
905
914
  };
915
+ const getValueByProtocolFieldId = ((protocolFieldId) => {
916
+ const fieldKey = keyByProtocolFieldId.get(protocolFieldId);
917
+ if (fieldKey === void 0) return void 0;
918
+ return get(fieldKey);
919
+ });
906
920
  const getAll = () => narrow(state.bag);
907
921
  const submit = () => {
908
922
  const snapshot = narrow(readBag());
@@ -1046,6 +1060,7 @@ function createFormInstance({ analyticsInstance, storage, persistence, schema, c
1046
1060
  key: schema.id,
1047
1061
  set,
1048
1062
  get,
1063
+ getValueByProtocolFieldId,
1049
1064
  getAll,
1050
1065
  submit,
1051
1066
  validate,
@@ -1086,4 +1101,4 @@ Object.defineProperty(exports, "initForms", {
1086
1101
  }
1087
1102
  });
1088
1103
 
1089
- //# sourceMappingURL=form-CffnaOiY.cjs.map
1104
+ //# sourceMappingURL=form-W_EyuKU4.cjs.map