@zerolith/state 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -11,4 +11,6 @@ export type { History, HistoryOptions } from "./history.js";
11
11
  export { history } from "./history.js";
12
12
  export { debounced, throttled } from "./timing.js";
13
13
  export { selector } from "./selector.js";
14
+ export type { Next, SetStore, Snapshot, Store } from "./store.js";
15
+ export { Reconciled, reconcile, snapshot, store } from "./store.js";
14
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -2,4 +2,5 @@ export { stored } from "./stored.js";
2
2
  export { history } from "./history.js";
3
3
  export { debounced, throttled } from "./timing.js";
4
4
  export { selector } from "./selector.js";
5
+ export { Reconciled, reconcile, snapshot, store } from "./store.js";
5
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
@@ -1,2 +1,2 @@
1
- export{stored}from"./stored.js";export{history}from"./history.js";export{debounced,throttled}from"./timing.js";export{selector}from"./selector.js";
1
+ export{stored}from"./stored.js";export{history}from"./history.js";export{debounced,throttled}from"./timing.js";export{selector}from"./selector.js";export{Reconciled,reconcile,snapshot,store}from"./store.js";
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["/**\n * @zerolith/state: signals with a memory, over the core alone. A signal\n * kept in storage and in step across tabs, undo and redo for a signal,\n * signals that follow another after a quiet spell or at most so often, and\n * a selector that tells each row whether it is the selected one. The public\n * surface is assembled here.\n */\nexport type { StoredOptions } from \"./stored.js\";\nexport { stored } from \"./stored.js\";\n\nexport type { History, HistoryOptions } from \"./history.js\";\nexport { history } from \"./history.js\";\n\nexport { debounced, throttled } from \"./timing.js\";\n\nexport { selector } from \"./selector.js\";\n"],"names":[],"mappings":"AAQA,OAAS,WAAc,cAGvB,OAAS,YAAe,eAExB,OAAS,UAAW,cAAiB,cAErC,OAAS,aAAgB"}
1
+ {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["/**\n * @zerolith/state: signals with a memory, over the core alone. A signal\n * kept in storage and in step across tabs, undo and redo for a signal,\n * signals that follow another after a quiet spell or at most so often, and\n * a selector that tells each row whether it is the selected one. The public\n * surface is assembled here.\n */\nexport type { StoredOptions } from \"./stored.js\";\nexport { stored } from \"./stored.js\";\n\nexport type { History, HistoryOptions } from \"./history.js\";\nexport { history } from \"./history.js\";\n\nexport { debounced, throttled } from \"./timing.js\";\n\nexport { selector } from \"./selector.js\";\n\nexport type { Next, SetStore, Snapshot, Store } from \"./store.js\";\nexport { Reconciled, reconcile, snapshot, store } from \"./store.js\";\n"],"names":[],"mappings":"AAQA,OAAS,WAAc,cAGvB,OAAS,YAAe,eAExB,OAAS,UAAW,cAAiB,cAErC,OAAS,aAAgB,gBAGzB,OAAS,WAAY,UAAW,SAAU,UAAa"}
@@ -0,0 +1,2 @@
1
+ import{ZerolithError as e,batch as t,diagnostic as n,isTracking as r,signal as i}from"zerolith";export class Reconciled{data;key;constructor(e,t){this.data=e,this.key=t}}const a=new WeakMap,o=new WeakMap,s=new WeakMap,c=Symbol(`keys`);function l(e){return e===`__proto__`||e===`constructor`||e===`prototype`}function u(e){if(typeof e!=`object`||!e||e===Object.prototype||Object.isFrozen(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null||Array.isArray(e)&&t===Array.prototype}function d(e){if(!u(e))return e;let t=a.get(e);return t===void 0&&(t=new Proxy(e,m),a.set(e,t),o.set(t,e)),t}function f(e,t,n){let r=s.get(e);r===void 0&&(r=new Map,s.set(e,r));let a=r.get(t);return a===void 0&&(a=i(n),r.set(t,a)),a}function p(){throw new e(n(`state_store_assigned`,`error`))}const m={get(e,t){let n=Reflect.get(e,t);return Array.isArray(e)||typeof t==`symbol`||!r()||!Object.hasOwn(e,t)&&t in e?d(n):d(f(e,t,n).get())},ownKeys(e){return!Array.isArray(e)&&r()&&f(e,c,void 0).get(),Reflect.ownKeys(e)},set:p,deleteProperty:p,defineProperty:p,setPrototypeOf:p};function h(e,t,n){if(t===`__proto__`||t===`constructor`||t===`prototype`)return;let r=!Object.hasOwn(e,t);e[t]=n;let i=s.get(e);i?.get(t)?.set(n),r&&i?.get(c)?.set({})}function g(e,t){if(t===`__proto__`||t===`constructor`||t===`prototype`)return;delete e[t];let n=s.get(e);n?.get(t)?.set(void 0),n?.get(c)?.set({})}function _(e,t=new Set){let n=o.get(e);if(n!==void 0)return n;if(!u(e)||a.has(e)||t.has(e))return e;t.add(e);let r=e;for(let e of Object.keys(r)){if(l(e))continue;let n=r[e],i=_(n,t);i!==n&&(r[e]=i)}return e}function v(e,t,n){if(!u(e)||!u(t)||Array.isArray(e)!==Array.isArray(t))return t;if(Array.isArray(t)){let r=e,i=n===void 0?void 0:new Map(r.filter(u).map(e=>[e[n],e])),a=t.map((e,t)=>{let a=i===void 0?r[t]:u(e)?i.get(e[n]):void 0;return a===void 0?e:v(a,e,n)});return a.length===r.length&&a.every((e,t)=>e===r[t])?r:a}let r=e,i=t;for(let e of Object.keys(i)){if(l(e))continue;let t=v(r[e],i[e],n);Object.is(t,r[e])&&Object.hasOwn(r,e)||h(r,e,t)}for(let e of Object.keys(r))Object.hasOwn(i,e)||g(r,e);return e}function y(e,t){return e instanceof Reconciled?v(t,_(e.data),e.key):_(typeof e==`function`?e(t):e)}function b(t,r,i,a){let o=r[i];if(l(o))throw TypeError(`a store path cannot go through ${String(o)}, which names a prototype rather than a field`);let s=t[o],c;if(i===r.length-1)c=y(a,s);else{if(!u(s))throw new e(n(`state_store_path_missing`,`error`,{key:String(o)}));c=b(s,r,i+1,a)}if(Object.is(c,s)&&(Array.isArray(t)||Object.hasOwn(t,o)))return t;if(Array.isArray(t)){let e=t.slice();return e[o]=c,e}return h(t,o,c),t}export function store(e){if(!u(e)||Array.isArray(e))throw TypeError(`store() takes a plain object, such as { rows: [] }`);return[d(_(e)),(...n)=>{let r=n.pop();if(n.length===0)throw TypeError(`a store's setter takes a path of at least one key before the value, such as setBoard("rows", [])`);t(()=>{b(e,n,0,r)})}]}export function reconcile(e,t){return new Reconciled(e,t)}export function snapshot(e){return x(o.get(e)??e,new Map)}function x(e,t){if(!u(e))return e;let n=t.get(e);if(n!==void 0)return n;if(Array.isArray(e)){let n=[];t.set(e,n);for(let r of e)n.push(x(r,t));return n}let r={};t.set(e,r);for(let[n,i]of Object.entries(e))l(n)||(r[n]=x(i,t));return r}
2
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sources":["../../src/store.ts"],"sourcesContent":["/**\n * A store: nested plain objects and arrays read through proxies whose\n * fields are signals, each made the first time a binding, an effect, or a\n * computed value reads it, and changed only through the store's setter.\n */\nimport { type Signal, ZerolithError, batch, diagnostic, isTracking, signal } from \"zerolith\";\n\n/** A store as its readers see it: every field read-only, nested objects and arrays included. */\nexport type Store<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | Date | RegExp | Promise<unknown>\n ? T\n : T extends object\n ? { readonly [K in keyof T]: Store<T[K]> }\n : T;\n\n/** The plain, writable type of a store's read-only view, as `snapshot` returns it. */\nexport type Snapshot<S> = S extends (...args: never[]) => unknown\n ? S\n : S extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | Date | RegExp | Promise<unknown>\n ? S\n : S extends object\n ? { -readonly [K in keyof S]: Snapshot<S[K]> }\n : S;\n\n/** The keys a path may take at `T`: an index of an array, a property name of an object. */\ntype Key<T> = NonNullable<T> extends readonly unknown[] ? number : keyof NonNullable<T> & (string | number);\n\n/** The value a path reaches at key `K` of `T`. */\ntype At<T, K> = NonNullable<T> extends readonly (infer E)[] ? E : K extends keyof NonNullable<T> ? NonNullable<T>[K] : never;\n\n/** What the last key of a path takes: a value, a function of the value there now that returns the next, or `reconcile(data)`. */\nexport type Next<V> = V | ((previous: V) => V) | Reconciled<V>;\n\n/** Sets the value at a path of keys into the store, waking only what reads the fields that changed. */\nexport type SetStore<T> = {\n <A extends Key<T>>(a: A, next: Next<At<T, A>>): void;\n <A extends Key<T>, B extends Key<At<T, A>>>(a: A, b: B, next: Next<At<At<T, A>, B>>): void;\n <A extends Key<T>, B extends Key<At<T, A>>, C extends Key<At<At<T, A>, B>>>(a: A, b: B, c: C, next: Next<At<At<At<T, A>, B>, C>>): void;\n <A extends Key<T>, B extends Key<At<T, A>>, C extends Key<At<At<T, A>, B>>, D extends Key<At<At<At<T, A>, B>, C>>>(a: A, b: B, c: C, d: D, next: Next<At<At<At<At<T, A>, B>, C>, D>>): void;\n};\n\n/** Data to merge into a store by `reconcile`, which the setter takes in place of a value. */\nexport class Reconciled<T>\n{\n /** The data to merge. */\n readonly data: T;\n /** The field that matches an item of an array to the item it replaces, or undefined to match by index. */\n readonly key: string | undefined;\n\n /** Holds `data` and `key` for the setter; `reconcile(data, key)` makes one. */\n constructor(data: T, key: string | undefined)\n {\n this.data = data;\n this.key = key;\n }\n}\n\nconst proxies: WeakMap<object, object> = new WeakMap();\nconst raws: WeakMap<object, object> = new WeakMap();\nconst fields: WeakMap<object, Map<PropertyKey, Signal<unknown>>> = new WeakMap();\nconst KEYS: unique symbol = Symbol(\"keys\");\n\n/** Whether a key names an object's prototype or its constructor, which a store never reads through, writes, or copies, so data cannot reach `Object.prototype`. */\nfunction unsafe(key: PropertyKey): boolean\n{\n return key === \"__proto__\" || key === \"constructor\" || key === \"prototype\";\n}\n\n/** Whether a value is held in the store as fields: a plain object or array that is not frozen. Maps, Sets, class instances, frozen objects, and `Object.prototype` itself are values. */\nfunction plain(value: unknown): value is object\n{\n if (typeof value !== \"object\" || value === null || value === Object.prototype || Object.isFrozen(value))\n {\n return false;\n }\n const prototype: unknown = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null || (Array.isArray(value) && prototype === Array.prototype);\n}\n\n/** The read-only view of a value the store holds: its proxy, made once, for a plain object or array. */\nfunction wrap(value: unknown): unknown\n{\n if (!plain(value))\n {\n return value;\n }\n let proxy = proxies.get(value);\n if (proxy === undefined)\n {\n proxy = new Proxy(value, handler);\n proxies.set(value, proxy);\n raws.set(proxy, value);\n }\n return proxy;\n}\n\n/** The signal of a field of `raw`, made the first time something reads it while tracking. */\nfunction field(raw: object, key: PropertyKey, value: unknown): Signal<unknown>\n{\n let map = fields.get(raw);\n if (map === undefined)\n {\n map = new Map();\n fields.set(raw, map);\n }\n let found = map.get(key);\n if (found === undefined)\n {\n found = signal(value);\n map.set(key, found);\n }\n return found;\n}\n\nfunction refuse(): never\n{\n throw new ZerolithError(diagnostic(\"state_store_assigned\", \"error\"));\n}\n\nconst handler: ProxyHandler<object> = {\n get(raw, key)\n {\n const value: unknown = Reflect.get(raw, key);\n if (Array.isArray(raw) || typeof key === \"symbol\" || !isTracking() || (!Object.hasOwn(raw, key) && key in raw))\n {\n return wrap(value);\n }\n return wrap(field(raw, key, value).get());\n },\n ownKeys(raw)\n {\n if (!Array.isArray(raw) && isTracking())\n {\n field(raw, KEYS, undefined).get();\n }\n return Reflect.ownKeys(raw);\n },\n set: refuse,\n deleteProperty: refuse,\n defineProperty: refuse,\n setPrototypeOf: refuse\n};\n\n/** Writes `value` to the field `key` of the object `raw` and wakes what reads it, and what reads its keys when the field is new. */\nfunction assign(raw: object, key: PropertyKey, value: unknown): void\n{\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\")\n {\n return;\n }\n const added = !Object.hasOwn(raw, key);\n (raw as Record<PropertyKey, unknown>)[key] = value;\n const map = fields.get(raw);\n map?.get(key)?.set(value);\n if (added)\n {\n map?.get(KEYS)?.set({});\n }\n}\n\n/** Removes the field `key` of the object `raw` and wakes what reads it and its keys. */\nfunction remove(raw: object, key: PropertyKey): void\n{\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\")\n {\n return;\n }\n delete (raw as Record<PropertyKey, unknown>)[key];\n const map = fields.get(raw);\n map?.get(key)?.set(undefined);\n map?.get(KEYS)?.set({});\n}\n\n/** `value` with every store view in it, at any depth, replaced by the data it shows, so the store holds its data and never a view of it. */\nfunction unwrap(value: unknown, seen: Set<object> = new Set()): unknown\n{\n const raw = raws.get(value as object);\n if (raw !== undefined)\n {\n return raw;\n }\n if (!plain(value) || proxies.has(value) || seen.has(value))\n {\n return value;\n }\n seen.add(value);\n const record = value as Record<string, unknown>;\n for (const name of Object.keys(record))\n {\n if (unsafe(name))\n {\n continue;\n }\n const inner = record[name];\n const unwrapped = unwrap(inner, seen);\n if (unwrapped !== inner)\n {\n record[name] = unwrapped;\n }\n }\n return value;\n}\n\n/** `next` merged into `current`: objects keep their identity and change field by field, arrays keep the items `key` matches, or each index without one. */\nfunction merge(current: unknown, next: unknown, key: string | undefined): unknown\n{\n if (!plain(current) || !plain(next) || Array.isArray(current) !== Array.isArray(next))\n {\n return next;\n }\n if (Array.isArray(next))\n {\n const previous = current as unknown[];\n const matched = key === undefined ? undefined : new Map(previous.filter(plain).map((item) => [(item as Record<string, unknown>)[key], item]));\n const merged = next.map((item, index) =>\n {\n const match = matched === undefined ? previous[index] : plain(item) ? matched.get((item as Record<string, unknown>)[key as string]) : undefined;\n return match === undefined ? item : merge(match, item, key);\n });\n return merged.length === previous.length && merged.every((item, index) => item === previous[index]) ? previous : merged;\n }\n const target = current as Record<string, unknown>;\n const source = next as Record<string, unknown>;\n for (const name of Object.keys(source))\n {\n if (unsafe(name))\n {\n continue;\n }\n const value = merge(target[name], source[name], key);\n if (!Object.is(value, target[name]) || !Object.hasOwn(target, name))\n {\n assign(target, name, value);\n }\n }\n for (const name of Object.keys(target))\n {\n if (!Object.hasOwn(source, name))\n {\n remove(target, name);\n }\n }\n return current;\n}\n\n/** What the setter writes for `next` where the path holds `current`. */\nfunction resolve(next: unknown, current: unknown): unknown\n{\n if (next instanceof Reconciled)\n {\n return merge(current, unwrap(next.data), next.key);\n }\n return unwrap(typeof next === \"function\" ? (next as (previous: unknown) => unknown)(current) : next);\n}\n\n/**\n * Writes `next` at `path` below `raw`, from the key at `index` on, and\n * returns `raw`, or a copy of it when `raw` is an array whose item\n * changed: an object changes in place, field by field, and an array is\n * replaced, so whatever reads the list sees a new one.\n */\nfunction write(raw: object, path: readonly PropertyKey[], index: number, next: unknown): object\n{\n const key = path[index] as PropertyKey;\n if (unsafe(key))\n {\n throw new TypeError(`a store path cannot go through ${String(key)}, which names a prototype rather than a field`);\n }\n const current = (raw as Record<PropertyKey, unknown>)[key];\n let value: unknown;\n if (index === path.length - 1)\n {\n value = resolve(next, current);\n }\n else\n {\n if (!plain(current))\n {\n throw new ZerolithError(diagnostic(\"state_store_path_missing\", \"error\", { key: String(key) }));\n }\n value = write(current, path, index + 1, next);\n }\n if (Object.is(value, current) && (Array.isArray(raw) || Object.hasOwn(raw, key)))\n {\n return raw;\n }\n if (Array.isArray(raw))\n {\n const replaced = raw.slice();\n replaced[key as number] = value;\n return replaced;\n }\n assign(raw, key, value);\n return raw;\n}\n\n/**\n * Creates a store of `initial`, a plain object, and returns it read-only\n * with its setter: `const [board, setBoard] = store({ rows: [] })`. A\n * field is a signal from the first time a binding, an effect, or a\n * computed value reads it, so it wakes only what reads it; a read\n * anywhere else is a property lookup. `setBoard(\"rows\", 3, \"label\",\n * \"renamed\")` changes one field; a function as the last argument gets the\n * value there now and returns the next; `reconcile(data, \"id\")` merges\n * data sent again whole. Writing an item of an array replaces the array,\n * so a list over it, as `each(computed(() => board.rows), ...)`, sees the\n * change; writing a field of an object changes only that field. The store\n * keeps `initial` and changes it in place. Assigning to the store throws\n * `state_store_assigned`, and a path through a value that is not an\n * object or array throws `state_store_path_missing`.\n */\nexport function store<T extends object>(initial: T): [Store<T>, SetStore<T>]\n{\n if (!plain(initial) || Array.isArray(initial))\n {\n throw new TypeError(\"store() takes a plain object, such as { rows: [] }\");\n }\n const view = wrap(unwrap(initial)) as Store<T>;\n const set = (...args: unknown[]): void =>\n {\n const next = args.pop();\n if (args.length === 0)\n {\n throw new TypeError(\"a store's setter takes a path of at least one key before the value, such as setBoard(\\\"rows\\\", [])\");\n }\n batch(() =>\n {\n write(initial, args as PropertyKey[], 0, next);\n });\n };\n return [view, set as SetStore<T>];\n}\n\n/** Data to merge into a store with the setter, as `setBoard(\"rows\", reconcile(rows, \"id\"))`: the items `key` matches keep their identity and change only in the fields that differ, so only what reads those fields wakes. Without `key`, items match by index. */\nexport function reconcile<T>(data: T, key?: string): Reconciled<T>\n{\n return new Reconciled(data, key);\n}\n\n/** A plain copy of a store or of any part of it, for code that wants ordinary objects. */\nexport function snapshot<S>(value: S): Snapshot<S>\n{\n return copy(raws.get(value as object) ?? value, new Map()) as Snapshot<S>;\n}\n\nfunction copy(value: unknown, seen: Map<object, unknown>): unknown\n{\n if (!plain(value))\n {\n return value;\n }\n const known = seen.get(value);\n if (known !== undefined)\n {\n return known;\n }\n if (Array.isArray(value))\n {\n const list: unknown[] = [];\n seen.set(value, list);\n for (const item of value)\n {\n list.push(copy(item, seen));\n }\n return list;\n }\n const record: Record<string, unknown> = {};\n seen.set(value, record);\n for (const [name, inner] of Object.entries(value))\n {\n if (!unsafe(name))\n {\n record[name] = copy(inner, seen);\n }\n }\n return record;\n}\n"],"names":["data","key","proxies","raws","fields","KEYS","unsafe","plain","value","prototype","wrap","proxy","handler","field","map","raw","found","signal","refuse","ZerolithError","diagnostic","isTracking","assign","added","remove","unwrap","seen","record","name","inner","unwrapped","merge","current","next","previous","matched","item","merged","match","index","target","source","resolve","write","path","replaced","initial","args","batch","copy","known","list"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /** A store as its readers see it: every field read-only, nested objects and arrays included. */
2
+ export type Store<T> = T extends (...args: never[]) => unknown ? T : T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | Date | RegExp | Promise<unknown> ? T : T extends object ? {
3
+ readonly [K in keyof T]: Store<T[K]>;
4
+ } : T;
5
+ /** The plain, writable type of a store's read-only view, as `snapshot` returns it. */
6
+ export type Snapshot<S> = S extends (...args: never[]) => unknown ? S : S extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | Date | RegExp | Promise<unknown> ? S : S extends object ? {
7
+ -readonly [K in keyof S]: Snapshot<S[K]>;
8
+ } : S;
9
+ /** The keys a path may take at `T`: an index of an array, a property name of an object. */
10
+ type Key<T> = NonNullable<T> extends readonly unknown[] ? number : keyof NonNullable<T> & (string | number);
11
+ /** The value a path reaches at key `K` of `T`. */
12
+ type At<T, K> = NonNullable<T> extends readonly (infer E)[] ? E : K extends keyof NonNullable<T> ? NonNullable<T>[K] : never;
13
+ /** What the last key of a path takes: a value, a function of the value there now that returns the next, or `reconcile(data)`. */
14
+ export type Next<V> = V | ((previous: V) => V) | Reconciled<V>;
15
+ /** Sets the value at a path of keys into the store, waking only what reads the fields that changed. */
16
+ export type SetStore<T> = {
17
+ <A extends Key<T>>(a: A, next: Next<At<T, A>>): void;
18
+ <A extends Key<T>, B extends Key<At<T, A>>>(a: A, b: B, next: Next<At<At<T, A>, B>>): void;
19
+ <A extends Key<T>, B extends Key<At<T, A>>, C extends Key<At<At<T, A>, B>>>(a: A, b: B, c: C, next: Next<At<At<At<T, A>, B>, C>>): void;
20
+ <A extends Key<T>, B extends Key<At<T, A>>, C extends Key<At<At<T, A>, B>>, D extends Key<At<At<At<T, A>, B>, C>>>(a: A, b: B, c: C, d: D, next: Next<At<At<At<At<T, A>, B>, C>, D>>): void;
21
+ };
22
+ /** Data to merge into a store by `reconcile`, which the setter takes in place of a value. */
23
+ export declare class Reconciled<T> {
24
+ /** The data to merge. */
25
+ readonly data: T;
26
+ /** The field that matches an item of an array to the item it replaces, or undefined to match by index. */
27
+ readonly key: string | undefined;
28
+ /** Holds `data` and `key` for the setter; `reconcile(data, key)` makes one. */
29
+ constructor(data: T, key: string | undefined);
30
+ }
31
+ /**
32
+ * Creates a store of `initial`, a plain object, and returns it read-only
33
+ * with its setter: `const [board, setBoard] = store({ rows: [] })`. A
34
+ * field is a signal from the first time a binding, an effect, or a
35
+ * computed value reads it, so it wakes only what reads it; a read
36
+ * anywhere else is a property lookup. `setBoard("rows", 3, "label",
37
+ * "renamed")` changes one field; a function as the last argument gets the
38
+ * value there now and returns the next; `reconcile(data, "id")` merges
39
+ * data sent again whole. Writing an item of an array replaces the array,
40
+ * so a list over it, as `each(computed(() => board.rows), ...)`, sees the
41
+ * change; writing a field of an object changes only that field. The store
42
+ * keeps `initial` and changes it in place. Assigning to the store throws
43
+ * `state_store_assigned`, and a path through a value that is not an
44
+ * object or array throws `state_store_path_missing`.
45
+ */
46
+ export declare function store<T extends object>(initial: T): [Store<T>, SetStore<T>];
47
+ /** Data to merge into a store with the setter, as `setBoard("rows", reconcile(rows, "id"))`: the items `key` matches keep their identity and change only in the fields that differ, so only what reads those fields wakes. Without `key`, items match by index. */
48
+ export declare function reconcile<T>(data: T, key?: string): Reconciled<T>;
49
+ /** A plain copy of a store or of any part of it, for code that wants ordinary objects. */
50
+ export declare function snapshot<S>(value: S): Snapshot<S>;
51
+ export {};
52
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/store.js ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * A store: nested plain objects and arrays read through proxies whose
3
+ * fields are signals, each made the first time a binding, an effect, or a
4
+ * computed value reads it, and changed only through the store's setter.
5
+ */
6
+ import { ZerolithError, batch, diagnostic, isTracking, signal } from "zerolith";
7
+ /** Data to merge into a store by `reconcile`, which the setter takes in place of a value. */
8
+ export class Reconciled {
9
+ /** The data to merge. */
10
+ data;
11
+ /** The field that matches an item of an array to the item it replaces, or undefined to match by index. */
12
+ key;
13
+ /** Holds `data` and `key` for the setter; `reconcile(data, key)` makes one. */
14
+ constructor(data, key) {
15
+ this.data = data;
16
+ this.key = key;
17
+ }
18
+ }
19
+ const proxies = new WeakMap();
20
+ const raws = new WeakMap();
21
+ const fields = new WeakMap();
22
+ const KEYS = Symbol("keys");
23
+ /** Whether a key names an object's prototype or its constructor, which a store never reads through, writes, or copies, so data cannot reach `Object.prototype`. */
24
+ function unsafe(key) {
25
+ return key === "__proto__" || key === "constructor" || key === "prototype";
26
+ }
27
+ /** Whether a value is held in the store as fields: a plain object or array that is not frozen. Maps, Sets, class instances, frozen objects, and `Object.prototype` itself are values. */
28
+ function plain(value) {
29
+ if (typeof value !== "object" || value === null || value === Object.prototype || Object.isFrozen(value)) {
30
+ return false;
31
+ }
32
+ const prototype = Object.getPrototypeOf(value);
33
+ return prototype === Object.prototype || prototype === null || (Array.isArray(value) && prototype === Array.prototype);
34
+ }
35
+ /** The read-only view of a value the store holds: its proxy, made once, for a plain object or array. */
36
+ function wrap(value) {
37
+ if (!plain(value)) {
38
+ return value;
39
+ }
40
+ let proxy = proxies.get(value);
41
+ if (proxy === undefined) {
42
+ proxy = new Proxy(value, handler);
43
+ proxies.set(value, proxy);
44
+ raws.set(proxy, value);
45
+ }
46
+ return proxy;
47
+ }
48
+ /** The signal of a field of `raw`, made the first time something reads it while tracking. */
49
+ function field(raw, key, value) {
50
+ let map = fields.get(raw);
51
+ if (map === undefined) {
52
+ map = new Map();
53
+ fields.set(raw, map);
54
+ }
55
+ let found = map.get(key);
56
+ if (found === undefined) {
57
+ found = signal(value);
58
+ map.set(key, found);
59
+ }
60
+ return found;
61
+ }
62
+ function refuse() {
63
+ throw new ZerolithError(diagnostic("state_store_assigned", "error"));
64
+ }
65
+ const handler = {
66
+ get(raw, key) {
67
+ const value = Reflect.get(raw, key);
68
+ if (Array.isArray(raw) || typeof key === "symbol" || !isTracking() || (!Object.hasOwn(raw, key) && key in raw)) {
69
+ return wrap(value);
70
+ }
71
+ return wrap(field(raw, key, value).get());
72
+ },
73
+ ownKeys(raw) {
74
+ if (!Array.isArray(raw) && isTracking()) {
75
+ field(raw, KEYS, undefined).get();
76
+ }
77
+ return Reflect.ownKeys(raw);
78
+ },
79
+ set: refuse,
80
+ deleteProperty: refuse,
81
+ defineProperty: refuse,
82
+ setPrototypeOf: refuse
83
+ };
84
+ /** Writes `value` to the field `key` of the object `raw` and wakes what reads it, and what reads its keys when the field is new. */
85
+ function assign(raw, key, value) {
86
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
87
+ return;
88
+ }
89
+ const added = !Object.hasOwn(raw, key);
90
+ raw[key] = value;
91
+ const map = fields.get(raw);
92
+ map?.get(key)?.set(value);
93
+ if (added) {
94
+ map?.get(KEYS)?.set({});
95
+ }
96
+ }
97
+ /** Removes the field `key` of the object `raw` and wakes what reads it and its keys. */
98
+ function remove(raw, key) {
99
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
100
+ return;
101
+ }
102
+ delete raw[key];
103
+ const map = fields.get(raw);
104
+ map?.get(key)?.set(undefined);
105
+ map?.get(KEYS)?.set({});
106
+ }
107
+ /** `value` with every store view in it, at any depth, replaced by the data it shows, so the store holds its data and never a view of it. */
108
+ function unwrap(value, seen = new Set()) {
109
+ const raw = raws.get(value);
110
+ if (raw !== undefined) {
111
+ return raw;
112
+ }
113
+ if (!plain(value) || proxies.has(value) || seen.has(value)) {
114
+ return value;
115
+ }
116
+ seen.add(value);
117
+ const record = value;
118
+ for (const name of Object.keys(record)) {
119
+ if (unsafe(name)) {
120
+ continue;
121
+ }
122
+ const inner = record[name];
123
+ const unwrapped = unwrap(inner, seen);
124
+ if (unwrapped !== inner) {
125
+ record[name] = unwrapped;
126
+ }
127
+ }
128
+ return value;
129
+ }
130
+ /** `next` merged into `current`: objects keep their identity and change field by field, arrays keep the items `key` matches, or each index without one. */
131
+ function merge(current, next, key) {
132
+ if (!plain(current) || !plain(next) || Array.isArray(current) !== Array.isArray(next)) {
133
+ return next;
134
+ }
135
+ if (Array.isArray(next)) {
136
+ const previous = current;
137
+ const matched = key === undefined ? undefined : new Map(previous.filter(plain).map((item) => [item[key], item]));
138
+ const merged = next.map((item, index) => {
139
+ const match = matched === undefined ? previous[index] : plain(item) ? matched.get(item[key]) : undefined;
140
+ return match === undefined ? item : merge(match, item, key);
141
+ });
142
+ return merged.length === previous.length && merged.every((item, index) => item === previous[index]) ? previous : merged;
143
+ }
144
+ const target = current;
145
+ const source = next;
146
+ for (const name of Object.keys(source)) {
147
+ if (unsafe(name)) {
148
+ continue;
149
+ }
150
+ const value = merge(target[name], source[name], key);
151
+ if (!Object.is(value, target[name]) || !Object.hasOwn(target, name)) {
152
+ assign(target, name, value);
153
+ }
154
+ }
155
+ for (const name of Object.keys(target)) {
156
+ if (!Object.hasOwn(source, name)) {
157
+ remove(target, name);
158
+ }
159
+ }
160
+ return current;
161
+ }
162
+ /** What the setter writes for `next` where the path holds `current`. */
163
+ function resolve(next, current) {
164
+ if (next instanceof Reconciled) {
165
+ return merge(current, unwrap(next.data), next.key);
166
+ }
167
+ return unwrap(typeof next === "function" ? next(current) : next);
168
+ }
169
+ /**
170
+ * Writes `next` at `path` below `raw`, from the key at `index` on, and
171
+ * returns `raw`, or a copy of it when `raw` is an array whose item
172
+ * changed: an object changes in place, field by field, and an array is
173
+ * replaced, so whatever reads the list sees a new one.
174
+ */
175
+ function write(raw, path, index, next) {
176
+ const key = path[index];
177
+ if (unsafe(key)) {
178
+ throw new TypeError(`a store path cannot go through ${String(key)}, which names a prototype rather than a field`);
179
+ }
180
+ const current = raw[key];
181
+ let value;
182
+ if (index === path.length - 1) {
183
+ value = resolve(next, current);
184
+ }
185
+ else {
186
+ if (!plain(current)) {
187
+ throw new ZerolithError(diagnostic("state_store_path_missing", "error", { key: String(key) }));
188
+ }
189
+ value = write(current, path, index + 1, next);
190
+ }
191
+ if (Object.is(value, current) && (Array.isArray(raw) || Object.hasOwn(raw, key))) {
192
+ return raw;
193
+ }
194
+ if (Array.isArray(raw)) {
195
+ const replaced = raw.slice();
196
+ replaced[key] = value;
197
+ return replaced;
198
+ }
199
+ assign(raw, key, value);
200
+ return raw;
201
+ }
202
+ /**
203
+ * Creates a store of `initial`, a plain object, and returns it read-only
204
+ * with its setter: `const [board, setBoard] = store({ rows: [] })`. A
205
+ * field is a signal from the first time a binding, an effect, or a
206
+ * computed value reads it, so it wakes only what reads it; a read
207
+ * anywhere else is a property lookup. `setBoard("rows", 3, "label",
208
+ * "renamed")` changes one field; a function as the last argument gets the
209
+ * value there now and returns the next; `reconcile(data, "id")` merges
210
+ * data sent again whole. Writing an item of an array replaces the array,
211
+ * so a list over it, as `each(computed(() => board.rows), ...)`, sees the
212
+ * change; writing a field of an object changes only that field. The store
213
+ * keeps `initial` and changes it in place. Assigning to the store throws
214
+ * `state_store_assigned`, and a path through a value that is not an
215
+ * object or array throws `state_store_path_missing`.
216
+ */
217
+ export function store(initial) {
218
+ if (!plain(initial) || Array.isArray(initial)) {
219
+ throw new TypeError("store() takes a plain object, such as { rows: [] }");
220
+ }
221
+ const view = wrap(unwrap(initial));
222
+ const set = (...args) => {
223
+ const next = args.pop();
224
+ if (args.length === 0) {
225
+ throw new TypeError("a store's setter takes a path of at least one key before the value, such as setBoard(\"rows\", [])");
226
+ }
227
+ batch(() => {
228
+ write(initial, args, 0, next);
229
+ });
230
+ };
231
+ return [view, set];
232
+ }
233
+ /** Data to merge into a store with the setter, as `setBoard("rows", reconcile(rows, "id"))`: the items `key` matches keep their identity and change only in the fields that differ, so only what reads those fields wakes. Without `key`, items match by index. */
234
+ export function reconcile(data, key) {
235
+ return new Reconciled(data, key);
236
+ }
237
+ /** A plain copy of a store or of any part of it, for code that wants ordinary objects. */
238
+ export function snapshot(value) {
239
+ return copy(raws.get(value) ?? value, new Map());
240
+ }
241
+ function copy(value, seen) {
242
+ if (!plain(value)) {
243
+ return value;
244
+ }
245
+ const known = seen.get(value);
246
+ if (known !== undefined) {
247
+ return known;
248
+ }
249
+ if (Array.isArray(value)) {
250
+ const list = [];
251
+ seen.set(value, list);
252
+ for (const item of value) {
253
+ list.push(copy(item, seen));
254
+ }
255
+ return list;
256
+ }
257
+ const record = {};
258
+ seen.set(value, record);
259
+ for (const [name, inner] of Object.entries(value)) {
260
+ if (!unsafe(name)) {
261
+ record[name] = copy(inner, seen);
262
+ }
263
+ }
264
+ return record;
265
+ }
266
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerolith/state",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Signals with a memory for zerolith: a signal kept in storage and across tabs, undo and redo, debounced and throttled signals, and a row selector",
5
5
  "license": "Apache-2.0",
6
6
  "author": "molexxxx",
@@ -44,9 +44,9 @@
44
44
  "build": "tsc -p tsconfig.build.json && node ../../scripts/production.ts"
45
45
  },
46
46
  "peerDependencies": {
47
- "zerolith": "^1.9.0"
47
+ "zerolith": "^1.10.0"
48
48
  },
49
49
  "devDependencies": {
50
- "zerolith": "^1.9.0"
50
+ "zerolith": "^1.10.0"
51
51
  }
52
52
  }
package/src/index.ts CHANGED
@@ -14,3 +14,6 @@ export { history } from "./history.js";
14
14
  export { debounced, throttled } from "./timing.js";
15
15
 
16
16
  export { selector } from "./selector.js";
17
+
18
+ export type { Next, SetStore, Snapshot, Store } from "./store.js";
19
+ export { Reconciled, reconcile, snapshot, store } from "./store.js";
package/src/store.ts ADDED
@@ -0,0 +1,378 @@
1
+ /**
2
+ * A store: nested plain objects and arrays read through proxies whose
3
+ * fields are signals, each made the first time a binding, an effect, or a
4
+ * computed value reads it, and changed only through the store's setter.
5
+ */
6
+ import { type Signal, ZerolithError, batch, diagnostic, isTracking, signal } from "zerolith";
7
+
8
+ /** A store as its readers see it: every field read-only, nested objects and arrays included. */
9
+ export type Store<T> = T extends (...args: never[]) => unknown
10
+ ? T
11
+ : T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | Date | RegExp | Promise<unknown>
12
+ ? T
13
+ : T extends object
14
+ ? { readonly [K in keyof T]: Store<T[K]> }
15
+ : T;
16
+
17
+ /** The plain, writable type of a store's read-only view, as `snapshot` returns it. */
18
+ export type Snapshot<S> = S extends (...args: never[]) => unknown
19
+ ? S
20
+ : S extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | Date | RegExp | Promise<unknown>
21
+ ? S
22
+ : S extends object
23
+ ? { -readonly [K in keyof S]: Snapshot<S[K]> }
24
+ : S;
25
+
26
+ /** The keys a path may take at `T`: an index of an array, a property name of an object. */
27
+ type Key<T> = NonNullable<T> extends readonly unknown[] ? number : keyof NonNullable<T> & (string | number);
28
+
29
+ /** The value a path reaches at key `K` of `T`. */
30
+ type At<T, K> = NonNullable<T> extends readonly (infer E)[] ? E : K extends keyof NonNullable<T> ? NonNullable<T>[K] : never;
31
+
32
+ /** What the last key of a path takes: a value, a function of the value there now that returns the next, or `reconcile(data)`. */
33
+ export type Next<V> = V | ((previous: V) => V) | Reconciled<V>;
34
+
35
+ /** Sets the value at a path of keys into the store, waking only what reads the fields that changed. */
36
+ export type SetStore<T> = {
37
+ <A extends Key<T>>(a: A, next: Next<At<T, A>>): void;
38
+ <A extends Key<T>, B extends Key<At<T, A>>>(a: A, b: B, next: Next<At<At<T, A>, B>>): void;
39
+ <A extends Key<T>, B extends Key<At<T, A>>, C extends Key<At<At<T, A>, B>>>(a: A, b: B, c: C, next: Next<At<At<At<T, A>, B>, C>>): void;
40
+ <A extends Key<T>, B extends Key<At<T, A>>, C extends Key<At<At<T, A>, B>>, D extends Key<At<At<At<T, A>, B>, C>>>(a: A, b: B, c: C, d: D, next: Next<At<At<At<At<T, A>, B>, C>, D>>): void;
41
+ };
42
+
43
+ /** Data to merge into a store by `reconcile`, which the setter takes in place of a value. */
44
+ export class Reconciled<T>
45
+ {
46
+ /** The data to merge. */
47
+ readonly data: T;
48
+ /** The field that matches an item of an array to the item it replaces, or undefined to match by index. */
49
+ readonly key: string | undefined;
50
+
51
+ /** Holds `data` and `key` for the setter; `reconcile(data, key)` makes one. */
52
+ constructor(data: T, key: string | undefined)
53
+ {
54
+ this.data = data;
55
+ this.key = key;
56
+ }
57
+ }
58
+
59
+ const proxies: WeakMap<object, object> = new WeakMap();
60
+ const raws: WeakMap<object, object> = new WeakMap();
61
+ const fields: WeakMap<object, Map<PropertyKey, Signal<unknown>>> = new WeakMap();
62
+ const KEYS: unique symbol = Symbol("keys");
63
+
64
+ /** Whether a key names an object's prototype or its constructor, which a store never reads through, writes, or copies, so data cannot reach `Object.prototype`. */
65
+ function unsafe(key: PropertyKey): boolean
66
+ {
67
+ return key === "__proto__" || key === "constructor" || key === "prototype";
68
+ }
69
+
70
+ /** Whether a value is held in the store as fields: a plain object or array that is not frozen. Maps, Sets, class instances, frozen objects, and `Object.prototype` itself are values. */
71
+ function plain(value: unknown): value is object
72
+ {
73
+ if (typeof value !== "object" || value === null || value === Object.prototype || Object.isFrozen(value))
74
+ {
75
+ return false;
76
+ }
77
+ const prototype: unknown = Object.getPrototypeOf(value);
78
+ return prototype === Object.prototype || prototype === null || (Array.isArray(value) && prototype === Array.prototype);
79
+ }
80
+
81
+ /** The read-only view of a value the store holds: its proxy, made once, for a plain object or array. */
82
+ function wrap(value: unknown): unknown
83
+ {
84
+ if (!plain(value))
85
+ {
86
+ return value;
87
+ }
88
+ let proxy = proxies.get(value);
89
+ if (proxy === undefined)
90
+ {
91
+ proxy = new Proxy(value, handler);
92
+ proxies.set(value, proxy);
93
+ raws.set(proxy, value);
94
+ }
95
+ return proxy;
96
+ }
97
+
98
+ /** The signal of a field of `raw`, made the first time something reads it while tracking. */
99
+ function field(raw: object, key: PropertyKey, value: unknown): Signal<unknown>
100
+ {
101
+ let map = fields.get(raw);
102
+ if (map === undefined)
103
+ {
104
+ map = new Map();
105
+ fields.set(raw, map);
106
+ }
107
+ let found = map.get(key);
108
+ if (found === undefined)
109
+ {
110
+ found = signal(value);
111
+ map.set(key, found);
112
+ }
113
+ return found;
114
+ }
115
+
116
+ function refuse(): never
117
+ {
118
+ throw new ZerolithError(diagnostic("state_store_assigned", "error"));
119
+ }
120
+
121
+ const handler: ProxyHandler<object> = {
122
+ get(raw, key)
123
+ {
124
+ const value: unknown = Reflect.get(raw, key);
125
+ if (Array.isArray(raw) || typeof key === "symbol" || !isTracking() || (!Object.hasOwn(raw, key) && key in raw))
126
+ {
127
+ return wrap(value);
128
+ }
129
+ return wrap(field(raw, key, value).get());
130
+ },
131
+ ownKeys(raw)
132
+ {
133
+ if (!Array.isArray(raw) && isTracking())
134
+ {
135
+ field(raw, KEYS, undefined).get();
136
+ }
137
+ return Reflect.ownKeys(raw);
138
+ },
139
+ set: refuse,
140
+ deleteProperty: refuse,
141
+ defineProperty: refuse,
142
+ setPrototypeOf: refuse
143
+ };
144
+
145
+ /** Writes `value` to the field `key` of the object `raw` and wakes what reads it, and what reads its keys when the field is new. */
146
+ function assign(raw: object, key: PropertyKey, value: unknown): void
147
+ {
148
+ if (key === "__proto__" || key === "constructor" || key === "prototype")
149
+ {
150
+ return;
151
+ }
152
+ const added = !Object.hasOwn(raw, key);
153
+ (raw as Record<PropertyKey, unknown>)[key] = value;
154
+ const map = fields.get(raw);
155
+ map?.get(key)?.set(value);
156
+ if (added)
157
+ {
158
+ map?.get(KEYS)?.set({});
159
+ }
160
+ }
161
+
162
+ /** Removes the field `key` of the object `raw` and wakes what reads it and its keys. */
163
+ function remove(raw: object, key: PropertyKey): void
164
+ {
165
+ if (key === "__proto__" || key === "constructor" || key === "prototype")
166
+ {
167
+ return;
168
+ }
169
+ delete (raw as Record<PropertyKey, unknown>)[key];
170
+ const map = fields.get(raw);
171
+ map?.get(key)?.set(undefined);
172
+ map?.get(KEYS)?.set({});
173
+ }
174
+
175
+ /** `value` with every store view in it, at any depth, replaced by the data it shows, so the store holds its data and never a view of it. */
176
+ function unwrap(value: unknown, seen: Set<object> = new Set()): unknown
177
+ {
178
+ const raw = raws.get(value as object);
179
+ if (raw !== undefined)
180
+ {
181
+ return raw;
182
+ }
183
+ if (!plain(value) || proxies.has(value) || seen.has(value))
184
+ {
185
+ return value;
186
+ }
187
+ seen.add(value);
188
+ const record = value as Record<string, unknown>;
189
+ for (const name of Object.keys(record))
190
+ {
191
+ if (unsafe(name))
192
+ {
193
+ continue;
194
+ }
195
+ const inner = record[name];
196
+ const unwrapped = unwrap(inner, seen);
197
+ if (unwrapped !== inner)
198
+ {
199
+ record[name] = unwrapped;
200
+ }
201
+ }
202
+ return value;
203
+ }
204
+
205
+ /** `next` merged into `current`: objects keep their identity and change field by field, arrays keep the items `key` matches, or each index without one. */
206
+ function merge(current: unknown, next: unknown, key: string | undefined): unknown
207
+ {
208
+ if (!plain(current) || !plain(next) || Array.isArray(current) !== Array.isArray(next))
209
+ {
210
+ return next;
211
+ }
212
+ if (Array.isArray(next))
213
+ {
214
+ const previous = current as unknown[];
215
+ const matched = key === undefined ? undefined : new Map(previous.filter(plain).map((item) => [(item as Record<string, unknown>)[key], item]));
216
+ const merged = next.map((item, index) =>
217
+ {
218
+ const match = matched === undefined ? previous[index] : plain(item) ? matched.get((item as Record<string, unknown>)[key as string]) : undefined;
219
+ return match === undefined ? item : merge(match, item, key);
220
+ });
221
+ return merged.length === previous.length && merged.every((item, index) => item === previous[index]) ? previous : merged;
222
+ }
223
+ const target = current as Record<string, unknown>;
224
+ const source = next as Record<string, unknown>;
225
+ for (const name of Object.keys(source))
226
+ {
227
+ if (unsafe(name))
228
+ {
229
+ continue;
230
+ }
231
+ const value = merge(target[name], source[name], key);
232
+ if (!Object.is(value, target[name]) || !Object.hasOwn(target, name))
233
+ {
234
+ assign(target, name, value);
235
+ }
236
+ }
237
+ for (const name of Object.keys(target))
238
+ {
239
+ if (!Object.hasOwn(source, name))
240
+ {
241
+ remove(target, name);
242
+ }
243
+ }
244
+ return current;
245
+ }
246
+
247
+ /** What the setter writes for `next` where the path holds `current`. */
248
+ function resolve(next: unknown, current: unknown): unknown
249
+ {
250
+ if (next instanceof Reconciled)
251
+ {
252
+ return merge(current, unwrap(next.data), next.key);
253
+ }
254
+ return unwrap(typeof next === "function" ? (next as (previous: unknown) => unknown)(current) : next);
255
+ }
256
+
257
+ /**
258
+ * Writes `next` at `path` below `raw`, from the key at `index` on, and
259
+ * returns `raw`, or a copy of it when `raw` is an array whose item
260
+ * changed: an object changes in place, field by field, and an array is
261
+ * replaced, so whatever reads the list sees a new one.
262
+ */
263
+ function write(raw: object, path: readonly PropertyKey[], index: number, next: unknown): object
264
+ {
265
+ const key = path[index] as PropertyKey;
266
+ if (unsafe(key))
267
+ {
268
+ throw new TypeError(`a store path cannot go through ${String(key)}, which names a prototype rather than a field`);
269
+ }
270
+ const current = (raw as Record<PropertyKey, unknown>)[key];
271
+ let value: unknown;
272
+ if (index === path.length - 1)
273
+ {
274
+ value = resolve(next, current);
275
+ }
276
+ else
277
+ {
278
+ if (!plain(current))
279
+ {
280
+ throw new ZerolithError(diagnostic("state_store_path_missing", "error", { key: String(key) }));
281
+ }
282
+ value = write(current, path, index + 1, next);
283
+ }
284
+ if (Object.is(value, current) && (Array.isArray(raw) || Object.hasOwn(raw, key)))
285
+ {
286
+ return raw;
287
+ }
288
+ if (Array.isArray(raw))
289
+ {
290
+ const replaced = raw.slice();
291
+ replaced[key as number] = value;
292
+ return replaced;
293
+ }
294
+ assign(raw, key, value);
295
+ return raw;
296
+ }
297
+
298
+ /**
299
+ * Creates a store of `initial`, a plain object, and returns it read-only
300
+ * with its setter: `const [board, setBoard] = store({ rows: [] })`. A
301
+ * field is a signal from the first time a binding, an effect, or a
302
+ * computed value reads it, so it wakes only what reads it; a read
303
+ * anywhere else is a property lookup. `setBoard("rows", 3, "label",
304
+ * "renamed")` changes one field; a function as the last argument gets the
305
+ * value there now and returns the next; `reconcile(data, "id")` merges
306
+ * data sent again whole. Writing an item of an array replaces the array,
307
+ * so a list over it, as `each(computed(() => board.rows), ...)`, sees the
308
+ * change; writing a field of an object changes only that field. The store
309
+ * keeps `initial` and changes it in place. Assigning to the store throws
310
+ * `state_store_assigned`, and a path through a value that is not an
311
+ * object or array throws `state_store_path_missing`.
312
+ */
313
+ export function store<T extends object>(initial: T): [Store<T>, SetStore<T>]
314
+ {
315
+ if (!plain(initial) || Array.isArray(initial))
316
+ {
317
+ throw new TypeError("store() takes a plain object, such as { rows: [] }");
318
+ }
319
+ const view = wrap(unwrap(initial)) as Store<T>;
320
+ const set = (...args: unknown[]): void =>
321
+ {
322
+ const next = args.pop();
323
+ if (args.length === 0)
324
+ {
325
+ throw new TypeError("a store's setter takes a path of at least one key before the value, such as setBoard(\"rows\", [])");
326
+ }
327
+ batch(() =>
328
+ {
329
+ write(initial, args as PropertyKey[], 0, next);
330
+ });
331
+ };
332
+ return [view, set as SetStore<T>];
333
+ }
334
+
335
+ /** Data to merge into a store with the setter, as `setBoard("rows", reconcile(rows, "id"))`: the items `key` matches keep their identity and change only in the fields that differ, so only what reads those fields wakes. Without `key`, items match by index. */
336
+ export function reconcile<T>(data: T, key?: string): Reconciled<T>
337
+ {
338
+ return new Reconciled(data, key);
339
+ }
340
+
341
+ /** A plain copy of a store or of any part of it, for code that wants ordinary objects. */
342
+ export function snapshot<S>(value: S): Snapshot<S>
343
+ {
344
+ return copy(raws.get(value as object) ?? value, new Map()) as Snapshot<S>;
345
+ }
346
+
347
+ function copy(value: unknown, seen: Map<object, unknown>): unknown
348
+ {
349
+ if (!plain(value))
350
+ {
351
+ return value;
352
+ }
353
+ const known = seen.get(value);
354
+ if (known !== undefined)
355
+ {
356
+ return known;
357
+ }
358
+ if (Array.isArray(value))
359
+ {
360
+ const list: unknown[] = [];
361
+ seen.set(value, list);
362
+ for (const item of value)
363
+ {
364
+ list.push(copy(item, seen));
365
+ }
366
+ return list;
367
+ }
368
+ const record: Record<string, unknown> = {};
369
+ seen.set(value, record);
370
+ for (const [name, inner] of Object.entries(value))
371
+ {
372
+ if (!unsafe(name))
373
+ {
374
+ record[name] = copy(inner, seen);
375
+ }
376
+ }
377
+ return record;
378
+ }