{"version":3,"names":["createEffect","createMemo","createSignal","untrack","readOption","SNAPSHOT_KEYS","snapshotKeys","NOT_VALIDATED","Object","freeze","valid","undefined","errors","message","toValidity","result","Array","isArray","filter","s","length","isPromiseLike","v","then","createValidator","options","value","committed","setCommitted","ownedWrite","name","pendingThrow","setPendingThrow","generation","runValidate","event","reason","myGen","isStale","validateFn","validate","next","onValidityChange","defaultPrevented","resultOrPromise","err","ev","Event","cancelable","validity","onValueSettled","settled","prev","effect","error","cleanup","console","warn","api","VALIDATOR_SNAPSHOT_KEYS"],"sources":["create-validator.ts"],"sourcesContent":["import {\n\ttype Accessor,\n\tcreateEffect,\n\tcreateMemo,\n\tcreateSignal,\n\tuntrack,\n} from \"solid-js\";\nimport { type MaybeAccessor, readOption } from \"../maybe-accessor\";\nimport {\n\tSNAPSHOT_KEYS,\n\ttype Snapshottable,\n\tsnapshotKeys,\n} from \"../observability/snapshot\";\n\n// Stage 02 — error-contract spike.\n//\n// Validates RFC 05's claim that a compound's API surface should not carry a\n// parallel `error` accessor: validation *messages* (returned strings) live in\n// the data graph, validation *exceptions* (sync throws / async rejections)\n// propagate through the reactive graph to the nearest `<Errored>` boundary.\n// Two different concepts, two different channels — neither one is `c.error`.\n//\n// Companion doc: work-left/primitive-template.md §2.3.\n\n/**\n * What a validator function may return. A returned string (or array of\n * strings) is a validation **message** — surfaced to the consumer through\n * `validity().errors`. Distinct from a validator that throws or rejects: a\n * thrown error is treated as a programmer/system fault and propagates to\n * `<Errored>`.\n */\nexport type ValidationResult = string | readonly string[] | undefined | null;\n\nexport type ValidatorChangeReason = \"imperative\" | \"input\" | \"submit\";\n\nexport interface ValidatorValidity {\n\t/** `undefined` until the first validation has settled. */\n\tvalid: boolean | undefined;\n\t/** Empty when valid or pre-validation. */\n\terrors: readonly string[];\n\t/** Convenience: `errors[0] ?? \"\"`. */\n\tmessage: string;\n}\n\nexport interface CreateValidatorOptions<V> {\n\t/**\n\t * Reactive value being validated. Tracking the accessor in the\n\t * primitive's compute lets validation re-run when it flips.\n\t */\n\tvalue: MaybeAccessor<V>;\n\t/**\n\t * Pure validator. Returns a message string (or list) when invalid,\n\t * `undefined`/`null` when valid. May return a Promise of the same.\n\t *\n\t * **Throwing or rejecting is reserved for true errors** (network\n\t * failure, programmer bug). The primitive does NOT catch — exceptions\n\t * surface to the nearest `<Errored>` boundary in the consumer tree.\n\t */\n\tvalidate?: (value: V) => ValidationResult | Promise<ValidationResult>;\n\t/**\n\t * Recoverable side-effect that runs whenever a settled value lands.\n\t * Wired through a separate `createEffect({ effect, error })` bundle —\n\t * if it throws, the primitive logs and resets local bookkeeping\n\t * **without** propagating to `<Errored>`. Use for autosave, telemetry,\n\t * cross-field reactions: things the consumer should not have to wrap a\n\t * boundary around.\n\t */\n\tonValueSettled?: (value: V) => undefined | (() => void);\n\t/**\n\t * Pre-change cancellation hook. Called with the next validity snapshot,\n\t * the triggering event, and a structured `reason`. The consumer can\n\t * call `event.preventDefault()` to suppress the validity write.\n\t */\n\tonValidityChange?: (\n\t\tnext: ValidatorValidity,\n\t\tevent: Event,\n\t\treason: ValidatorChangeReason,\n\t) => void;\n}\n\nexport interface ValidatorApi<V> {\n\tvalue: Accessor<V>;\n\t/**\n\t * Latest committed validity. Reading inside an `<Errored>` boundary\n\t * surfaces validator exceptions; reading inside a `<Loading>` boundary\n\t * suspends while async validation is pending.\n\t *\n\t * **No `error` accessor accompanies this** — by design (RFC 05).\n\t * Returned message strings are not errors; they are typed feedback.\n\t */\n\tvalidity: Accessor<ValidatorValidity>;\n\t/**\n\t * Imperatively trigger validation. Awaiting the returned promise\n\t * observes async-validator rejections at the call site; reading\n\t * `validity()` afterwards re-throws them into the graph for `<Errored>`.\n\t * Pass an `event` to participate in pre-change cancellation.\n\t */\n\tvalidate: (\n\t\tevent?: Event,\n\t\treason?: ValidatorChangeReason,\n\t) => Promise<ValidatorValidity>;\n}\n\nconst NOT_VALIDATED: ValidatorValidity = Object.freeze({\n\tvalid: undefined,\n\terrors: Object.freeze([]) as readonly string[],\n\tmessage: \"\",\n});\n\nfunction toValidity(result: ValidationResult): ValidatorValidity {\n\tif (result == null) {\n\t\treturn { valid: true, errors: [], message: \"\" };\n\t}\n\tconst errors: readonly string[] = Array.isArray(result)\n\t\t? (result as readonly string[]).filter((s) => s.length > 0)\n\t\t: typeof result === \"string\" && result.length > 0\n\t\t\t? [result]\n\t\t\t: [];\n\tif (errors.length === 0) {\n\t\treturn { valid: true, errors: [], message: \"\" };\n\t}\n\treturn { valid: false, errors, message: errors[0] ?? \"\" };\n}\n\nfunction isPromiseLike<T>(v: unknown): v is PromiseLike<T> {\n\treturn (\n\t\ttypeof v === \"object\" &&\n\t\tv !== null &&\n\t\ttypeof (v as { then?: unknown }).then === \"function\"\n\t);\n}\n\nexport function createValidator<V>(\n\toptions: CreateValidatorOptions<V>,\n): ValidatorApi<V> {\n\tconst value: Accessor<V> = () => readOption(options.value);\n\n\t// Internal validity-store backed by a signal that rejects can poison.\n\t// On rejection we stash the error in `pendingThrow`; the public\n\t// `validity` memo re-reads it and throws into the graph so a `<Errored>`\n\t// boundary above the consumer's read site catches it.\n\tconst [committed, setCommitted] = createSignal<ValidatorValidity>(\n\t\tNOT_VALIDATED,\n\t\t{ ownedWrite: true, name: \"validator.committed\" },\n\t);\n\tconst [pendingThrow, setPendingThrow] = createSignal<unknown>(undefined, {\n\t\townedWrite: true,\n\t\tname: \"validator.pendingThrow\",\n\t});\n\n\tlet generation = 0;\n\n\tconst runValidate = async (\n\t\tv: V,\n\t\tevent: Event,\n\t\treason: ValidatorChangeReason,\n\t): Promise<ValidatorValidity> => {\n\t\tconst myGen = ++generation;\n\t\tconst isStale = () => myGen !== generation;\n\n\t\tconst validateFn = options.validate;\n\t\t// No validator → trivially valid. Useful for hooking pre-change\n\t\t// cancellation without writing a real `validate`.\n\t\tif (!validateFn) {\n\t\t\tconst next: ValidatorValidity = { valid: true, errors: [], message: \"\" };\n\t\t\toptions.onValidityChange?.(next, event, reason);\n\t\t\tif (event.defaultPrevented) return committed();\n\t\t\tsetPendingThrow(undefined);\n\t\t\tsetCommitted(next);\n\t\t\treturn next;\n\t\t}\n\n\t\t// Sync throws are intentionally not caught here — they re-throw at\n\t\t// the call site (consumer's `await c.validate()` rejects) AND\n\t\t// poison `validity()` so a downstream read inside `<Errored>` also\n\t\t// catches. This mirrors RFC 05's \"one error path\" promise.\n\t\tlet resultOrPromise: ValidationResult | Promise<ValidationResult>;\n\t\ttry {\n\t\t\tresultOrPromise = validateFn(v);\n\t\t} catch (err) {\n\t\t\tif (!isStale()) setPendingThrow(err);\n\t\t\tthrow err;\n\t\t}\n\n\t\tlet result: ValidationResult;\n\t\tif (isPromiseLike<ValidationResult>(resultOrPromise)) {\n\t\t\ttry {\n\t\t\t\tresult = await resultOrPromise;\n\t\t\t} catch (err) {\n\t\t\t\tif (!isStale()) setPendingThrow(err);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tif (isStale()) return committed();\n\t\t} else {\n\t\t\tresult = resultOrPromise;\n\t\t}\n\n\t\tconst next = toValidity(result);\n\t\toptions.onValidityChange?.(next, event, reason);\n\t\tif (event.defaultPrevented) return committed();\n\t\tsetPendingThrow(undefined);\n\t\tsetCommitted(next);\n\t\treturn next;\n\t};\n\n\tconst validate = (\n\t\tevent?: Event,\n\t\treason: ValidatorChangeReason = \"imperative\",\n\t): Promise<ValidatorValidity> => {\n\t\tconst ev = event ?? new Event(\"validate\", { cancelable: true });\n\t\treturn runValidate(untrack(value), ev, reason);\n\t};\n\n\t// Public validity accessor. The memo re-reads `pendingThrow` so a\n\t// poisoned validator surfaces to `<Errored>` on every read until the\n\t// next successful `validate()` clears it.\n\tconst validity = createMemo<ValidatorValidity>(\n\t\t() => {\n\t\t\tconst err = pendingThrow();\n\t\t\tif (err !== undefined) throw err;\n\t\t\treturn committed();\n\t\t},\n\t\t{ name: \"validator.validity\" },\n\t);\n\n\t// `onValueSettled` runs through the {effect, error} bundle. Errors\n\t// here are explicitly **internal** — they don't surface to the\n\t// consumer, by design. The bundle's `error` handler is the primitive's\n\t// recovery path: log, run cleanup, move on.\n\t//\n\t// **Observed contract — important.** RFC 01's bundle catches exceptions\n\t// thrown from the **compute** phase, not the effect phase. So the\n\t// throwing work has to ride in the compute body. The empty `effect`\n\t// callback is the price of using the bundle as a try/catch around\n\t// per-tick side-effect work.\n\tif (options.onValueSettled) {\n\t\tconst settled = options.onValueSettled;\n\t\tcreateEffect<V>(\n\t\t\t(prev) => {\n\t\t\t\tconst v = value();\n\t\t\t\tif (prev !== undefined) {\n\t\t\t\t\t// Run cleanup from the prior tick before the new\n\t\t\t\t\t// side-effect; mirrors React-effect lifecycle.\n\t\t\t\t}\n\t\t\t\tsettled(v);\n\t\t\t\treturn v;\n\t\t\t},\n\t\t\t{\n\t\t\t\teffect: () => undefined,\n\t\t\t\terror: (err, cleanup) => {\n\t\t\t\t\t// In a real component this would route through\n\t\t\t\t\t// `DEV.diagnostics` so attribution names the compound\n\t\t\t\t\t// + slot (stage 04). For the spike, console.warn is\n\t\t\t\t\t// enough to demonstrate the contract.\n\t\t\t\t\tif (typeof console !== \"undefined\") {\n\t\t\t\t\t\tconsole.warn(\"[createValidator] onValueSettled threw\", err);\n\t\t\t\t\t}\n\t\t\t\t\tcleanup();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tconst api: ValidatorApi<V> = { value, validity, validate };\n\t(api as ValidatorApi<V> & Snapshottable)[SNAPSHOT_KEYS] =\n\t\tVALIDATOR_SNAPSHOT_KEYS;\n\treturn api;\n}\n\nconst VALIDATOR_SNAPSHOT_KEYS = snapshotKeys<ValidatorApi<unknown>>(\n\t\"value\",\n\t\"validity\",\n);\n"],"mappings":"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","ignoreList":[]}