UNPKG

svelte-specma

Version:

Svelte store for data validation using Specma

1 lines 63.4 kB
{"version":3,"file":"svelte-specma.cjs.mjs","sources":["../src/flexDerived.js","../src/util.js","../src/collDerived.js","../src/constants.js","../src/configure.js","../src/predSpecable.js","../src/writableByValue.js","../src/collSpecable.js","../src/register.js","../src/specable.js"],"sourcesContent":["import { readable, writable } from \"svelte/store\";\n\n/**\n * Minimal helpers\n * @private\n */\nconst identity = (x) => x;\nconst noop = () => {};\n\n/**\n * flexDerived\n *\n * Create a derived-like Svelte store from a dynamic list of input stores.\n * Unlike Svelte's built-in derived, this helper allows the set of source\n * stores to change over time (stores can be added/removed). It exposes\n * control methods to update the tracked stores and keeps a readable store\n * for subscribers that publishes the combined result.\n *\n * Behaviour:\n * - `fn` receives an array of source values in the order of the tracked stores.\n * - If `fn.length < 2` (arity 1) its return value is published directly.\n * - If `fn.length >= 2` (arity 2) `fn` is called with (values, publish) and may\n * call `publish` asynchronously; its return value may be a cleanup function.\n *\n * Parameters:\n * - initialStores: Array of Svelte stores to subscribe to initially.\n * - fn: mapping function (values) => result OR (values, publish) => (cleanup|void)\n * - initialValue: optional initial published value\n *\n * Return value (object):\n * - subscribe(fn): subscribe to derived values (Svelte readable contract)\n * - include(...stores): add stores to the tracked set\n * - exclude(...stores): remove stores from the tracked set\n * - set(newStoresArray): replace the tracked set of stores\n * - update(fn): update the tracked set using fn(prev) => next\n * - stores: internal writable store holding the current list of tracked stores\n *\n * Example:\n * const derived = flexDerived([storeA, storeB], (values) => values.join(\"-\"), \"\");\n * derived.subscribe(v => console.log(v));\n * derived.include(storeC);\n *\n * Notes:\n * - The implementation ensures ordering of values matches the tracked store order.\n * - When no subscribers remain the helper unsubscribes from all source stores.\n */\nfunction flexDerived(initialStores = [], fn = identity, initialValue) {\n /* Used for ordering values */\n let _stores = initialStores;\n /* Store of stores. When last subscriber unsubscribes,\n * will stop all stores subs. */\n const $stores = writable(initialStores);\n\n /* Callback function can use a second argument, a `publish` function to set result asynchronously.\n * If not used, the return value of the function is the actual result to publish.\n * Otherwise, the return value might be a function to call before each execution\n * and when store is unsubscribed. */\n const auto = fn.length < 2;\n\n /* Use a readable store to manage subscribers. */\n const mainStore = readable(initialValue, (publish) => {\n let initialized = false;\n\n let cleanup = noop;\n let pending = 0;\n let unsubs = new Map();\n let values = new Map();\n\n /* Unsubscribe saved stores not included in a new list of stores\n * and create a new Map of unsub by store, reusing old ones when they exist. */\n function updateUnsubs(stores = []) {\n stopUnusedSubs(stores);\n unsubs = new Map(stores.map((store) => [store, unsubs.get(store)]));\n }\n\n function stopUnusedSubs(usedStores = []) {\n unsubs.forEach((unsub, store) => {\n if (!usedStores.includes(store) && unsub) unsub();\n });\n }\n\n function stopSubscriptions() {\n unsubs.forEach((unsub) => unsub && unsub());\n }\n\n /* Create a new map of values by store, reusing old ones when they exist. */\n function updateValues(stores = []) {\n values = new Map(stores.map((store) => [store, values.get(store)]));\n }\n\n /* Recompute and publish the combined result on values,\n * using `_stores` to ensure values order consistency. */\n function sync() {\n if (pending) return;\n cleanup();\n const vals = _stores.reduce(\n (acc, store) => (values.has(store) ? [...acc, values.get(store)] : acc),\n []\n );\n const result = fn(vals, publish);\n\n if (auto) {\n publish(result);\n } else {\n cleanup = typeof result === \"function\" ? result : noop;\n }\n }\n\n const unsubscribe = $stores.subscribe((stores) => {\n updateUnsubs(stores);\n updateValues(stores);\n\n /* Subscribe to each store if not alreay done.\n * When a any store value changes,\n * combined result will be updated and published. */\n _stores = [];\n stores.forEach((store, i) => {\n _stores[i] = store;\n\n if (!unsubs.get(store)) {\n unsubs.set(\n store,\n store.subscribe(\n (value) => {\n values.set(store, value);\n pending &= ~(1 << i);\n if (initialized) sync();\n },\n () => {\n pending |= 1 << i;\n }\n )\n );\n }\n });\n if (initialized) sync();\n });\n\n initialized = true;\n sync();\n\n return function stop() {\n unsubscribe();\n stopSubscriptions();\n cleanup();\n };\n });\n\n /* Exclude one or mode stores from the list. */\n function exclude(...stores) {\n $stores.update((prev) => prev.filter((store) => !stores.includes(store)));\n }\n\n /* Include one or more stores into the list. */\n function include(...stores) {\n $stores.update((prev) => [...prev, ...stores]);\n }\n\n return {\n exclude,\n include,\n set: $stores.set,\n stores: $stores,\n subscribe: mainStore.subscribe,\n update: $stores.update,\n };\n}\n\nexport default flexDerived;\n","/**\n * util.js\n *\n * Small utility helpers used throughout the library.\n * - Type/shape inspectors: typeOf, isColl, isFunc, isStore\n * - Collection helpers: entries, fromEntries, values, keys, merge\n * - Generic helpers: identity, genRandomId, get, getPath, keepForwardPath\n * - Equality: equals (uses fast-deep-equal) with normalization for Dates and collections\n *\n * Functions include JSDoc for in-editor hints and to clarify expected inputs/outputs.\n */\n\nimport fastEquals from \"fast-deep-equal\";\n\n/**\n * Identity function.\n * @template T\n * @param {T} x\n * @returns {T}\n */\nexport const identity = (x) => x;\n\n/**\n * Check whether a value is a collection (array, map or plain object).\n * @param {any} x\n * @returns {boolean}\n */\nexport const isColl = (x) => [\"array\", \"map\", \"object\"].includes(typeOf(x));\n\n/**\n * Check whether a value is a function.\n * @param {any} x\n * @returns {boolean}\n */\nexport const isFunc = (x) => typeof x === \"function\";\n\n/**\n * Heuristic to detect Svelte stores (object with a subscribe function).\n * @param {any} x\n * @returns {boolean}\n */\nexport const isStore = (x) => x && x.subscribe && isFunc(x.subscribe);\n\n/**\n * Return a normalized low-level type string for common JS types.\n * Examples: [], {} and new Map() => \"array\", \"object\", \"map\"\n * @param {any} obj\n * @returns {string}\n */\nexport const typeOf = (obj) =>\n ({}.toString.call(obj).split(\" \")[1].slice(0, -1).toLowerCase());\n\n/**\n * Get entries from a collection in a uniform [key, value] format.\n * Supports arrays, maps and objects.\n * @param {Array|Map|Object} coll\n * @returns {Array<[any, any]>}\n */\nexport function entries(coll) {\n const fn = {\n array: () => coll.map((v, i) => [i, v]),\n map: () => Array.from(coll.entries()),\n object: () => Object.entries(coll),\n }[typeOf(coll)];\n\n return fn ? fn(coll) : [];\n}\n\n/**\n * Build a collection of the requested target type from an entries array.\n * - array: returns values array (index ignored)\n * - map: returns new Map(entries)\n * - object: returns Object.fromEntries(entries)\n * If unknown toType is provided, falls back to building a Map.\n *\n * @param {Array<[any, any]>} entriesArr\n * @param {'array'|'map'|'object'} toType\n * @returns {Array|Map|Object}\n */\nexport function fromEntries(entriesArr, toType) {\n const fn = {\n array: () => entriesArr.map(([, val]) => val),\n map: () => new Map(entriesArr),\n object: () => Object.fromEntries(entriesArr),\n }[toType];\n return fn ? fn() : fromEntries(entriesArr, \"map\");\n}\n\n/**\n * Extract values from a collection.\n * @param {Array|Map|Object} coll\n * @returns {Array<any>}\n */\nexport function values(coll) {\n const fn = {\n array: () => [...coll],\n map: () => Array.from(coll.values()),\n object: () => Object.values(coll),\n }[typeOf(coll)];\n\n return fn ? fn(coll) : [];\n}\n\n/**\n * Extract keys/indices from a collection.\n * @param {Array|Map|Object} coll\n * @returns {Array<any>}\n */\nexport function keys(coll) {\n const fn = {\n array: () => coll.map((v, i) => i),\n map: () => Array.from(coll.keys()),\n object: () => Object.keys(coll),\n }[typeOf(coll)];\n\n return fn ? fn(coll) : [];\n}\n\n/**\n * Merge multiple collections of the same type (rightmost arguments take precedence).\n * - array: preserve longest array, fill missing entries from previous arrays\n * - map: new Map of provided entries (later maps override)\n * - object: Object.assign({}, ...colls)\n *\n * @param {...(Array|Map|Object)} args\n * @returns {Array|Map|Object}\n * @throws {TypeError} if collections are of mixed types\n */\nexport function merge(...args) {\n const colls = args.filter(isColl);\n const type = typeOf(colls[0]);\n if (!colls.every((coll) => typeOf(coll) === type)) {\n const collTypes = colls.map(typeOf).join(\", \");\n throw new TypeError(\n `Collections must be of same type. Received '${collTypes}'.`\n );\n }\n\n const fn = {\n array: () =>\n colls.reduce((acc, coll) => {\n if (coll.length >= acc.length) return coll;\n return [...coll, ...acc.slice(coll.length)];\n }, []),\n map: () => new Map(colls.map((coll) => coll.entries())),\n object: () => Object.assign({}, ...colls),\n }[type];\n\n if (!fn) throw new Error(`'merge' not implemented yet for ${type}`);\n\n return fn();\n}\n\n/**\n * Generate a short numeric random id (string).\n * @returns {string}\n */\nexport function genRandomId() {\n return (Math.random() * 1e9).toFixed(0);\n}\n\n/**\n * Safely get a value by key from a collection (array, map, object).\n * @param {string|number} key\n * @param {Array|Map|Object} coll\n * @returns {any|undefined}\n */\nexport function get(key, coll) {\n const fn = {\n array: () => coll[key],\n map: () => coll.get(key),\n object: () => coll[key],\n }[typeOf(coll)];\n\n return fn ? fn(key, coll) : undefined;\n}\n\n/**\n * Resolve a nested value given a path (array of keys/indices).\n * @param {Array<string|number>} [path=[]]\n * @param {any} value\n * @returns {any}\n */\nexport function getPath(path = [], value) {\n return path.reduce((parent, key) => get(key, parent), value);\n}\n\n/**\n * Count occurrences of \"../\" or terminal \"..\" segments in a path-like string.\n * Used to detect ancestor references.\n * @param {string} [str=\"\"]\n * @returns {number}\n */\nexport function countPathAncestors(str = \"\") {\n return (str.match(/\\.\\.\\/|\\.\\.$/g) || []).length;\n}\n\n/**\n * Convert a forward-slash separated path string into an array of path segments,\n * ignoring empty segments and segments that start with '.' (relative/ancestor tokens).\n * Numeric segments are converted to numbers for array access.\n *\n * Example: \"items/0/name\" -> [\"items\", 0, \"name\"]\n *\n * @param {string} [str=\"\"]\n * @returns {Array<string|number>}\n */\nexport function keepForwardPath(str = \"\") {\n return str.split(\"/\").reduce((acc, node) => {\n if (!node || node.startsWith(\".\")) return acc;\n const index = parseInt(node, 10);\n return [...acc, isNaN(index) ? node : index];\n }, []);\n}\n\n/**\n * Equality check that normalizes inputs before using deep equality.\n * - Dates are compared by valueOf()\n * - Collections have undefined values removed before comparison\n *\n * @param {any} a\n * @param {any} b\n * @param {Function} [eqBy=defaultEqBy] - optional normalizer function\n * @returns {boolean}\n */\nexport function equals(a, b, eqBy = defaultEqBy) {\n const [_a, _b] = [a, b].map(eqBy);\n return _a === _b || fastEquals(_a, _b);\n}\n\n/**\n * Default normalizer used by equals.\n * - Dates -> numeric value\n * - Collections -> remove undefined entries recursively\n *\n * @param {any} x\n * @returns {any}\n */\nfunction defaultEqBy(x) {\n if (x instanceof Date) return x.valueOf();\n if (isColl(x)) return removeUndefined(x);\n return x;\n}\n\n/**\n * Recursively remove undefined values from collections to avoid false differences\n * when comparing shapes where undefined properties/entries should be ignored.\n *\n * @param {Array|Map|Object} x\n * @returns {Array|Map|Object}\n */\nfunction removeUndefined(x) {\n if (!isColl(x)) return x;\n return fromEntries(\n entries(x).reduce(\n (acc, [key, val]) =>\n val === undefined ? acc : [...acc, [key, removeUndefined(val)]],\n []\n ),\n typeOf(x)\n );\n}\n","import flexDerived from \"./flexDerived\";\nimport { entries, fromEntries, identity, typeOf } from \"./util\";\n\n/**\n * Build a small snapshot of the collection of stores.\n *\n * @param {Object|Array|Map} coll - collection mapping keys -> store\n * @returns {{\n * coll: (Object|Array|Map),\n * collType: string,\n * storesEntries: Array<[any, any]>,\n * keys: Array<any>,\n * stores: Array<any>\n * }}\n */\nfunction deriveState(coll) {\n const storesEntries = entries(coll);\n return {\n coll,\n collType: typeOf(coll),\n storesEntries,\n keys: storesEntries.map(([key]) => key),\n stores: storesEntries.map(([, store]) => store),\n };\n}\n\n/**\n * collDerived\n *\n * A helper that adapts a dynamic collection of Svelte stores into a single\n * derived store. The key feature is that the collection shape can change\n * over time (stores added/removed); calling `.set(newColl)` updates the\n * internal state so subsequent derived updates subscribe to the new stores.\n *\n * Parameters:\n * - initialColl: a collection (object/array/Map) whose values are Svelte stores\n * - fn: a mapping function => receives an indexed view of the current stores\n * (object with { coll, collType, storesEntries, keys, stores }) and\n * should return the derived value. If fn.length >= 2 it is treated as\n * asynchronous and receives a second `set` callback to update the result.\n * - initialValue: optional initial value for the derived store\n *\n * Returns an object with:\n * - subscribe(fn): subscribe to derived values\n * - set(newColl): replace the tracked collection so derived subscriptions\n * will follow the new set of stores\n */\nfunction collDerived(initialColl, fn = identity, initialValue) {\n let state = deriveState(initialColl);\n\n /* The callback passed to `flexDerived` can have arity 1 or 2.\n * The arity should match the one of the provided function.\n * When using arity 2, a set function is provided so that\n * result can be used asynchronously */\n const auto = fn.length < 2;\n const index$stores = ($stores) =>\n fromEntries(\n $stores.map((store, idx) => [state.keys[idx], store]),\n state.collType\n );\n\n const values = flexDerived(\n state.stores,\n auto\n ? ($stores) => fn(index$stores($stores))\n : ($stores, _set) => fn(index$stores($stores), _set),\n initialValue\n );\n\n function set(newColl) {\n state = deriveState(newColl);\n values.set(state.stores);\n }\n\n return {\n set,\n subscribe: values.subscribe,\n };\n}\n\nexport default collDerived;\n","export const ALWAYS_VALID = { valid: true };\n","/**\n * configure.js\n *\n * Small adapter to wire a Specma-compatible implementation into this library.\n *\n * This module:\n * - declares the list of required Specma functions the library depends on\n * - exports a mutable `specma` reference that other modules import\n * - provides `configure(specmaFns)` to set the implementation (must provide\n * required functions)\n * - provides `ensureConfigured()` to assert configuration and throw a clear\n * error when not configured\n *\n * Usage:\n * import { configure } from \"svelte-specma\";\n * configure(specma); // specma must expose required helper functions\n */\n\n/**\n * List of function names that a Specma implementation must provide.\n * These functions are accessed by the library at runtime.\n * @type {string[]}\n */\nconst REQUIRED_SPECMA_FNS = [\n \"and\",\n \"getMessage\",\n \"getPred\",\n \"getSpread\",\n \"isOpt\",\n \"validatePred\",\n];\n\n/**\n * Error message used when the library has not been configured correctly.\n * @type {string}\n */\nconst CONFIG_ERROR_MSG =\n \"SvelteSpecma must be configured with a valid Specma version.\";\n\n/**\n * Mutable export that will hold the configured Specma implementation.\n * Other modules import this and expect it to be populated by calling `configure`.\n * It is `undefined` until `configure()` is called successfully.\n * @type {object|undefined}\n */\nexport let specma = undefined;\n\n/**\n * ensureConfigured\n *\n * Throw a TypeError with a descriptive message if the library has not been\n * configured with a valid Specma object.\n *\n * Call this at the start of functions that depend on `specma` to provide a\n * clear runtime failure rather than failing with obscure errors later.\n *\n * @throws {TypeError} when `specma` is not set\n */\nexport function ensureConfigured() {\n if (!specma) {\n throw new TypeError(CONFIG_ERROR_MSG);\n }\n}\n\n/**\n * configure\n *\n * Provide a Specma-compatible implementation to the library.\n * The provided object must implement the functions listed in REQUIRED_SPECMA_FNS.\n *\n * Example:\n * import * as specma from \"specma\";\n * configure(specma);\n *\n * @param {object} specmaFns - an object implementing required Specma functions\n * @throws {TypeError} if the provided object is missing required functions\n */\nexport default function configure(specmaFns) {\n if (!specmaFns) {\n throw new TypeError(CONFIG_ERROR_MSG);\n }\n\n REQUIRED_SPECMA_FNS.forEach((key) => {\n if (typeof specmaFns[key] !== \"function\") {\n throw new TypeError(`'${key}' must be a function provided by 'specma'`);\n }\n });\n specma = specmaFns;\n}\n","import { derived, get as getStoreValue, writable } from \"svelte/store\";\nimport { ALWAYS_VALID } from \"./constants\";\nimport { countPathAncestors, equals, getPath, keepForwardPath } from \"./util\";\nimport collDerived from \"./collDerived\";\nimport { specma, ensureConfigured } from \"./configure\";\nimport writableByValue from \"./writableByValue\";\n\nconst alwaysTrue = () => true;\nconst isMissing = (x) => [undefined, null, \"\"].includes(x);\nconst defaultChangePred = (a, b) => !equals(a, b);\n\nconst reqSpec = (x) => !isMissing(x) || specma.getMessage(\"isRequired\");\n\n/**\n * Create a predicate spec-aware Svelte store for a single value.\n *\n * The store validates a primitive or non-collection value against a Specma\n * predicate spec and exposes helper methods like `.activate()`, `.set()`,\n * `.reset()` and `.submit()`.\n *\n * @param {any} initialValue - initial value to validate\n * @param {Object} [options]\n * @param {Function} [options.changePred] - (a,b)=>boolean, determines changed state\n * @param {any} [options.id] - optional identifier\n * @param {boolean} [options.required] - is value required\n * @param {any} [options.spec] - Specma spec (predicate)\n * @param {Function} [options.onSubmit] - optional submit handler\n * @param {Object} [_extra] - internal helpers (e.g. getAncestor)\n * @returns {import('svelte/store').Readable}\n */\nexport default function predSpecable(\n initialValue,\n { changePred = defaultChangePred, id, required, spec, onSubmit } = {},\n _extra = {}\n) {\n ensureConfigured();\n const { and, getPred, validatePred } = specma;\n\n const { getAncestor } = _extra;\n const pred = getPred(spec) || alwaysTrue;\n const isRequired = !!required;\n const ownSpec = isRequired ? and(reqSpec, pred) : pred;\n\n const contextStores = {};\n const context = collDerived(contextStores);\n\n function addContext(relPath) {\n /* If `getFrom` has already been called once,\n * context store is already tracking the value. */\n if (contextStores[relPath]) return;\n\n const ancestor = getAncestor(countPathAncestors(relPath));\n if (!ancestor) return;\n\n const pathSinceAncestor = keepForwardPath(relPath);\n\n contextStores[relPath] = derived(ancestor, ($ancestor, set) => {\n const ancestorValue = $ancestor.value;\n if (!ancestorValue) return;\n\n const curr = contextStores[relPath].value;\n const next = getPath(pathSinceAncestor, ancestorValue);\n if (!equals(curr, next)) {\n contextStores[relPath].value = next;\n set(next);\n }\n });\n context.set(contextStores);\n\n /* If context has just been created, it won't be accessible\n * in the derived store at first.\n * In that case, return the static store value. */\n return getPath(pathSinceAncestor, getStoreValue(ancestor).value);\n }\n\n let currPromise;\n let _initialValue = initialValue;\n\n const active = writable(false);\n const submitting = writable(false);\n const value = writableByValue(_initialValue);\n\n const store = derived(\n [active, value, context, submitting],\n ([$active, $value, $context, $submitting], set) => {\n currPromise = undefined;\n\n function getFrom(relPath) {\n if (!contextStores[relPath]) {\n return addContext(relPath);\n }\n return $context[relPath];\n }\n\n const shouldValidate = $active && ($value !== undefined || required);\n\n const result = enhanceResult(\n shouldValidate ? validatePred(ownSpec, $value, getFrom) : ALWAYS_VALID\n );\n const baseArgs = {\n active: $active,\n changePred,\n initialValue: _initialValue,\n id,\n result,\n submitting: $submitting,\n value: $value,\n };\n\n currPromise = result.promise;\n\n set(interpretState(baseArgs));\n\n if (result.valid === null) {\n result.promise.then((resolvedResult) => {\n /* Promise might be outdated */\n if (result.promise !== currPromise) return;\n\n set(interpretState({ ...baseArgs, result: resolvedResult }));\n });\n }\n }\n );\n\n async function activate(bool = true) {\n active.set(bool);\n // Let derived subscribers run before reading currPromise\n await Promise.resolve();\n const res = await currPromise;\n return res.valid;\n }\n\n async function submit() {\n if (!onSubmit) return;\n submitting.set(true);\n try {\n const valid = await activate();\n if (!valid) return false;\n\n const currValue = getStoreValue(value);\n await onSubmit(currValue, mainStore);\n return true;\n } finally {\n submitting.set(false);\n }\n }\n\n const mainStore = {\n id,\n isRequired,\n spec: pred,\n\n activate,\n\n reset(newValue = _initialValue) {\n _initialValue = newValue;\n this.activate(false);\n this.set(newValue);\n },\n\n set: (newValue, shouldActivate = false) => {\n value.set(newValue);\n if (shouldActivate) activate();\n },\n\n submit,\n\n subscribe: store.subscribe,\n };\n\n return mainStore;\n}\n\nfunction enhanceResult(res) {\n return {\n ...res,\n promise: res.promise\n ? res.promise.then((promised) => enhanceResult(promised))\n : Promise.resolve(res),\n };\n}\n\nfunction interpretState({\n active,\n changePred,\n id,\n initialValue,\n result,\n submitting,\n value,\n}) {\n const changed = changePred(value, initialValue);\n return {\n active,\n changed,\n error: result.valid === false && result.reason,\n id,\n initialValue,\n promise: result.promise || Promise.resolve(result),\n submitting,\n valid: !!result.valid,\n validating: result.valid === null,\n value: changed ? value : initialValue,\n };\n}\n","import { writable } from \"svelte/store\";\nimport equals from \"fast-deep-equal\";\n\n/**\n * Writable store that avoids updates when new value is deep-equal to current.\n *\n * This is a limited version of Svelte's `writable` that performs a deep value\n * equality check before calling the underlying store `set`, preventing\n * unnecessary subscriber notifications and derived recomputations.\n *\n * @param {any} initialValue - initial store value\n * @param {...any} rest - additional args forwarded to `writable`\n * @returns {{ set: (newValue:any) => void, subscribe: import('svelte/store').Subscriber }}\n */\nexport default function wriableByValue(initialValue, ...rest) {\n let _value = initialValue;\n const store = writable(initialValue, ...rest);\n return {\n set(newValue) {\n if (equals(newValue, _value)) return;\n _value = newValue;\n store.set(_value);\n },\n subscribe: store.subscribe,\n };\n}\n","import { get as getStoreValue, writable } from \"svelte/store\";\nimport collDerived from \"./collDerived\";\nimport flexDerived from \"./flexDerived\";\nimport predSpecable from \"./predSpecable\";\nimport {\n entries,\n fromEntries,\n genRandomId,\n get,\n isColl,\n keys,\n merge,\n typeOf,\n values,\n} from \"./util\";\nimport { specma, ensureConfigured } from \"./configure\";\n\n/**\n * collSpecable\n *\n * A spec-aware Svelte store factory for collection-like data (objects, arrays,\n * Maps). It composes child specable stores (predSpecable or nested collSpecable)\n * for each entry in the collection and exposes an aggregate \"status\" store\n * that mirrors validation state, errors and submission state.\n *\n * Key responsibilities:\n * - Compose child stores according to `fields`, `spec`, `getId`, and `required`.\n * - Keep an aggregate derived value that merges children values with a base\n * collection value (supports \"spread\" semantics).\n * - Provide manipulation helpers: add, remove, set, reset, update, activate, submit.\n * - Produce a Svelte-compatible subscribe() that emits the aggregated status.\n *\n * Parameters:\n * - initialValue: the initial collection value (array/object/Map or undefined)\n * - options: configuration object:\n * - changePred, fields, getId, id, required, spec, onSubmit\n * - _extra: internal helpers (used for recursion; supplies specable and getAncestor)\n *\n * Returns an object with:\n * - id, isRequired, spec, stores\n * - activate(), add(), getChild(), getChildren(), remove(), reset(), set(), update()\n * - children: { subscribe } (stores of children)\n * - submit(), subscribe(fn) (subscribe to aggregated status)\n */\nexport default function collSpecable(\n initialValue,\n { changePred, fields, getId, id, required, spec, onSubmit } = {},\n _extra = {}\n) {\n ensureConfigured();\n const { getPred, getSpread, isOpt } = specma;\n\n let collValue = initialValue; // For static properties\n let isUndef = collValue === undefined;\n\n const { getAncestor } = _extra;\n const collDefiner = [fields, spec, initialValue].find(isColl);\n const collType = typeOf(collDefiner);\n const isRequired = required && !isOpt(required);\n const spreadGetId = getSpread(getId);\n const spreadSpec = getSpread(spec);\n const spreadFields = getSpread(fields);\n const spreadRequired = getSpread(required);\n const isSpread =\n spreadSpec ||\n spreadFields ||\n spreadRequired ||\n spreadGetId ||\n collType === \"array\";\n\n const valueKeys = isSpread ? keys(initialValue) : [];\n const allKeys = new Set(\n fields\n ? [...keys(fields), ...valueKeys]\n : [...keys(spec), ...keys(required), ...valueKeys]\n );\n\n const ownGetId = getPred(getId);\n const idGen = (v, k) => {\n if (ownGetId) return ownGetId(v, k);\n if (collType === \"array\") return genRandomId();\n return k;\n };\n\n const ownSpecable = predSpecable(\n initialValue,\n {\n changePred: getPred(changePred),\n id,\n required: isRequired,\n spec,\n },\n _extra\n );\n\n const createChildEntry = (key, val) => {\n const subChangePred = get(key, changePred) || getSpread(changePred);\n const subVal = val;\n const subSpec = get(key, spec) || spreadSpec;\n const subGetId = get(key, getId) || spreadGetId;\n const subId = idGen(subVal, key);\n const subFields = get(key, fields) || spreadFields;\n const subRequired = get(key, required) || spreadRequired;\n\n const subStore = _extra.specable(\n subVal,\n {\n spec: subSpec,\n changePred: subChangePred,\n id: subId,\n getId: subGetId,\n fields: subFields,\n required: subRequired,\n },\n {\n getAncestor: (n) =>\n n <= 1 || !getAncestor ? ownSpecable : getAncestor(n - 1),\n }\n );\n\n return [key, { ...subStore, id: subId }];\n };\n\n let childrenStores = fromEntries(\n Array.from(allKeys).map((key) =>\n createChildEntry(key, get(key, initialValue))\n ),\n collType\n );\n const children = writable(childrenStores);\n const submitting = writable(false);\n\n const derivedValue = collDerived(childrenStores, ($childrenStores) => {\n if (isUndef) return undefined;\n\n const $childrenEntries = entries($childrenStores);\n const $childrenValues = $childrenEntries.map(([key, state]) => [\n key,\n state.value,\n ]);\n const childrenValue = fromEntries($childrenValues, collType);\n const value = isSpread ? childrenValue : merge(collValue, childrenValue);\n isUndef = value === undefined;\n return value;\n });\n\n const aggregateStatusStores = () => [\n submitting,\n ownSpecable,\n ...values(childrenStores),\n ];\n\n const status = flexDerived(aggregateStatusStores(), ($statusStores) => {\n const [$submitting, $ownSpecable, ...$children] = $statusStores;\n\n const combined = isUndef\n ? $ownSpecable\n : [$ownSpecable, ...$children].reduce(combineChildren);\n\n if (combined.active !== false) ownSpecable.activate();\n\n const { value, error } = $ownSpecable;\n\n const details = Object.fromEntries([\n [\"_\", $ownSpecable],\n ...(isUndef ? [] : $children.map((child) => [child.id, child])),\n ]);\n\n const errors = detailsToErrors(details, id);\n const collErrors = errors.filter(({ isColl }) => isColl);\n\n return {\n ...combined,\n id,\n initialValue: $ownSpecable.initialValue,\n value,\n error,\n errors,\n collErrors,\n details,\n submitting: $submitting,\n };\n });\n\n function setChildrenStores(newChildrenStores) {\n childrenStores = newChildrenStores;\n children.set(newChildrenStores);\n derivedValue.set(newChildrenStores);\n status.set(aggregateStatusStores());\n }\n\n function addChildren(coll) {\n if (!coll) return;\n\n const newEntries = keys(coll).map((key) =>\n createChildEntry(key, get(key, coll))\n );\n const updatedStores = fromEntries(\n [...entries(childrenStores), ...newEntries],\n collType\n );\n setChildrenStores(updatedStores);\n }\n\n function removeChildrenById(idsToRemove = []) {\n if (idsToRemove.length < 1) return;\n\n const updatedStores = fromEntries(\n entries(childrenStores).filter(\n ([, store]) => !idsToRemove.includes(store.id)\n ),\n collType\n );\n\n setChildrenStores(updatedStores);\n }\n\n function setValue(coll, { partial = false, reset = false } = {}) {\n const setMethod = reset ? \"reset\" : \"set\";\n collValue = !reset && partial && !isSpread ? merge(collValue, coll) : coll;\n isUndef = collValue === undefined;\n ownSpecable[setMethod](collValue);\n\n const childrenEntries = entries(childrenStores);\n\n childrenEntries.forEach(([key, store]) => {\n const newValue = get(key, coll);\n if (partial && newValue === undefined) return;\n store[setMethod](newValue, partial);\n });\n if (!isSpread) return;\n\n /* If collection allows spread children... */\n\n /* Add `coll` entries that are not yet part of the children stores. */\n const childrenKeys = keys(childrenStores);\n const missingChildrenEntries = entries(coll).filter(\n ([key]) => !childrenKeys.includes(key)\n );\n if (missingChildrenEntries.length > 0) {\n addChildren(fromEntries(missingChildrenEntries, collType));\n }\n\n if (partial) return;\n\n /* If update is not partial, remove children stores that do not store\n * a collection value anymore (garbage collection). */\n const collKeys = keys(coll);\n const unusedIds = childrenEntries.reduce((acc, [key, childStore]) => {\n return collKeys.includes(key) ? acc : [...acc, childStore.id];\n }, []);\n removeChildrenById(unusedIds);\n }\n\n function activate(bool = true) {\n const storesToActivate = [\n ownSpecable,\n ...(isUndef ? [] : values(childrenStores)),\n ];\n const promises = storesToActivate.map((store) => {\n const promise = store.activate(bool);\n return promise.then((valid) => {\n if (valid) return valid;\n throw valid;\n });\n });\n return Promise.all(promises)\n .then(() => true)\n .catch(() => false);\n }\n\n async function submit() {\n if (!onSubmit) return;\n submitting.set(true);\n try {\n const valid = await activate();\n if (!valid) return false;\n\n const currValue = getStoreValue(ownSpecable).value;\n await onSubmit(currValue, mainStore);\n return true;\n } finally {\n submitting.set(false);\n }\n }\n\n const mainStore = {\n id,\n isRequired,\n spec,\n stores: childrenStores,\n\n activate,\n\n add(coll) {\n if (coll !== undefined) isUndef = false;\n addChildren(coll);\n return this;\n },\n\n getChild(path = []) {\n const reduced = path.reduce(\n (acc, key) => {\n const { children } = acc;\n if (!children) return { res: null };\n const childStore = get(key, children);\n if (!childStore) return { res: null };\n return {\n res: childStore,\n children: childStore.getChildren ? childStore.getChildren() : [],\n };\n },\n { children: childrenStores }\n );\n return reduced.res;\n },\n\n getChildren() {\n return childrenStores;\n },\n\n remove(idsToRemove = []) {\n removeChildrenById(idsToRemove);\n return this;\n },\n\n reset(newInitialValue = initialValue) {\n setValue(newInitialValue, { reset: true });\n activate(false);\n return this;\n },\n\n set(coll, partial = false, shouldActivate = false) {\n setValue(coll, { partial });\n if (shouldActivate) activate();\n return this;\n },\n\n update: (fn) => {\n setChildrenStores(fn(childrenStores));\n return this;\n },\n\n children: {\n subscribe: children.subscribe,\n },\n\n submit,\n\n subscribe: (fn) => {\n const unsub1 = derivedValue.subscribe((value) => ownSpecable.set(value));\n const unsub2 = status.subscribe(fn);\n return () => {\n unsub1();\n unsub2();\n };\n },\n };\n\n return mainStore;\n}\n\n/**\n * Combine two child status objects into an aggregate status.\n *\n * Returns an object with:\n * - active: true/false/null depending on consistency between children\n * - changed: boolean flag if any child changed\n * - valid: boolean|null (null while validating)\n * - validating: boolean\n */\nfunction combineChildren(a, b) {\n const validating = a.validating || b.validating;\n return {\n active: a.active === b.active ? b.active : null,\n changed: a.changed || b.changed,\n valid: validating ? null : a.valid && b.valid,\n validating,\n };\n}\n\n/**\n * Create an error-lifting wrapper that prefixes a child's path with the\n * parent's id (when present) and normalizes error shape.\n *\n * Returns a function that accepts an error-like object { path, error, ... }\n * and returns a normalized object with:\n * - path: array\n * - which: dotted path string\n * - error: the original error payload\n */\nconst liftError = (parentId) => ({ path, error, ...rest }) => {\n const newPath = parentId === undefined ? path : [parentId, ...path];\n return {\n ...rest,\n path: newPath,\n which: newPath.join(\".\"),\n error,\n };\n};\n\n/**\n * Recursively convert a details tree (status.details) to a flat list of\n * error objects with lifted paths. Handles leaf nodes (no details) and\n * nested nodes that themselves have .details.\n *\n * - details: an object mapping keys to status objects\n * - parentId: optional id to prefix to each child's path\n *\n * Returns an array of normalized error objects.\n */\nfunction detailsToErrors(details, parentId) {\n return Object.entries(details)\n .flatMap(([key, status]) => {\n if (!status.details) {\n if (!status.error) return [];\n if (key === \"_\") {\n return [{ path: [], error: status.error, isColl: true }];\n }\n return [summarizeStatusError(status)];\n }\n\n const subErrors = detailsToErrors(status.details, details.id);\n return subErrors.map(liftError(status.id));\n })\n .map(liftError(parentId));\n}\n\n/**\n * Summarize a status object that contains an error into a single error\n * descriptor with a one-segment path.\n *\n * Input: { id, error } -> Output: { path: [id], which: id, error }\n */\nfunction summarizeStatusError({ id, error }) {\n return { path: [id], which: id, error };\n}\n","import { identity } from \"./util\";\n\n/* Keep the second parameter available to custom transforms without making the\n * default representation depend on the element's current editing state. */\nfunction defaultToInput(value /* , currentInput */) {\n return value ?? \"\";\n}\n\n/**\n * Register an HTML input element to a `predSpecable` store.\n *\n * Usage in Svelte: <input use:register=\"{store}\" />\n * Optionally: <input use:register=\"{[store, { toInput, toValue }]}\" />\n *\n * `toInput` receives the store value and, after the initial synchronization,\n * the element's current input. A transform may return that current input to\n * preserve an equivalent intermediate representation while it is being edited.\n * The current input is omitted when subscribing to a new store so its canonical\n * representation replaces any input left over from the previous store.\n *\n * The returned object matches Svelte action contract { update, destroy }.\n *\n * @param {HTMLElement} el - the input element\n * @param {import('./predSpecable').default|Array} storeOrArgs - store or [store, {toInput,toValue}]\n * @returns {{ destroy: () => void, update: (newArgs:any) => void }|undefined}\n */\nexport default function register(el, storeOrArgs) {\n let args = normalizeArgs(storeOrArgs);\n if (!el || !args.store) return;\n\n let unsub;\n listen();\n\n function blurHandler() {\n args.store.activate();\n }\n\n function inputHandler(e) {\n args.store.set(args.toValue(e.target.value));\n }\n\n function listen() {\n // A subscription emits synchronously. Do not expose the element's existing\n // input on that first emission: it may belong to a previously bound store.\n let initialized = false;\n\n unsub = args.store.subscribe(({ value }) => {\n const currInput = el.value;\n const elValue = initialized\n ? args.toInput(value, currInput)\n : args.toInput(value);\n\n initialized = true;\n\n if (currInput !== elValue) el.value = elValue;\n });\n el.addEventListener(\"blur\", blurHandler);\n el.addEventListener(\"input\", inputHandler);\n }\n\n function unlisten() {\n unsub();\n el.removeEventListener(\"blur\", blurHandler);\n el.removeEventListener(\"input\", inputHandler);\n }\n\n return {\n destroy: unlisten,\n update(newArgs) {\n unlisten();\n if (!newArgs) return;\n args = normalizeArgs(newArgs);\n listen();\n },\n };\n}\n\nfunction normalizeArgs(storeOrArgs) {\n if (!storeOrArgs) return {};\n\n if (!Array.isArray(storeOrArgs)) {\n return {\n store: storeOrArgs,\n toInput: defaultToInput,\n toValue: identity,\n };\n }\n\n const [\n store,\n { toInput = defaultToInput, toValue = identity } = {},\n ] = storeOrArgs;\n return { store, toInput, toValue };\n}\n","import collSpecable from \"./collSpecable\";\nimport predSpecable from \"./predSpecable\";\nimport { isColl, isStore } from \"./util\";\n\n/**\n * specable\n *\n * Convenience factory that returns a spec-aware Svelte store for the given\n * initial value. It chooses between a collection-aware store (collSpecable)\n * and a predicate/value store (predSpecable) based on the inputs.\n *\n * Behaviour:\n * - If `initialValue` is already a Svelte store, it is returned unchanged.\n * - If `options.fields` or `options.spec` (or the `initialValue` itself)\n * look like a collection, `collSpecable` is used.\n * - Otherwise `predSpecable` is used for single-value (primitive/non-collection)\n * validation.\n *\n * Parameters:\n * - initialValue: any initial value for the store (can be undefined)\n * - options: configuration object forwarded to the chosen factory\n * - _extra: internal helpers (used for recursion when collSpecable needs to\n * create nested specable stores)\n *\n * Returns:\n * - a Svelte store implementing the specable API (either collSpecable or predSpecable)\n *\n * Example:\n * import { specable } from \"svelte-specma\";\n * const s1 = specable({ a: 1 }, { spec: { a: v => !v && \"err\" } });\n * const s2 = specable(42, { spec: v => v === 42 || \"must be 42\" });\n */\nexport default function specable(initialValue, options = {}, _extra) {\n if (isStore(initialValue)) return initialValue;\n\n const collCandidate = options.fields || options.spec || initialValue;\n\n if (isColl(collCandidate)) {\n return collSpecable(initialValue, options, { ..._extra, specable });\n }\n\n return predSpecable(initialValue, options, _extra);\n}\n"],"names":["identity","x","noop","flexDerived","initialStores","fn","initialValue","_stores","$stores","writable","auto","length","mainStore","readable","publish","initialized","cleanup","pending","unsubs","Map","values","sync","vals","reduce","acc","store","has","concat","get","result","unsubscribe","subscribe","stores","usedStores","forEach","unsub","includes","map","updateUnsubs","updateValues","i","set","value","exclude","_arguments","arguments","update","prev","filter","slice","call","include","_arguments2","isColl","typeOf","obj","toString","split","toLowerCase","entries","coll","array","v","Array","from","object","Object","fromEntries","entriesArr","toType","_ref","keys","merge","colls","type","every","collTypes","join","TypeError","assign","apply","Error","key","undefined","getPath","path","parent","equals","a","b","eqBy","defaultEqBy","_map","_a","_b","fastEquals","Date","valueOf","removeUndefined","_ref2","val","deriveState","storesEntries","collType","collDerived","initialColl","state","index$stores","idx","_set","newColl","ALWAYS_VALID","valid","REQUIRED_SPECMA_FNS","CONFIG_ERROR_MSG","specma","ensureConfigured","configure","specmaFns","alwaysTrue","defaultChangePred","reqSpec","isMissing","getMessage","predSpecable","_temp","_extra","activate","bool","active","Promise","resolve","then","currPromise","res","e","reject","_ref$changePred","changePred","id","required","spec","onSubmit","and","validatePred","getAncestor","pred","getPred","isRequired","ownSpec","contextStores","context","_initialValue","submitting","_value","newValue","writableByValue","derived","$active","$value","$context","$submitting","enhanceResult","relPath","str","ancestor","match","pathSinceAncestor","node","startsWith","index","parseInt","isNaN","keepForwardPath","$ancestor","ancestorValue","curr","next","getStoreValue","addContext","baseArgs","promise","interpretState","resolvedResult","_extends","reset","this","shouldActivate","submit","body","finalizer","currValue","bind","_finallyRethrows","_wasThrown","_result","promised","_ref3","changed","error","reason","validating","collSpecable","_this","fields","getId","getSpread","isOpt","collValue","isUndef","collDefiner","find","spreadGetId","spreadSpec","spreadFields","spreadRequired","isSpread","valueKeys","allKeys","Set","ownGetId","ownSpecable","createChildEntry","k","subChangePred","subVal","subSpec","subGetId","subId","Math","random","toFixed","subFields","subRequired","subStore","specable","n","childrenStores","children","derivedValue","$childrenStores","childrenValue","aggregateStatusStores","status","$statusStores","$ownSpecable","$children","combined","combineChildren","details","child","errors","detailsToErrors","collErrors","setChildrenStores","newChildrenStores","addChildren","newEntries","removeChildrenById","idsToRemove","_ref4","setValue","_temp2","_ref5","_ref5$partial","partial","_ref5$reset","setMethod","childrenEntries","_ref6","childrenKeys","missingChildrenEntries","_ref7","collKeys","_ref8","childStore","promises","all","add","getChild","reduced","getChildren","remove","newInitialValue","unsub1","unsub2","liftError","parentId","_ref9","rest","_objectWithoutPropertiesLoose","newPath","which","flatMap","_ref0","_ref1","defaultToInput","register","el","storeOrArgs","args","normalizeArgs","listen","destroy","unlisten","newArgs","blurHandler","inputHandler","toValue","target","currInput","elValue","toInput","addEventListener","removeEventListener","isArray","_storeOrArgs$","_storeOrArgs$$toInput","_storeOrArgs$$toValue","options","isFunc"],"mappings":"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