mvc-kit 2.5.2 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/agent-config/claude-code/skills/guide/api-reference.md +10 -10
- package/dist/Channel.d.ts +1 -1
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Collection.d.ts +7 -7
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Model.d.ts +4 -4
- package/dist/Model.d.ts.map +1 -1
- package/dist/PersistentCollection-B8kNECDj.cjs.map +1 -1
- package/dist/{PersistentCollection-BFrgskju.js → PersistentCollection-CbYqzFHc.js} +2 -2
- package/dist/PersistentCollection-CbYqzFHc.js.map +1 -0
- package/dist/PersistentCollection.d.ts +4 -4
- package/dist/PersistentCollection.d.ts.map +1 -1
- package/dist/Resource.d.ts +6 -6
- package/dist/Resource.d.ts.map +1 -1
- package/dist/ViewModel.d.ts +2 -2
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/mvc-kit.cjs.map +1 -1
- package/dist/mvc-kit.js +2 -2
- package/dist/mvc-kit.js.map +1 -1
- package/dist/react/types.d.ts +1 -1
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/use-instance.d.ts +1 -1
- package/dist/react/use-instance.d.ts.map +1 -1
- package/dist/react/use-local.d.ts +4 -4
- package/dist/react/use-local.d.ts.map +1 -1
- package/dist/react/use-model.d.ts +1 -1
- package/dist/react/use-model.d.ts.map +1 -1
- package/dist/react/use-singleton.d.ts +1 -1
- package/dist/react/use-singleton.d.ts.map +1 -1
- package/dist/react-native.js +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/web/WebStorageCollection.d.ts +2 -2
- package/dist/web/WebStorageCollection.d.ts.map +1 -1
- package/dist/web.cjs.map +1 -1
- package/dist/web.js +1 -1
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
- package/dist/PersistentCollection-BFrgskju.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-singleton.d.ts","sourceRoot":"","sources":["../../src/react/use-singleton.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAIzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,GAAG,UAAU,EACtC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EACd,IAAI,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EAElC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,EAC/B,GAAG,IAAI,EAAE,IAAI,GACZ,CAAC,
|
|
1
|
+
{"version":3,"file":"use-singleton.d.ts","sourceRoot":"","sources":["../../src/react/use-singleton.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAIzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,GAAG,UAAU,EACtC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EACd,IAAI,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EAElC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,EAC/B,GAAG,IAAI,EAAE,IAAI,GACZ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEV;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,IAAI,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EACnF,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,EAC/B,GAAG,IAAI,EAAE,IAAI,GACZ,CAAC,CAAC"}
|
package/dist/react-native.js
CHANGED
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.cjs","sources":["../src/react/use-instance.ts","../src/react/guards.ts","../src/react/use-local.ts","../src/react/use-singleton.ts","../src/react/use-model.ts","../src/react/use-event-bus.ts","../src/react/use-teardown.ts","../src/react/provider.tsx"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\nimport type { Subscribable } from '../types';\n\nfunction hasAsyncSubscription(obj: unknown): obj is { subscribeAsync(cb: () => void): () => void } {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n typeof (obj as any).subscribeAsync === 'function'\n );\n}\n\n/**\n * Subscribe to an existing Subscribable instance.\n * No ownership - caller manages the instance lifecycle.\n *\n * If the instance has a `subscribeAsync` method (duck-typed),\n * a second subscription ensures async state changes also\n * trigger React re-renders.\n */\nexport function useInstance<S>(subscribable: Subscribable<S>): Readonly<S> {\n const state = useSyncExternalStore(\n (onStoreChange) => subscribable.subscribe(onStoreChange),\n () => subscribable.state,\n () => subscribable.state // SSR snapshot\n );\n\n // Async subscription — forces re-render when any async status changes.\n // Duck-typed: safe for Collection/Model (they don't have subscribeAsync).\n const versionRef = useRef(0);\n useSyncExternalStore(\n (onStoreChange) => {\n if (!hasAsyncSubscription(subscribable)) return () => {};\n return subscribable.subscribeAsync(() => {\n versionRef.current++;\n onStoreChange();\n });\n },\n () => versionRef.current,\n () => 0 // SSR: no async ops server-side\n );\n\n return state;\n}\n","import type { Subscribable } from '../types';\n\n/** @internal Type guard for Subscribable */\nexport const isSubscribable = (obj: unknown): obj is Subscribable<unknown> =>\n obj !== null &&\n typeof obj === 'object' &&\n 'state' in obj &&\n 'subscribe' in obj &&\n typeof (obj as Subscribable<unknown>).subscribe === 'function';\n\n/** @internal Type guard for Initializable */\nexport const isInitializable = (obj: unknown): obj is { init(): void | Promise<void> } =>\n obj !== null &&\n typeof obj === 'object' &&\n 'init' in obj &&\n typeof (obj as any).init === 'function';\n","import { useRef, useEffect } from 'react';\nimport type { DependencyList } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { isSubscribable, isInitializable } from './guards';\nimport { useInstance } from './use-instance';\nimport type { StateOf } from './types';\n\nfunction depsChanged(prev: DependencyList | undefined, next: DependencyList): boolean {\n if (prev === undefined) return false;\n if (prev.length !== next.length) return true;\n for (let i = 0; i < prev.length; i++) {\n if (!Object.is(prev[i], next[i])) return true;\n }\n return false;\n}\n\n// ── With deps (class + initialState + deps) ────────────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<any> & Disposable>(\n Class: new (initialState: StateOf<T>) => T,\n initialState: StateOf<T>,\n deps: DependencyList,\n): [Readonly<StateOf<T>>, T];\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T,\n deps: DependencyList,\n): [Readonly<S>, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(\n factory: () => T,\n deps: DependencyList,\n): T;\n\n// ── Without deps (existing overloads, unchanged) ───────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [Readonly<S>, T];\n\n/**\n * Create component-scoped Disposable instance (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T\n): [Readonly<S>, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(factory: () => T): T;\n\n// ── Implementation ─────────────────────────────────────────────────\n\nexport function useLocal<T extends Disposable, S = StateOf<T>>(\n classOrFactory: (new (...args: unknown[]) => T) | (() => T),\n ...rest: unknown[]\n): [Readonly<S>, T] | T {\n // ── Detect deps: last arg is an array → treat as deps ──\n let args: unknown[];\n let deps: DependencyList | undefined;\n\n if (rest.length > 0 && Array.isArray(rest[rest.length - 1])) {\n deps = rest[rest.length - 1] as DependencyList;\n args = rest.slice(0, -1);\n } else {\n args = rest;\n deps = undefined;\n }\n\n const instanceRef = useRef<T | null>(null);\n const mountedRef = useRef(false);\n const prevDepsRef = useRef<DependencyList | undefined>(undefined);\n\n // ── Render phase: dep-change detection ──\n if (deps !== undefined && depsChanged(prevDepsRef.current, deps)) {\n instanceRef.current?.dispose();\n instanceRef.current = null;\n }\n if (deps !== undefined) {\n prevDepsRef.current = deps;\n }\n\n // ── Create instance if needed ──\n if (!instanceRef.current || instanceRef.current.disposed) {\n const isClass =\n typeof classOrFactory === 'function' &&\n classOrFactory.prototype &&\n classOrFactory.prototype.constructor === classOrFactory;\n\n if (isClass) {\n instanceRef.current = new (classOrFactory as new (...a: unknown[]) => T)(...args);\n } else {\n instanceRef.current = (classOrFactory as () => T)();\n }\n }\n\n // ── Effect: init + deferred cleanup ──\n useEffect(() => {\n const instance = instanceRef.current!; // capture for cleanup closure\n mountedRef.current = true;\n if (isInitializable(instance)) {\n instance.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n instance.dispose();\n }\n }, 0);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps ?? []);\n\n // ── Subscribe to state if Subscribable ──\n if (isSubscribable(instanceRef.current)) {\n const state = useInstance(instanceRef.current as unknown as Subscribable<S>);\n return [state, instanceRef.current];\n }\n\n return instanceRef.current;\n}\n","import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isInitializable } from './guards';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [Readonly<S>, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [Readonly<S>, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as Readonly<S>;\n return [state, instance];\n }\n\n return instance;\n}\n","import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: Readonly<S>;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useSyncExternalStore(\n (onStoreChange) => modelRef.current!.subscribe(onStoreChange),\n () => modelRef.current!.state,\n () => modelRef.current!.state,\n );\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { EventBus } from '../EventBus';\n\n/**\n * Subscribe to a typed event, auto-unsubscribes on unmount.\n * Accepts an EventBus directly or any object with an `events` property (e.g. a ViewModel).\n */\nexport function useEvent<E extends Record<string, any>, K extends keyof E>(\n source: EventBus<E> | { events: EventBus<E> },\n event: K,\n handler: (payload: E[K]) => void\n): void {\n const bus = source instanceof EventBus ? source : source.events;\n\n // Use ref to keep handler stable across re-renders\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const unsubscribe = bus.on(event, (payload) => {\n handlerRef.current(payload);\n });\n\n return unsubscribe;\n }, [bus, event]);\n}\n\n/**\n * Get a stable emit function for an EventBus.\n */\nexport function useEmit<E extends Record<string, any>>(\n bus: EventBus<E>\n): <K extends keyof E>(event: K, payload: E[K]) => void {\n return useCallback(\n <K extends keyof E>(event: K, payload: E[K]) => {\n bus.emit(event, payload);\n },\n [bus]\n );\n}\n","import { useEffect, useRef } from 'react';\nimport type { Disposable } from '../types';\nimport { teardown } from '../singleton';\n\n/**\n * Teardown singleton class(es) on unmount.\n * Uses deferred disposal to handle StrictMode's double-mount cycle.\n */\nexport function useTeardown(\n ...Classes: Array<new (...args: unknown[]) => Disposable>\n): void {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n for (const Class of Classes) {\n teardown(Class);\n }\n }\n }, 0);\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n}\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { singleton } from '../singleton';\nimport type { Disposable } from '../types';\nimport type { ProviderRegistry } from './types';\n\nconst ProviderContext = createContext<ProviderRegistry | null>(null);\n\n/** Props for the `Provider` component used to inject test/Storybook dependencies. */\nexport interface ProviderProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n provide: Array<[new (...args: any[]) => any, any]>;\n children: ReactNode;\n}\n\n/**\n * DI container for testing and Storybook.\n */\nexport function Provider({ provide, children }: ProviderProps): ReactNode {\n const registry = useMemo(() => {\n const map: ProviderRegistry = new Map();\n for (const [Class, instance] of provide) {\n map.set(Class, instance);\n }\n return map;\n }, [provide]);\n\n return (\n <ProviderContext.Provider value={registry}>\n {children}\n </ProviderContext.Provider>\n );\n}\n\n/**\n * Resolve from Provider context or fallback to singleton().\n */\nexport function useResolve<T, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T {\n const registry = useContext(ProviderContext);\n\n if (registry?.has(Class)) {\n return registry.get(Class) as T;\n }\n return singleton(Class as new (...args: Args) => T & Disposable, ...args);\n}\n"],"names":["hasAsyncSubscription","obj","useInstance","subscribable","state","useSyncExternalStore","onStoreChange","versionRef","useRef","isSubscribable","isInitializable","depsChanged","prev","next","i","useLocal","classOrFactory","rest","args","deps","instanceRef","mountedRef","prevDepsRef","useEffect","instance","useSingleton","Class","singleton","useModel","factory","modelRef","model","useField","field","getSnapshot","useCallback","cachedRef","subscribe","current","snapshot","set","value","partial","useEvent","source","event","handler","bus","EventBus","handlerRef","payload","useEmit","useTeardown","Classes","teardown","ProviderContext","createContext","Provider","provide","children","registry","useMemo","map","useResolve","useContext"],"mappings":"8KAGA,SAASA,EAAqBC,EAAqE,CACjG,OACEA,IAAQ,MACR,OAAOA,GAAQ,UACf,OAAQA,EAAY,gBAAmB,UAE3C,CAUO,SAASC,EAAeC,EAA4C,CACzE,MAAMC,EAAQC,EAAAA,qBACXC,GAAkBH,EAAa,UAAUG,CAAa,EACvD,IAAMH,EAAa,MACnB,IAAMA,EAAa,KAAA,EAKfI,EAAaC,EAAAA,OAAO,CAAC,EAC3BH,OAAAA,EAAAA,qBACGC,GACMN,EAAqBG,CAAY,EAC/BA,EAAa,eAAe,IAAM,CACvCI,EAAW,UACXD,EAAA,CACF,CAAC,EAJ+C,IAAM,CAAC,EAMzD,IAAMC,EAAW,QACjB,IAAM,CAAA,EAGDH,CACT,CCvCO,MAAMK,EAAkBR,GAC7BA,IAAQ,MACR,OAAOA,GAAQ,UACf,UAAWA,GACX,cAAeA,GACf,OAAQA,EAA8B,WAAc,WAGzCS,EAAmBT,GAC9BA,IAAQ,MACR,OAAOA,GAAQ,UACf,SAAUA,GACV,OAAQA,EAAY,MAAS,WCR/B,SAASU,EAAYC,EAAkCC,EAA+B,CACpF,GAAID,IAAS,OAAW,MAAO,GAC/B,GAAIA,EAAK,SAAWC,EAAK,OAAQ,MAAO,GACxC,QAASC,EAAI,EAAGA,EAAIF,EAAK,OAAQE,IAC/B,GAAI,CAAC,OAAO,GAAGF,EAAKE,CAAC,EAAGD,EAAKC,CAAC,CAAC,EAAG,MAAO,GAE3C,MAAO,EACT,CA2EO,SAASC,EACdC,KACGC,EACmB,CAEtB,IAAIC,EACAC,EAEAF,EAAK,OAAS,GAAK,MAAM,QAAQA,EAAKA,EAAK,OAAS,CAAC,CAAC,GACxDE,EAAOF,EAAKA,EAAK,OAAS,CAAC,EAC3BC,EAAOD,EAAK,MAAM,EAAG,EAAE,IAEvBC,EAAOD,EACPE,EAAO,QAGT,MAAMC,EAAcZ,EAAAA,OAAiB,IAAI,EACnCa,EAAab,EAAAA,OAAO,EAAK,EACzBc,EAAcd,EAAAA,OAAmC,MAAS,EA4ChE,OAzCIW,IAAS,QAAaR,EAAYW,EAAY,QAASH,CAAI,IAC7DC,EAAY,SAAS,QAAA,EACrBA,EAAY,QAAU,MAEpBD,IAAS,SACXG,EAAY,QAAUH,IAIpB,CAACC,EAAY,SAAWA,EAAY,QAAQ,YAE5C,OAAOJ,GAAmB,YAC1BA,EAAe,WACfA,EAAe,UAAU,cAAgBA,EAGzCI,EAAY,QAAU,IAAKJ,EAA8C,GAAGE,CAAI,EAEhFE,EAAY,QAAWJ,EAAA,GAK3BO,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAWJ,EAAY,QAC7B,OAAAC,EAAW,QAAU,GACjBX,EAAgBc,CAAQ,GAC1BA,EAAS,KAAA,EAEJ,IAAM,CACXH,EAAW,QAAU,GACrB,WAAW,IAAM,CACVA,EAAW,SACdG,EAAS,QAAA,CAEb,EAAG,CAAC,CACN,CAEF,EAAGL,GAAQ,EAAE,EAGTV,EAAeW,EAAY,OAAO,EAE7B,CADOlB,EAAYkB,EAAY,OAAqC,EAC5DA,EAAY,OAAO,EAG7BA,EAAY,OACrB,CC/HO,SAASK,EACdC,KACGR,EACmB,CACtB,MAAMM,EAAWG,EAAAA,UAAUD,EAAO,GAAGR,CAAI,EAQzC,OANAK,EAAAA,UAAU,IAAM,CACVb,EAAgBc,CAAQ,GAC1BA,EAAS,KAAA,CAEb,EAAG,CAACA,CAAQ,CAAC,EAETf,EAAee,CAAQ,EAElB,CADOtB,EAAYsB,CAAQ,EACnBA,CAAQ,EAGlBA,CACT,CC9BO,SAASI,EACdC,EAC4B,CAC5B,MAAMC,EAAWtB,EAAAA,OAAiB,IAAI,EAChCa,EAAab,EAAAA,OAAO,EAAK,GAE3B,CAACsB,EAAS,SAAWA,EAAS,QAAQ,YACxCA,EAAS,QAAUD,EAAA,GAGrBxB,EAAAA,qBACGC,GAAkBwB,EAAS,QAAS,UAAUxB,CAAa,EAC5D,IAAMwB,EAAS,QAAS,MACxB,IAAMA,EAAS,QAAS,KAAA,EAG1BP,EAAAA,UAAU,KACRF,EAAW,QAAU,GACjBX,EAAgBoB,EAAS,OAAO,GAClCA,EAAS,QAAQ,KAAA,EAEZ,IAAM,CACXT,EAAW,QAAU,GACrB,WAAW,IAAM,CACVA,EAAW,SACdS,EAAS,SAAS,QAAA,CAEtB,EAAG,CAAC,CACN,GACC,CAAA,CAAE,EAEL,MAAMC,EAAQD,EAAS,QAEvB,MAAO,CACL,MAAOC,EAAM,MACb,OAAQA,EAAM,OACd,MAAOA,EAAM,MACb,MAAOA,EAAM,MACb,MAAAA,CAAA,CAEJ,CAYO,SAASC,EACdD,EACAE,EACmB,CAEnB,MAAMC,EAAcC,EAAAA,YAAY,KACvB,CACL,MAAOJ,EAAM,MAAME,CAAK,EACxB,MAAOF,EAAM,OAAOE,CAAK,CAAA,GAE1B,CAACF,EAAOE,CAAK,CAAC,EAGXG,EAAY5B,SAAO0B,GAAa,EAEhCG,EAAYF,EAAAA,YACf7B,GACQyB,EAAM,UAAU,IAAM,CAC3B,MAAMlB,EAAOqB,EAAA,EACPI,EAAUF,EAAU,SAGtBvB,EAAK,QAAUyB,EAAQ,OAASzB,EAAK,QAAUyB,EAAQ,SACzDF,EAAU,QAAUvB,EACpBP,EAAA,EAEJ,CAAC,EAEH,CAACyB,EAAOG,CAAW,CAAA,EAGfK,EAAWlC,EAAAA,qBACfgC,EACA,IAAMD,EAAU,QAChB,IAAMA,EAAU,OAAA,EAGZI,EAAML,EAAAA,YACTM,GAAgB,CAGf,MAAMC,EAAsB,CAAE,CAACT,CAAK,EAAGQ,CAAA,EACtCV,EAA4D,IAAIW,CAAO,CAC1E,EACA,CAACX,EAAOE,CAAK,CAAA,EAGf,MAAO,CACL,MAAOM,EAAS,MAChB,MAAOA,EAAS,MAChB,IAAAC,CAAA,CAEJ,CCnHO,SAASG,EACdC,EACAC,EACAC,EACM,CACN,MAAMC,EAAMH,aAAkBI,EAAAA,SAAWJ,EAASA,EAAO,OAGnDK,EAAazC,EAAAA,OAAOsC,CAAO,EACjCG,EAAW,QAAUH,EAErBvB,EAAAA,UAAU,IACYwB,EAAI,GAAGF,EAAQK,GAAY,CAC7CD,EAAW,QAAQC,CAAO,CAC5B,CAAC,EAGA,CAACH,EAAKF,CAAK,CAAC,CACjB,CAKO,SAASM,EACdJ,EACsD,CACtD,OAAOZ,EAAAA,YACL,CAAoBU,EAAUK,IAAkB,CAC9CH,EAAI,KAAKF,EAAOK,CAAO,CACzB,EACA,CAACH,CAAG,CAAA,CAER,CC/BO,SAASK,KACXC,EACG,CACN,MAAMhC,EAAab,EAAAA,OAAO,EAAK,EAE/Be,EAAAA,UAAU,KACRF,EAAW,QAAU,GACd,IAAM,CACXA,EAAW,QAAU,GACrB,WAAW,IAAM,CACf,GAAI,CAACA,EAAW,QACd,UAAWK,KAAS2B,EAClBC,EAAAA,SAAS5B,CAAK,CAGpB,EAAG,CAAC,CACN,GACC,CAAA,CAAE,CACP,CCrBA,MAAM6B,EAAkBC,EAAAA,cAAuC,IAAI,EAY5D,SAASC,EAAS,CAAE,QAAAC,EAAS,SAAAC,GAAsC,CACxE,MAAMC,EAAWC,EAAAA,QAAQ,IAAM,CAC7B,MAAMC,MAA4B,IAClC,SAAW,CAACpC,EAAOF,CAAQ,IAAKkC,EAC9BI,EAAI,IAAIpC,EAAOF,CAAQ,EAEzB,OAAOsC,CACT,EAAG,CAACJ,CAAO,CAAC,EAEZ,aACGH,EAAgB,SAAhB,CAAyB,MAAOK,EAC9B,SAAAD,EACH,CAEJ,CAKO,SAASI,EACdrC,KACGR,EACA,CACH,MAAM0C,EAAWI,EAAAA,WAAWT,CAAe,EAE3C,OAAIK,GAAU,IAAIlC,CAAK,EACdkC,EAAS,IAAIlC,CAAK,EAEpBC,EAAAA,UAAUD,EAAgD,GAAGR,CAAI,CAC1E"}
|
|
1
|
+
{"version":3,"file":"react.cjs","sources":["../src/react/use-instance.ts","../src/react/guards.ts","../src/react/use-local.ts","../src/react/use-singleton.ts","../src/react/use-model.ts","../src/react/use-event-bus.ts","../src/react/use-teardown.ts","../src/react/provider.tsx"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\nimport type { Subscribable } from '../types';\n\nfunction hasAsyncSubscription(obj: unknown): obj is { subscribeAsync(cb: () => void): () => void } {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n typeof (obj as any).subscribeAsync === 'function'\n );\n}\n\n/**\n * Subscribe to an existing Subscribable instance.\n * No ownership - caller manages the instance lifecycle.\n *\n * If the instance has a `subscribeAsync` method (duck-typed),\n * a second subscription ensures async state changes also\n * trigger React re-renders.\n */\nexport function useInstance<S>(subscribable: Subscribable<S>): S {\n const state = useSyncExternalStore(\n (onStoreChange) => subscribable.subscribe(onStoreChange),\n () => subscribable.state,\n () => subscribable.state // SSR snapshot\n );\n\n // Async subscription — forces re-render when any async status changes.\n // Duck-typed: safe for Collection/Model (they don't have subscribeAsync).\n const versionRef = useRef(0);\n useSyncExternalStore(\n (onStoreChange) => {\n if (!hasAsyncSubscription(subscribable)) return () => {};\n return subscribable.subscribeAsync(() => {\n versionRef.current++;\n onStoreChange();\n });\n },\n () => versionRef.current,\n () => 0 // SSR: no async ops server-side\n );\n\n return state;\n}\n","import type { Subscribable } from '../types';\n\n/** @internal Type guard for Subscribable */\nexport const isSubscribable = (obj: unknown): obj is Subscribable<unknown> =>\n obj !== null &&\n typeof obj === 'object' &&\n 'state' in obj &&\n 'subscribe' in obj &&\n typeof (obj as Subscribable<unknown>).subscribe === 'function';\n\n/** @internal Type guard for Initializable */\nexport const isInitializable = (obj: unknown): obj is { init(): void | Promise<void> } =>\n obj !== null &&\n typeof obj === 'object' &&\n 'init' in obj &&\n typeof (obj as any).init === 'function';\n","import { useRef, useEffect } from 'react';\nimport type { DependencyList } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { isSubscribable, isInitializable } from './guards';\nimport { useInstance } from './use-instance';\nimport type { StateOf } from './types';\n\nfunction depsChanged(prev: DependencyList | undefined, next: DependencyList): boolean {\n if (prev === undefined) return false;\n if (prev.length !== next.length) return true;\n for (let i = 0; i < prev.length; i++) {\n if (!Object.is(prev[i], next[i])) return true;\n }\n return false;\n}\n\n// ── With deps (class + initialState + deps) ────────────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<any> & Disposable>(\n Class: new (initialState: StateOf<T>) => T,\n initialState: StateOf<T>,\n deps: DependencyList,\n): [StateOf<T>, T];\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T,\n deps: DependencyList,\n): [S, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(\n factory: () => T,\n deps: DependencyList,\n): T;\n\n// ── Without deps (existing overloads, unchanged) ───────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Create component-scoped Disposable instance (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T\n): [S, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(factory: () => T): T;\n\n// ── Implementation ─────────────────────────────────────────────────\n\nexport function useLocal<T extends Disposable, S = StateOf<T>>(\n classOrFactory: (new (...args: unknown[]) => T) | (() => T),\n ...rest: unknown[]\n): [S, T] | T {\n // ── Detect deps: last arg is an array → treat as deps ──\n let args: unknown[];\n let deps: DependencyList | undefined;\n\n if (rest.length > 0 && Array.isArray(rest[rest.length - 1])) {\n deps = rest[rest.length - 1] as DependencyList;\n args = rest.slice(0, -1);\n } else {\n args = rest;\n deps = undefined;\n }\n\n const instanceRef = useRef<T | null>(null);\n const mountedRef = useRef(false);\n const prevDepsRef = useRef<DependencyList | undefined>(undefined);\n\n // ── Render phase: dep-change detection ──\n if (deps !== undefined && depsChanged(prevDepsRef.current, deps)) {\n instanceRef.current?.dispose();\n instanceRef.current = null;\n }\n if (deps !== undefined) {\n prevDepsRef.current = deps;\n }\n\n // ── Create instance if needed ──\n if (!instanceRef.current || instanceRef.current.disposed) {\n const isClass =\n typeof classOrFactory === 'function' &&\n classOrFactory.prototype &&\n classOrFactory.prototype.constructor === classOrFactory;\n\n if (isClass) {\n instanceRef.current = new (classOrFactory as new (...a: unknown[]) => T)(...args);\n } else {\n instanceRef.current = (classOrFactory as () => T)();\n }\n }\n\n // ── Effect: init + deferred cleanup ──\n useEffect(() => {\n const instance = instanceRef.current!; // capture for cleanup closure\n mountedRef.current = true;\n if (isInitializable(instance)) {\n instance.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n instance.dispose();\n }\n }, 0);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps ?? []);\n\n // ── Subscribe to state if Subscribable ──\n if (isSubscribable(instanceRef.current)) {\n const state = useInstance(instanceRef.current as unknown as Subscribable<S>);\n return [state, instanceRef.current];\n }\n\n return instanceRef.current;\n}\n","import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isInitializable } from './guards';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as S;\n return [state, instance];\n }\n\n return instance;\n}\n","import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: S;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useSyncExternalStore(\n (onStoreChange) => modelRef.current!.subscribe(onStoreChange),\n () => modelRef.current!.state,\n () => modelRef.current!.state,\n );\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { EventBus } from '../EventBus';\n\n/**\n * Subscribe to a typed event, auto-unsubscribes on unmount.\n * Accepts an EventBus directly or any object with an `events` property (e.g. a ViewModel).\n */\nexport function useEvent<E extends Record<string, any>, K extends keyof E>(\n source: EventBus<E> | { events: EventBus<E> },\n event: K,\n handler: (payload: E[K]) => void\n): void {\n const bus = source instanceof EventBus ? source : source.events;\n\n // Use ref to keep handler stable across re-renders\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const unsubscribe = bus.on(event, (payload) => {\n handlerRef.current(payload);\n });\n\n return unsubscribe;\n }, [bus, event]);\n}\n\n/**\n * Get a stable emit function for an EventBus.\n */\nexport function useEmit<E extends Record<string, any>>(\n bus: EventBus<E>\n): <K extends keyof E>(event: K, payload: E[K]) => void {\n return useCallback(\n <K extends keyof E>(event: K, payload: E[K]) => {\n bus.emit(event, payload);\n },\n [bus]\n );\n}\n","import { useEffect, useRef } from 'react';\nimport type { Disposable } from '../types';\nimport { teardown } from '../singleton';\n\n/**\n * Teardown singleton class(es) on unmount.\n * Uses deferred disposal to handle StrictMode's double-mount cycle.\n */\nexport function useTeardown(\n ...Classes: Array<new (...args: unknown[]) => Disposable>\n): void {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n for (const Class of Classes) {\n teardown(Class);\n }\n }\n }, 0);\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n}\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { singleton } from '../singleton';\nimport type { Disposable } from '../types';\nimport type { ProviderRegistry } from './types';\n\nconst ProviderContext = createContext<ProviderRegistry | null>(null);\n\n/** Props for the `Provider` component used to inject test/Storybook dependencies. */\nexport interface ProviderProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n provide: Array<[new (...args: any[]) => any, any]>;\n children: ReactNode;\n}\n\n/**\n * DI container for testing and Storybook.\n */\nexport function Provider({ provide, children }: ProviderProps): ReactNode {\n const registry = useMemo(() => {\n const map: ProviderRegistry = new Map();\n for (const [Class, instance] of provide) {\n map.set(Class, instance);\n }\n return map;\n }, [provide]);\n\n return (\n <ProviderContext.Provider value={registry}>\n {children}\n </ProviderContext.Provider>\n );\n}\n\n/**\n * Resolve from Provider context or fallback to singleton().\n */\nexport function useResolve<T, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T {\n const registry = useContext(ProviderContext);\n\n if (registry?.has(Class)) {\n return registry.get(Class) as T;\n }\n return singleton(Class as new (...args: Args) => T & Disposable, ...args);\n}\n"],"names":["hasAsyncSubscription","obj","useInstance","subscribable","state","useSyncExternalStore","onStoreChange","versionRef","useRef","isSubscribable","isInitializable","depsChanged","prev","next","i","useLocal","classOrFactory","rest","args","deps","instanceRef","mountedRef","prevDepsRef","useEffect","instance","useSingleton","Class","singleton","useModel","factory","modelRef","model","useField","field","getSnapshot","useCallback","cachedRef","subscribe","current","snapshot","set","value","partial","useEvent","source","event","handler","bus","EventBus","handlerRef","payload","useEmit","useTeardown","Classes","teardown","ProviderContext","createContext","Provider","provide","children","registry","useMemo","map","useResolve","useContext"],"mappings":"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"}
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sources":["../src/react/use-instance.ts","../src/react/guards.ts","../src/react/use-local.ts","../src/react/use-singleton.ts","../src/react/use-model.ts","../src/react/use-event-bus.ts","../src/react/use-teardown.ts","../src/react/provider.tsx"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\nimport type { Subscribable } from '../types';\n\nfunction hasAsyncSubscription(obj: unknown): obj is { subscribeAsync(cb: () => void): () => void } {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n typeof (obj as any).subscribeAsync === 'function'\n );\n}\n\n/**\n * Subscribe to an existing Subscribable instance.\n * No ownership - caller manages the instance lifecycle.\n *\n * If the instance has a `subscribeAsync` method (duck-typed),\n * a second subscription ensures async state changes also\n * trigger React re-renders.\n */\nexport function useInstance<S>(subscribable: Subscribable<S>): Readonly<S> {\n const state = useSyncExternalStore(\n (onStoreChange) => subscribable.subscribe(onStoreChange),\n () => subscribable.state,\n () => subscribable.state // SSR snapshot\n );\n\n // Async subscription — forces re-render when any async status changes.\n // Duck-typed: safe for Collection/Model (they don't have subscribeAsync).\n const versionRef = useRef(0);\n useSyncExternalStore(\n (onStoreChange) => {\n if (!hasAsyncSubscription(subscribable)) return () => {};\n return subscribable.subscribeAsync(() => {\n versionRef.current++;\n onStoreChange();\n });\n },\n () => versionRef.current,\n () => 0 // SSR: no async ops server-side\n );\n\n return state;\n}\n","import type { Subscribable } from '../types';\n\n/** @internal Type guard for Subscribable */\nexport const isSubscribable = (obj: unknown): obj is Subscribable<unknown> =>\n obj !== null &&\n typeof obj === 'object' &&\n 'state' in obj &&\n 'subscribe' in obj &&\n typeof (obj as Subscribable<unknown>).subscribe === 'function';\n\n/** @internal Type guard for Initializable */\nexport const isInitializable = (obj: unknown): obj is { init(): void | Promise<void> } =>\n obj !== null &&\n typeof obj === 'object' &&\n 'init' in obj &&\n typeof (obj as any).init === 'function';\n","import { useRef, useEffect } from 'react';\nimport type { DependencyList } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { isSubscribable, isInitializable } from './guards';\nimport { useInstance } from './use-instance';\nimport type { StateOf } from './types';\n\nfunction depsChanged(prev: DependencyList | undefined, next: DependencyList): boolean {\n if (prev === undefined) return false;\n if (prev.length !== next.length) return true;\n for (let i = 0; i < prev.length; i++) {\n if (!Object.is(prev[i], next[i])) return true;\n }\n return false;\n}\n\n// ── With deps (class + initialState + deps) ────────────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<any> & Disposable>(\n Class: new (initialState: StateOf<T>) => T,\n initialState: StateOf<T>,\n deps: DependencyList,\n): [Readonly<StateOf<T>>, T];\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T,\n deps: DependencyList,\n): [Readonly<S>, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(\n factory: () => T,\n deps: DependencyList,\n): T;\n\n// ── Without deps (existing overloads, unchanged) ───────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [Readonly<S>, T];\n\n/**\n * Create component-scoped Disposable instance (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T\n): [Readonly<S>, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(factory: () => T): T;\n\n// ── Implementation ─────────────────────────────────────────────────\n\nexport function useLocal<T extends Disposable, S = StateOf<T>>(\n classOrFactory: (new (...args: unknown[]) => T) | (() => T),\n ...rest: unknown[]\n): [Readonly<S>, T] | T {\n // ── Detect deps: last arg is an array → treat as deps ──\n let args: unknown[];\n let deps: DependencyList | undefined;\n\n if (rest.length > 0 && Array.isArray(rest[rest.length - 1])) {\n deps = rest[rest.length - 1] as DependencyList;\n args = rest.slice(0, -1);\n } else {\n args = rest;\n deps = undefined;\n }\n\n const instanceRef = useRef<T | null>(null);\n const mountedRef = useRef(false);\n const prevDepsRef = useRef<DependencyList | undefined>(undefined);\n\n // ── Render phase: dep-change detection ──\n if (deps !== undefined && depsChanged(prevDepsRef.current, deps)) {\n instanceRef.current?.dispose();\n instanceRef.current = null;\n }\n if (deps !== undefined) {\n prevDepsRef.current = deps;\n }\n\n // ── Create instance if needed ──\n if (!instanceRef.current || instanceRef.current.disposed) {\n const isClass =\n typeof classOrFactory === 'function' &&\n classOrFactory.prototype &&\n classOrFactory.prototype.constructor === classOrFactory;\n\n if (isClass) {\n instanceRef.current = new (classOrFactory as new (...a: unknown[]) => T)(...args);\n } else {\n instanceRef.current = (classOrFactory as () => T)();\n }\n }\n\n // ── Effect: init + deferred cleanup ──\n useEffect(() => {\n const instance = instanceRef.current!; // capture for cleanup closure\n mountedRef.current = true;\n if (isInitializable(instance)) {\n instance.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n instance.dispose();\n }\n }, 0);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps ?? []);\n\n // ── Subscribe to state if Subscribable ──\n if (isSubscribable(instanceRef.current)) {\n const state = useInstance(instanceRef.current as unknown as Subscribable<S>);\n return [state, instanceRef.current];\n }\n\n return instanceRef.current;\n}\n","import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isInitializable } from './guards';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [Readonly<S>, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [Readonly<S>, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as Readonly<S>;\n return [state, instance];\n }\n\n return instance;\n}\n","import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: Readonly<S>;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useSyncExternalStore(\n (onStoreChange) => modelRef.current!.subscribe(onStoreChange),\n () => modelRef.current!.state,\n () => modelRef.current!.state,\n );\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { EventBus } from '../EventBus';\n\n/**\n * Subscribe to a typed event, auto-unsubscribes on unmount.\n * Accepts an EventBus directly or any object with an `events` property (e.g. a ViewModel).\n */\nexport function useEvent<E extends Record<string, any>, K extends keyof E>(\n source: EventBus<E> | { events: EventBus<E> },\n event: K,\n handler: (payload: E[K]) => void\n): void {\n const bus = source instanceof EventBus ? source : source.events;\n\n // Use ref to keep handler stable across re-renders\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const unsubscribe = bus.on(event, (payload) => {\n handlerRef.current(payload);\n });\n\n return unsubscribe;\n }, [bus, event]);\n}\n\n/**\n * Get a stable emit function for an EventBus.\n */\nexport function useEmit<E extends Record<string, any>>(\n bus: EventBus<E>\n): <K extends keyof E>(event: K, payload: E[K]) => void {\n return useCallback(\n <K extends keyof E>(event: K, payload: E[K]) => {\n bus.emit(event, payload);\n },\n [bus]\n );\n}\n","import { useEffect, useRef } from 'react';\nimport type { Disposable } from '../types';\nimport { teardown } from '../singleton';\n\n/**\n * Teardown singleton class(es) on unmount.\n * Uses deferred disposal to handle StrictMode's double-mount cycle.\n */\nexport function useTeardown(\n ...Classes: Array<new (...args: unknown[]) => Disposable>\n): void {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n for (const Class of Classes) {\n teardown(Class);\n }\n }\n }, 0);\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n}\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { singleton } from '../singleton';\nimport type { Disposable } from '../types';\nimport type { ProviderRegistry } from './types';\n\nconst ProviderContext = createContext<ProviderRegistry | null>(null);\n\n/** Props for the `Provider` component used to inject test/Storybook dependencies. */\nexport interface ProviderProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n provide: Array<[new (...args: any[]) => any, any]>;\n children: ReactNode;\n}\n\n/**\n * DI container for testing and Storybook.\n */\nexport function Provider({ provide, children }: ProviderProps): ReactNode {\n const registry = useMemo(() => {\n const map: ProviderRegistry = new Map();\n for (const [Class, instance] of provide) {\n map.set(Class, instance);\n }\n return map;\n }, [provide]);\n\n return (\n <ProviderContext.Provider value={registry}>\n {children}\n </ProviderContext.Provider>\n );\n}\n\n/**\n * Resolve from Provider context or fallback to singleton().\n */\nexport function useResolve<T, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T {\n const registry = useContext(ProviderContext);\n\n if (registry?.has(Class)) {\n return registry.get(Class) as T;\n }\n return singleton(Class as new (...args: Args) => T & Disposable, ...args);\n}\n"],"names":["hasAsyncSubscription","obj","useInstance","subscribable","state","useSyncExternalStore","onStoreChange","versionRef","useRef","isSubscribable","isInitializable","depsChanged","prev","next","i","useLocal","classOrFactory","rest","args","deps","instanceRef","mountedRef","prevDepsRef","useEffect","instance","useSingleton","Class","singleton","useModel","factory","modelRef","model","useField","field","getSnapshot","useCallback","cachedRef","subscribe","current","snapshot","set","value","partial","useEvent","source","event","handler","bus","EventBus","handlerRef","payload","useEmit","useTeardown","Classes","teardown","ProviderContext","createContext","Provider","provide","children","registry","useMemo","map","useResolve","useContext"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react.js","sources":["../src/react/use-instance.ts","../src/react/guards.ts","../src/react/use-local.ts","../src/react/use-singleton.ts","../src/react/use-model.ts","../src/react/use-event-bus.ts","../src/react/use-teardown.ts","../src/react/provider.tsx"],"sourcesContent":["import { useSyncExternalStore, useRef } from 'react';\nimport type { Subscribable } from '../types';\n\nfunction hasAsyncSubscription(obj: unknown): obj is { subscribeAsync(cb: () => void): () => void } {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n typeof (obj as any).subscribeAsync === 'function'\n );\n}\n\n/**\n * Subscribe to an existing Subscribable instance.\n * No ownership - caller manages the instance lifecycle.\n *\n * If the instance has a `subscribeAsync` method (duck-typed),\n * a second subscription ensures async state changes also\n * trigger React re-renders.\n */\nexport function useInstance<S>(subscribable: Subscribable<S>): S {\n const state = useSyncExternalStore(\n (onStoreChange) => subscribable.subscribe(onStoreChange),\n () => subscribable.state,\n () => subscribable.state // SSR snapshot\n );\n\n // Async subscription — forces re-render when any async status changes.\n // Duck-typed: safe for Collection/Model (they don't have subscribeAsync).\n const versionRef = useRef(0);\n useSyncExternalStore(\n (onStoreChange) => {\n if (!hasAsyncSubscription(subscribable)) return () => {};\n return subscribable.subscribeAsync(() => {\n versionRef.current++;\n onStoreChange();\n });\n },\n () => versionRef.current,\n () => 0 // SSR: no async ops server-side\n );\n\n return state;\n}\n","import type { Subscribable } from '../types';\n\n/** @internal Type guard for Subscribable */\nexport const isSubscribable = (obj: unknown): obj is Subscribable<unknown> =>\n obj !== null &&\n typeof obj === 'object' &&\n 'state' in obj &&\n 'subscribe' in obj &&\n typeof (obj as Subscribable<unknown>).subscribe === 'function';\n\n/** @internal Type guard for Initializable */\nexport const isInitializable = (obj: unknown): obj is { init(): void | Promise<void> } =>\n obj !== null &&\n typeof obj === 'object' &&\n 'init' in obj &&\n typeof (obj as any).init === 'function';\n","import { useRef, useEffect } from 'react';\nimport type { DependencyList } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { isSubscribable, isInitializable } from './guards';\nimport { useInstance } from './use-instance';\nimport type { StateOf } from './types';\n\nfunction depsChanged(prev: DependencyList | undefined, next: DependencyList): boolean {\n if (prev === undefined) return false;\n if (prev.length !== next.length) return true;\n for (let i = 0; i < prev.length; i++) {\n if (!Object.is(prev[i], next[i])) return true;\n }\n return false;\n}\n\n// ── With deps (class + initialState + deps) ────────────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<any> & Disposable>(\n Class: new (initialState: StateOf<T>) => T,\n initialState: StateOf<T>,\n deps: DependencyList,\n): [StateOf<T>, T];\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T,\n deps: DependencyList,\n): [S, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Disposes and recreates when deps change.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(\n factory: () => T,\n deps: DependencyList,\n): T;\n\n// ── Without deps (existing overloads, unchanged) ───────────────────\n\n/**\n * Create component-scoped Subscribable instance, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Create component-scoped Disposable instance (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n/**\n * Create component-scoped Subscribable instance via factory, auto-disposed on unmount.\n * Returns [state, instance] tuple.\n */\nexport function useLocal<T extends Subscribable<S> & Disposable, S = StateOf<T>>(\n factory: () => T\n): [S, T];\n\n/**\n * Create component-scoped Disposable instance via factory (non-Subscribable), auto-disposed on unmount.\n * Returns the instance directly.\n */\nexport function useLocal<T extends Disposable>(factory: () => T): T;\n\n// ── Implementation ─────────────────────────────────────────────────\n\nexport function useLocal<T extends Disposable, S = StateOf<T>>(\n classOrFactory: (new (...args: unknown[]) => T) | (() => T),\n ...rest: unknown[]\n): [S, T] | T {\n // ── Detect deps: last arg is an array → treat as deps ──\n let args: unknown[];\n let deps: DependencyList | undefined;\n\n if (rest.length > 0 && Array.isArray(rest[rest.length - 1])) {\n deps = rest[rest.length - 1] as DependencyList;\n args = rest.slice(0, -1);\n } else {\n args = rest;\n deps = undefined;\n }\n\n const instanceRef = useRef<T | null>(null);\n const mountedRef = useRef(false);\n const prevDepsRef = useRef<DependencyList | undefined>(undefined);\n\n // ── Render phase: dep-change detection ──\n if (deps !== undefined && depsChanged(prevDepsRef.current, deps)) {\n instanceRef.current?.dispose();\n instanceRef.current = null;\n }\n if (deps !== undefined) {\n prevDepsRef.current = deps;\n }\n\n // ── Create instance if needed ──\n if (!instanceRef.current || instanceRef.current.disposed) {\n const isClass =\n typeof classOrFactory === 'function' &&\n classOrFactory.prototype &&\n classOrFactory.prototype.constructor === classOrFactory;\n\n if (isClass) {\n instanceRef.current = new (classOrFactory as new (...a: unknown[]) => T)(...args);\n } else {\n instanceRef.current = (classOrFactory as () => T)();\n }\n }\n\n // ── Effect: init + deferred cleanup ──\n useEffect(() => {\n const instance = instanceRef.current!; // capture for cleanup closure\n mountedRef.current = true;\n if (isInitializable(instance)) {\n instance.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n instance.dispose();\n }\n }, 0);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps ?? []);\n\n // ── Subscribe to state if Subscribable ──\n if (isSubscribable(instanceRef.current)) {\n const state = useInstance(instanceRef.current as unknown as Subscribable<S>);\n return [state, instanceRef.current];\n }\n\n return instanceRef.current;\n}\n","import { useEffect } from 'react';\nimport type { Subscribable, Disposable } from '../types';\nimport { singleton } from '../singleton';\nimport { useInstance } from './use-instance';\nimport { isSubscribable, isInitializable } from './guards';\nimport type { StateOf } from './types';\n\n/**\n * Get singleton Subscribable instance and subscribe to its state.\n * Returns [state, instance] tuple.\n */\nexport function useSingleton<\n T extends Subscribable<S> & Disposable,\n S = StateOf<T>,\n Args extends unknown[] = unknown[]\n>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T];\n\n/**\n * Get singleton Disposable instance (non-Subscribable).\n * Returns the instance directly.\n */\nexport function useSingleton<T extends Disposable, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T;\n\n// Implementation\nexport function useSingleton<T extends Disposable, S = StateOf<T>, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): [S, T] | T {\n const instance = singleton(Class, ...args);\n\n useEffect(() => {\n if (isInitializable(instance)) {\n instance.init();\n }\n }, [instance]);\n\n if (isSubscribable(instance)) {\n const state = useInstance(instance) as S;\n return [state, instance];\n }\n\n return instance;\n}\n","import { useRef, useEffect, useSyncExternalStore, useCallback } from 'react';\nimport type { Model } from '../Model';\nimport type { ValidationErrors } from '../types';\nimport type { StateOf } from './types';\nimport { isInitializable } from './guards';\n\n/** Return type of `useModel`, providing state, validation, and model access. */\nexport interface ModelHandle<S extends object, M extends Model<S>> {\n state: S;\n errors: ValidationErrors<S>;\n valid: boolean;\n dirty: boolean;\n model: M;\n}\n\n/**\n * Bind to a component-scoped Model with validation and dirty state exposed.\n */\nexport function useModel<M extends Model<any>>(\n factory: () => M\n): ModelHandle<StateOf<M>, M> {\n const modelRef = useRef<M | null>(null);\n const mountedRef = useRef(false);\n\n if (!modelRef.current || modelRef.current.disposed) {\n modelRef.current = factory();\n }\n\n useSyncExternalStore(\n (onStoreChange) => modelRef.current!.subscribe(onStoreChange),\n () => modelRef.current!.state,\n () => modelRef.current!.state,\n );\n\n useEffect(() => {\n mountedRef.current = true;\n if (isInitializable(modelRef.current)) {\n modelRef.current.init();\n }\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n modelRef.current?.dispose();\n }\n }, 0);\n };\n }, []);\n\n const model = modelRef.current;\n\n return {\n state: model.state,\n errors: model.errors,\n valid: model.valid,\n dirty: model.dirty,\n model,\n };\n}\n\n/** Return type of `useField`, providing a single field's value, error, and setter. */\nexport interface FieldHandle<V> {\n value: V;\n error: string | undefined;\n set: (value: V) => void;\n}\n\n/**\n * Bind to a single Model field with surgical re-renders.\n */\nexport function useField<S extends object, K extends keyof S>(\n model: Model<S>,\n field: K\n): FieldHandle<S[K]> {\n // Track the field value and error for comparison\n const getSnapshot = useCallback(() => {\n return {\n value: model.state[field],\n error: model.errors[field],\n };\n }, [model, field]);\n\n // Use object comparison for subscription\n const cachedRef = useRef(getSnapshot());\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n return model.subscribe(() => {\n const next = getSnapshot();\n const current = cachedRef.current;\n\n // Only trigger re-render if field value or error changed\n if (next.value !== current.value || next.error !== current.error) {\n cachedRef.current = next;\n onStoreChange();\n }\n });\n },\n [model, getSnapshot]\n );\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => cachedRef.current,\n () => cachedRef.current\n );\n\n const set = useCallback(\n (value: S[K]) => {\n // Access the protected set method through type assertion\n // The Model subclass should expose a setter method\n const partial: Partial<S> = { [field]: value } as unknown as Partial<S>;\n (model as unknown as { set: (partial: Partial<S>) => void }).set(partial);\n },\n [model, field]\n );\n\n return {\n value: snapshot.value,\n error: snapshot.error,\n set,\n };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { EventBus } from '../EventBus';\n\n/**\n * Subscribe to a typed event, auto-unsubscribes on unmount.\n * Accepts an EventBus directly or any object with an `events` property (e.g. a ViewModel).\n */\nexport function useEvent<E extends Record<string, any>, K extends keyof E>(\n source: EventBus<E> | { events: EventBus<E> },\n event: K,\n handler: (payload: E[K]) => void\n): void {\n const bus = source instanceof EventBus ? source : source.events;\n\n // Use ref to keep handler stable across re-renders\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n const unsubscribe = bus.on(event, (payload) => {\n handlerRef.current(payload);\n });\n\n return unsubscribe;\n }, [bus, event]);\n}\n\n/**\n * Get a stable emit function for an EventBus.\n */\nexport function useEmit<E extends Record<string, any>>(\n bus: EventBus<E>\n): <K extends keyof E>(event: K, payload: E[K]) => void {\n return useCallback(\n <K extends keyof E>(event: K, payload: E[K]) => {\n bus.emit(event, payload);\n },\n [bus]\n );\n}\n","import { useEffect, useRef } from 'react';\nimport type { Disposable } from '../types';\nimport { teardown } from '../singleton';\n\n/**\n * Teardown singleton class(es) on unmount.\n * Uses deferred disposal to handle StrictMode's double-mount cycle.\n */\nexport function useTeardown(\n ...Classes: Array<new (...args: unknown[]) => Disposable>\n): void {\n const mountedRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n setTimeout(() => {\n if (!mountedRef.current) {\n for (const Class of Classes) {\n teardown(Class);\n }\n }\n }, 0);\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n}\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { singleton } from '../singleton';\nimport type { Disposable } from '../types';\nimport type { ProviderRegistry } from './types';\n\nconst ProviderContext = createContext<ProviderRegistry | null>(null);\n\n/** Props for the `Provider` component used to inject test/Storybook dependencies. */\nexport interface ProviderProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n provide: Array<[new (...args: any[]) => any, any]>;\n children: ReactNode;\n}\n\n/**\n * DI container for testing and Storybook.\n */\nexport function Provider({ provide, children }: ProviderProps): ReactNode {\n const registry = useMemo(() => {\n const map: ProviderRegistry = new Map();\n for (const [Class, instance] of provide) {\n map.set(Class, instance);\n }\n return map;\n }, [provide]);\n\n return (\n <ProviderContext.Provider value={registry}>\n {children}\n </ProviderContext.Provider>\n );\n}\n\n/**\n * Resolve from Provider context or fallback to singleton().\n */\nexport function useResolve<T, Args extends unknown[] = unknown[]>(\n Class: new (...args: Args) => T,\n ...args: Args\n): T {\n const registry = useContext(ProviderContext);\n\n if (registry?.has(Class)) {\n return registry.get(Class) as T;\n }\n return singleton(Class as new (...args: Args) => T & Disposable, ...args);\n}\n"],"names":["hasAsyncSubscription","obj","useInstance","subscribable","state","useSyncExternalStore","onStoreChange","versionRef","useRef","isSubscribable","isInitializable","depsChanged","prev","next","i","useLocal","classOrFactory","rest","args","deps","instanceRef","mountedRef","prevDepsRef","useEffect","instance","useSingleton","Class","singleton","useModel","factory","modelRef","model","useField","field","getSnapshot","useCallback","cachedRef","subscribe","current","snapshot","set","value","partial","useEvent","source","event","handler","bus","EventBus","handlerRef","payload","useEmit","useTeardown","Classes","teardown","ProviderContext","createContext","Provider","provide","children","registry","useMemo","map","useResolve","useContext"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import type { AppError } from './errors';
|
|
|
2
2
|
/**
|
|
3
3
|
* Listener callback type for state change notifications.
|
|
4
4
|
*/
|
|
5
|
-
export type Listener<S> = (state:
|
|
5
|
+
export type Listener<S> = (state: S, prev: S) => void;
|
|
6
6
|
/**
|
|
7
7
|
* Updater function type for functional state updates.
|
|
8
8
|
*/
|
|
9
|
-
export type Updater<S> = (prev:
|
|
9
|
+
export type Updater<S> = (prev: S) => Partial<S>;
|
|
10
10
|
/**
|
|
11
11
|
* Validation errors mapped by field key.
|
|
12
12
|
*/
|
|
@@ -38,7 +38,7 @@ export interface Initializable {
|
|
|
38
38
|
* Interface for reactive subscribable state containers.
|
|
39
39
|
*/
|
|
40
40
|
export interface Subscribable<S> extends Disposable {
|
|
41
|
-
|
|
41
|
+
state: S;
|
|
42
42
|
subscribe(listener: Listener<S>): () => void;
|
|
43
43
|
}
|
|
44
44
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC;IACpC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,CAAE,SAAQ,UAAU;IACjD,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;CAC9C"}
|
|
@@ -19,8 +19,8 @@ export declare abstract class WebStorageCollection<T extends {
|
|
|
19
19
|
private _autoHydrated;
|
|
20
20
|
private get _storage();
|
|
21
21
|
private _ensureHydrated;
|
|
22
|
-
get items():
|
|
23
|
-
get state():
|
|
22
|
+
get items(): T[];
|
|
23
|
+
get state(): T[];
|
|
24
24
|
get length(): number;
|
|
25
25
|
get(id: T['id']): T | undefined;
|
|
26
26
|
has(id: T['id']): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebStorageCollection.d.ts","sourceRoot":"","sources":["../../src/web/WebStorageCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI/D;;;;;;;;;;;GAWG;AACH,8BAAsB,oBAAoB,CACxC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CACjC,SAAQ,oBAAoB,CAAC,CAAC,CAAC;IAC/B,kFAAkF;IAClF,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAW;IAE9C,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,KAAK,QAAQ,GAInB;IAED,OAAO,CAAC,eAAe;IAQvB,IAAI,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"WebStorageCollection.d.ts","sourceRoot":"","sources":["../../src/web/WebStorageCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI/D;;;;;;;;;;;GAWG;AACH,8BAAsB,oBAAoB,CACxC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CACjC,SAAQ,oBAAoB,CAAC,CAAC,CAAC;IAC/B,kFAAkF;IAClF,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAW;IAE9C,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,KAAK,QAAQ,GAInB;IAED,OAAO,CAAC,eAAe;IAQvB,IAAI,KAAK,IAAI,CAAC,EAAE,CAGf;IAED,IAAI,KAAK,IAAI,CAAC,EAAE,CAEf;IAED,IAAI,MAAM,IAAI,MAAM,CAGnB;IAED,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS;IAK/B,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO;IAOzB,SAAS,CAAC,aAAa,IAAI,CAAC,EAAE;IAe9B,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI;IAK3C,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI;IAcvC,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI;IAc9C,SAAS,CAAC,YAAY,IAAI,IAAI;CAG/B"}
|
package/dist/web.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.cjs","sources":["../src/web/WebStorageCollection.ts","../src/web/idb.ts","../src/web/IndexedDBCollection.ts"],"sourcesContent":["import { PersistentCollection } from '../PersistentCollection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n/**\n * PersistentCollection backed by localStorage or sessionStorage.\n * Auto-hydrates on first access (sync storage — no need for manual `hydrate()` call).\n *\n * Uses blob strategy: all items stored as a single JSON string under `storageKey`.\n *\n * ```ts\n * class CartCollection extends WebStorageCollection<CartItem> {\n * protected readonly storageKey = 'cart';\n * }\n * ```\n */\nexport abstract class WebStorageCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** Which Web Storage backend to use. Override to 'session' for sessionStorage. */\n static STORAGE: 'local' | 'session' = 'local';\n\n private _autoHydrated = false;\n\n private get _storage(): Storage {\n return (this.constructor as typeof WebStorageCollection).STORAGE === 'session'\n ? sessionStorage\n : localStorage;\n }\n\n private _ensureHydrated(): void {\n if (this._autoHydrated) return;\n this._autoHydrated = true;\n this._hydrateSync();\n }\n\n // ── Override access points to trigger lazy hydration ──\n\n get items(): readonly T[] {\n this._ensureHydrated();\n return super.items;\n }\n\n get state(): readonly T[] {\n return this.items;\n }\n\n get length(): number {\n this._ensureHydrated();\n return super.length;\n }\n\n get(id: T['id']): T | undefined {\n this._ensureHydrated();\n return super.get(id);\n }\n\n has(id: T['id']): boolean {\n this._ensureHydrated();\n return super.has(id);\n }\n\n // ── Persist interface (blob strategy) ──\n\n protected persistGetAll(): T[] {\n const raw = this._storage.getItem(this.storageKey);\n if (!raw) return [];\n try {\n return this.deserialize(raw);\n } catch {\n if (__DEV__) {\n console.warn(\n `[mvc-kit] Corrupted data in storage key \"${this.storageKey}\". Ignoring stored data.`,\n );\n }\n return [];\n }\n }\n\n protected persistGet(id: T['id']): T | null {\n const all = this.persistGetAll();\n return all.find((i) => i.id === id) ?? null;\n }\n\n protected persistSet(_items: T[]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistRemove(_ids: T['id'][]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistClear(): void {\n this._storage.removeItem(this.storageKey);\n }\n}\n","/**\n * Shared IndexedDB connection manager.\n * Deduplicates indexedDB.open() calls and handles dynamic object store creation\n * by bumping the DB version when a new storageKey is encountered.\n */\n\nconst _connections = new Map<string, IDBDatabase>();\nconst _stores = new Map<string, Set<string>>(); // dbName → known store names\nlet _openQueue: Promise<void> = Promise.resolve(); // Sequential open queue\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n // If we already have a connection with the required store, reuse it\n const existing = _connections.get(dbName);\n if (existing) {\n if (existing.objectStoreNames.contains(storeName)) {\n return Promise.resolve(existing);\n }\n // Need to add a new store — close and reopen with bumped version\n existing.close();\n _connections.delete(dbName);\n }\n\n // Track known stores\n let stores = _stores.get(dbName);\n if (!stores) {\n stores = new Set();\n _stores.set(dbName, stores);\n }\n stores.add(storeName);\n\n // Serialize opens to prevent version conflicts\n const result = _openQueue.then(() => doOpen(dbName, stores!));\n _openQueue = result.then(() => {}, () => {}); // Absorb errors for the queue\n return result;\n}\n\nfunction doOpen(dbName: string, stores: Set<string>): Promise<IDBDatabase> {\n // Close existing cached connection if any (may have been opened by queued op)\n const existingDb = _connections.get(dbName);\n existingDb?.close();\n _connections.delete(dbName);\n\n // Probe current DB version (version-less open never triggers upgrade)\n return new Promise<IDBDatabase>((resolve, reject) => {\n const probe = indexedDB.open(dbName);\n probe.onsuccess = () => {\n const db = probe.result;\n const version = db.version;\n\n // Check if all required stores already exist\n let needsUpgrade = false;\n for (const name of stores) {\n if (!db.objectStoreNames.contains(name)) {\n needsUpgrade = true;\n break;\n }\n }\n\n if (!needsUpgrade) {\n _connections.set(dbName, db);\n resolve(db);\n return;\n }\n\n // Need new stores — close and reopen with bumped version\n db.close();\n const upgrade = indexedDB.open(dbName, version + 1);\n upgrade.onupgradeneeded = () => {\n const udb = upgrade.result;\n for (const name of stores) {\n if (!udb.objectStoreNames.contains(name)) {\n udb.createObjectStore(name, { keyPath: 'id' });\n }\n }\n };\n upgrade.onsuccess = () => {\n _connections.set(dbName, upgrade.result);\n resolve(upgrade.result);\n };\n upgrade.onerror = () => reject(upgrade.error);\n };\n probe.onerror = () => reject(probe.error);\n });\n}\n\nexport function getStore(\n dbName: string,\n storeName: string,\n mode: IDBTransactionMode,\n): Promise<IDBObjectStore> {\n return openDB(dbName, storeName).then((db) => {\n const tx = db.transaction(storeName, mode);\n return tx.objectStore(storeName);\n });\n}\n\nexport function idbGetAll<T>(store: IDBObjectStore): Promise<T[]> {\n return new Promise((resolve, reject) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbGet<T>(store: IDBObjectStore, id: IDBValidKey): Promise<T | null> {\n return new Promise((resolve, reject) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbPut<T>(store: IDBObjectStore, items: T[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const item of items) {\n store.put(item);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbDelete(store: IDBObjectStore, ids: IDBValidKey[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const id of ids) {\n store.delete(id);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbClear(store: IDBObjectStore): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n store.clear();\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\n/** Close all cached connections and delete all known databases. Used in test cleanup. */\nexport function closeAllConnections(): void {\n for (const db of _connections.values()) {\n db.close();\n }\n _connections.clear();\n _stores.clear();\n _openQueue = Promise.resolve();\n}\n\n/** Delete a database by name. Returns a promise that resolves when deleted. */\nexport function deleteDatabase(dbName: string): Promise<void> {\n const existing = _connections.get(dbName);\n existing?.close();\n _connections.delete(dbName);\n _stores.delete(dbName);\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(dbName);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n}\n","import { PersistentCollection } from '../PersistentCollection';\nimport { getStore, idbGetAll, idbGet, idbPut, idbDelete, idbClear } from './idb';\n\n/**\n * PersistentCollection backed by IndexedDB. Stores items individually by `id`\n * in a dedicated object store (named by `storageKey`).\n *\n * **Requires manual `hydrate()` call** (async storage).\n * Typically called in ViewModel's `onInit()`.\n *\n * Uses per-item strategy: each item is stored as a separate entry in the object store.\n * No JSON serialization needed — IndexedDB uses structured cloning.\n *\n * ```ts\n * class MessagesCollection extends IndexedDBCollection<Message> {\n * protected readonly storageKey = 'messages';\n * }\n *\n * // In ViewModel:\n * async onInit() {\n * await this.collection.hydrate();\n * if (this.collection.length === 0) this.load();\n * }\n * ```\n */\nexport abstract class IndexedDBCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** IndexedDB database name. Override to use a separate database. */\n static DB_NAME = 'mvc-kit';\n\n private get _dbName(): string {\n return (this.constructor as typeof IndexedDBCollection).DB_NAME;\n }\n\n private _getStore(mode: IDBTransactionMode) {\n return getStore(this._dbName, this.storageKey, mode);\n }\n\n // ── Persist interface (per-item strategy) ──\n\n protected async persistGetAll(): Promise<T[]> {\n const store = await this._getStore('readonly');\n return idbGetAll<T>(store);\n }\n\n protected async persistGet(id: T['id']): Promise<T | null> {\n const store = await this._getStore('readonly');\n return idbGet<T>(store, id as IDBValidKey);\n }\n\n protected async persistSet(items: T[]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbPut(store, items);\n }\n\n protected async persistRemove(ids: T['id'][]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbDelete(store, ids as IDBValidKey[]);\n }\n\n protected async persistClear(): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbClear(store);\n }\n}\n"],"names":["__DEV__","WebStorageCollection","PersistentCollection","id","raw","i","_items","err","_ids","_connections","_stores","_openQueue","openDB","dbName","storeName","existing","stores","result","doOpen","resolve","reject","probe","db","version","needsUpgrade","name","upgrade","udb","getStore","mode","idbGetAll","store","request","idbGet","idbPut","items","tx","item","idbDelete","ids","idbClear","IndexedDBCollection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web.cjs","sources":["../src/web/WebStorageCollection.ts","../src/web/idb.ts","../src/web/IndexedDBCollection.ts"],"sourcesContent":["import { PersistentCollection } from '../PersistentCollection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n/**\n * PersistentCollection backed by localStorage or sessionStorage.\n * Auto-hydrates on first access (sync storage — no need for manual `hydrate()` call).\n *\n * Uses blob strategy: all items stored as a single JSON string under `storageKey`.\n *\n * ```ts\n * class CartCollection extends WebStorageCollection<CartItem> {\n * protected readonly storageKey = 'cart';\n * }\n * ```\n */\nexport abstract class WebStorageCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** Which Web Storage backend to use. Override to 'session' for sessionStorage. */\n static STORAGE: 'local' | 'session' = 'local';\n\n private _autoHydrated = false;\n\n private get _storage(): Storage {\n return (this.constructor as typeof WebStorageCollection).STORAGE === 'session'\n ? sessionStorage\n : localStorage;\n }\n\n private _ensureHydrated(): void {\n if (this._autoHydrated) return;\n this._autoHydrated = true;\n this._hydrateSync();\n }\n\n // ── Override access points to trigger lazy hydration ──\n\n get items(): T[] {\n this._ensureHydrated();\n return super.items;\n }\n\n get state(): T[] {\n return this.items;\n }\n\n get length(): number {\n this._ensureHydrated();\n return super.length;\n }\n\n get(id: T['id']): T | undefined {\n this._ensureHydrated();\n return super.get(id);\n }\n\n has(id: T['id']): boolean {\n this._ensureHydrated();\n return super.has(id);\n }\n\n // ── Persist interface (blob strategy) ──\n\n protected persistGetAll(): T[] {\n const raw = this._storage.getItem(this.storageKey);\n if (!raw) return [];\n try {\n return this.deserialize(raw);\n } catch {\n if (__DEV__) {\n console.warn(\n `[mvc-kit] Corrupted data in storage key \"${this.storageKey}\". Ignoring stored data.`,\n );\n }\n return [];\n }\n }\n\n protected persistGet(id: T['id']): T | null {\n const all = this.persistGetAll();\n return all.find((i) => i.id === id) ?? null;\n }\n\n protected persistSet(_items: T[]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistRemove(_ids: T['id'][]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistClear(): void {\n this._storage.removeItem(this.storageKey);\n }\n}\n","/**\n * Shared IndexedDB connection manager.\n * Deduplicates indexedDB.open() calls and handles dynamic object store creation\n * by bumping the DB version when a new storageKey is encountered.\n */\n\nconst _connections = new Map<string, IDBDatabase>();\nconst _stores = new Map<string, Set<string>>(); // dbName → known store names\nlet _openQueue: Promise<void> = Promise.resolve(); // Sequential open queue\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n // If we already have a connection with the required store, reuse it\n const existing = _connections.get(dbName);\n if (existing) {\n if (existing.objectStoreNames.contains(storeName)) {\n return Promise.resolve(existing);\n }\n // Need to add a new store — close and reopen with bumped version\n existing.close();\n _connections.delete(dbName);\n }\n\n // Track known stores\n let stores = _stores.get(dbName);\n if (!stores) {\n stores = new Set();\n _stores.set(dbName, stores);\n }\n stores.add(storeName);\n\n // Serialize opens to prevent version conflicts\n const result = _openQueue.then(() => doOpen(dbName, stores!));\n _openQueue = result.then(() => {}, () => {}); // Absorb errors for the queue\n return result;\n}\n\nfunction doOpen(dbName: string, stores: Set<string>): Promise<IDBDatabase> {\n // Close existing cached connection if any (may have been opened by queued op)\n const existingDb = _connections.get(dbName);\n existingDb?.close();\n _connections.delete(dbName);\n\n // Probe current DB version (version-less open never triggers upgrade)\n return new Promise<IDBDatabase>((resolve, reject) => {\n const probe = indexedDB.open(dbName);\n probe.onsuccess = () => {\n const db = probe.result;\n const version = db.version;\n\n // Check if all required stores already exist\n let needsUpgrade = false;\n for (const name of stores) {\n if (!db.objectStoreNames.contains(name)) {\n needsUpgrade = true;\n break;\n }\n }\n\n if (!needsUpgrade) {\n _connections.set(dbName, db);\n resolve(db);\n return;\n }\n\n // Need new stores — close and reopen with bumped version\n db.close();\n const upgrade = indexedDB.open(dbName, version + 1);\n upgrade.onupgradeneeded = () => {\n const udb = upgrade.result;\n for (const name of stores) {\n if (!udb.objectStoreNames.contains(name)) {\n udb.createObjectStore(name, { keyPath: 'id' });\n }\n }\n };\n upgrade.onsuccess = () => {\n _connections.set(dbName, upgrade.result);\n resolve(upgrade.result);\n };\n upgrade.onerror = () => reject(upgrade.error);\n };\n probe.onerror = () => reject(probe.error);\n });\n}\n\nexport function getStore(\n dbName: string,\n storeName: string,\n mode: IDBTransactionMode,\n): Promise<IDBObjectStore> {\n return openDB(dbName, storeName).then((db) => {\n const tx = db.transaction(storeName, mode);\n return tx.objectStore(storeName);\n });\n}\n\nexport function idbGetAll<T>(store: IDBObjectStore): Promise<T[]> {\n return new Promise((resolve, reject) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbGet<T>(store: IDBObjectStore, id: IDBValidKey): Promise<T | null> {\n return new Promise((resolve, reject) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbPut<T>(store: IDBObjectStore, items: T[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const item of items) {\n store.put(item);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbDelete(store: IDBObjectStore, ids: IDBValidKey[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const id of ids) {\n store.delete(id);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbClear(store: IDBObjectStore): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n store.clear();\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\n/** Close all cached connections and delete all known databases. Used in test cleanup. */\nexport function closeAllConnections(): void {\n for (const db of _connections.values()) {\n db.close();\n }\n _connections.clear();\n _stores.clear();\n _openQueue = Promise.resolve();\n}\n\n/** Delete a database by name. Returns a promise that resolves when deleted. */\nexport function deleteDatabase(dbName: string): Promise<void> {\n const existing = _connections.get(dbName);\n existing?.close();\n _connections.delete(dbName);\n _stores.delete(dbName);\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(dbName);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n}\n","import { PersistentCollection } from '../PersistentCollection';\nimport { getStore, idbGetAll, idbGet, idbPut, idbDelete, idbClear } from './idb';\n\n/**\n * PersistentCollection backed by IndexedDB. Stores items individually by `id`\n * in a dedicated object store (named by `storageKey`).\n *\n * **Requires manual `hydrate()` call** (async storage).\n * Typically called in ViewModel's `onInit()`.\n *\n * Uses per-item strategy: each item is stored as a separate entry in the object store.\n * No JSON serialization needed — IndexedDB uses structured cloning.\n *\n * ```ts\n * class MessagesCollection extends IndexedDBCollection<Message> {\n * protected readonly storageKey = 'messages';\n * }\n *\n * // In ViewModel:\n * async onInit() {\n * await this.collection.hydrate();\n * if (this.collection.length === 0) this.load();\n * }\n * ```\n */\nexport abstract class IndexedDBCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** IndexedDB database name. Override to use a separate database. */\n static DB_NAME = 'mvc-kit';\n\n private get _dbName(): string {\n return (this.constructor as typeof IndexedDBCollection).DB_NAME;\n }\n\n private _getStore(mode: IDBTransactionMode) {\n return getStore(this._dbName, this.storageKey, mode);\n }\n\n // ── Persist interface (per-item strategy) ──\n\n protected async persistGetAll(): Promise<T[]> {\n const store = await this._getStore('readonly');\n return idbGetAll<T>(store);\n }\n\n protected async persistGet(id: T['id']): Promise<T | null> {\n const store = await this._getStore('readonly');\n return idbGet<T>(store, id as IDBValidKey);\n }\n\n protected async persistSet(items: T[]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbPut(store, items);\n }\n\n protected async persistRemove(ids: T['id'][]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbDelete(store, ids as IDBValidKey[]);\n }\n\n protected async persistClear(): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbClear(store);\n }\n}\n"],"names":["__DEV__","WebStorageCollection","PersistentCollection","id","raw","i","_items","err","_ids","_connections","_stores","_openQueue","openDB","dbName","storeName","existing","stores","result","doOpen","resolve","reject","probe","db","version","needsUpgrade","name","upgrade","udb","getStore","mode","idbGetAll","store","request","idbGet","idbPut","items","tx","item","idbDelete","ids","idbClear","IndexedDBCollection"],"mappings":"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"}
|
package/dist/web.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as f } from "./PersistentCollection-
|
|
1
|
+
import { P as f } from "./PersistentCollection-CbYqzFHc.js";
|
|
2
2
|
const l = typeof __MVC_KIT_DEV__ < "u" && __MVC_KIT_DEV__;
|
|
3
3
|
class b extends f {
|
|
4
4
|
/** Which Web Storage backend to use. Override to 'session' for sessionStorage. */
|
package/dist/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sources":["../src/web/WebStorageCollection.ts","../src/web/idb.ts","../src/web/IndexedDBCollection.ts"],"sourcesContent":["import { PersistentCollection } from '../PersistentCollection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n/**\n * PersistentCollection backed by localStorage or sessionStorage.\n * Auto-hydrates on first access (sync storage — no need for manual `hydrate()` call).\n *\n * Uses blob strategy: all items stored as a single JSON string under `storageKey`.\n *\n * ```ts\n * class CartCollection extends WebStorageCollection<CartItem> {\n * protected readonly storageKey = 'cart';\n * }\n * ```\n */\nexport abstract class WebStorageCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** Which Web Storage backend to use. Override to 'session' for sessionStorage. */\n static STORAGE: 'local' | 'session' = 'local';\n\n private _autoHydrated = false;\n\n private get _storage(): Storage {\n return (this.constructor as typeof WebStorageCollection).STORAGE === 'session'\n ? sessionStorage\n : localStorage;\n }\n\n private _ensureHydrated(): void {\n if (this._autoHydrated) return;\n this._autoHydrated = true;\n this._hydrateSync();\n }\n\n // ── Override access points to trigger lazy hydration ──\n\n get items(): readonly T[] {\n this._ensureHydrated();\n return super.items;\n }\n\n get state(): readonly T[] {\n return this.items;\n }\n\n get length(): number {\n this._ensureHydrated();\n return super.length;\n }\n\n get(id: T['id']): T | undefined {\n this._ensureHydrated();\n return super.get(id);\n }\n\n has(id: T['id']): boolean {\n this._ensureHydrated();\n return super.has(id);\n }\n\n // ── Persist interface (blob strategy) ──\n\n protected persistGetAll(): T[] {\n const raw = this._storage.getItem(this.storageKey);\n if (!raw) return [];\n try {\n return this.deserialize(raw);\n } catch {\n if (__DEV__) {\n console.warn(\n `[mvc-kit] Corrupted data in storage key \"${this.storageKey}\". Ignoring stored data.`,\n );\n }\n return [];\n }\n }\n\n protected persistGet(id: T['id']): T | null {\n const all = this.persistGetAll();\n return all.find((i) => i.id === id) ?? null;\n }\n\n protected persistSet(_items: T[]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistRemove(_ids: T['id'][]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistClear(): void {\n this._storage.removeItem(this.storageKey);\n }\n}\n","/**\n * Shared IndexedDB connection manager.\n * Deduplicates indexedDB.open() calls and handles dynamic object store creation\n * by bumping the DB version when a new storageKey is encountered.\n */\n\nconst _connections = new Map<string, IDBDatabase>();\nconst _stores = new Map<string, Set<string>>(); // dbName → known store names\nlet _openQueue: Promise<void> = Promise.resolve(); // Sequential open queue\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n // If we already have a connection with the required store, reuse it\n const existing = _connections.get(dbName);\n if (existing) {\n if (existing.objectStoreNames.contains(storeName)) {\n return Promise.resolve(existing);\n }\n // Need to add a new store — close and reopen with bumped version\n existing.close();\n _connections.delete(dbName);\n }\n\n // Track known stores\n let stores = _stores.get(dbName);\n if (!stores) {\n stores = new Set();\n _stores.set(dbName, stores);\n }\n stores.add(storeName);\n\n // Serialize opens to prevent version conflicts\n const result = _openQueue.then(() => doOpen(dbName, stores!));\n _openQueue = result.then(() => {}, () => {}); // Absorb errors for the queue\n return result;\n}\n\nfunction doOpen(dbName: string, stores: Set<string>): Promise<IDBDatabase> {\n // Close existing cached connection if any (may have been opened by queued op)\n const existingDb = _connections.get(dbName);\n existingDb?.close();\n _connections.delete(dbName);\n\n // Probe current DB version (version-less open never triggers upgrade)\n return new Promise<IDBDatabase>((resolve, reject) => {\n const probe = indexedDB.open(dbName);\n probe.onsuccess = () => {\n const db = probe.result;\n const version = db.version;\n\n // Check if all required stores already exist\n let needsUpgrade = false;\n for (const name of stores) {\n if (!db.objectStoreNames.contains(name)) {\n needsUpgrade = true;\n break;\n }\n }\n\n if (!needsUpgrade) {\n _connections.set(dbName, db);\n resolve(db);\n return;\n }\n\n // Need new stores — close and reopen with bumped version\n db.close();\n const upgrade = indexedDB.open(dbName, version + 1);\n upgrade.onupgradeneeded = () => {\n const udb = upgrade.result;\n for (const name of stores) {\n if (!udb.objectStoreNames.contains(name)) {\n udb.createObjectStore(name, { keyPath: 'id' });\n }\n }\n };\n upgrade.onsuccess = () => {\n _connections.set(dbName, upgrade.result);\n resolve(upgrade.result);\n };\n upgrade.onerror = () => reject(upgrade.error);\n };\n probe.onerror = () => reject(probe.error);\n });\n}\n\nexport function getStore(\n dbName: string,\n storeName: string,\n mode: IDBTransactionMode,\n): Promise<IDBObjectStore> {\n return openDB(dbName, storeName).then((db) => {\n const tx = db.transaction(storeName, mode);\n return tx.objectStore(storeName);\n });\n}\n\nexport function idbGetAll<T>(store: IDBObjectStore): Promise<T[]> {\n return new Promise((resolve, reject) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbGet<T>(store: IDBObjectStore, id: IDBValidKey): Promise<T | null> {\n return new Promise((resolve, reject) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbPut<T>(store: IDBObjectStore, items: T[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const item of items) {\n store.put(item);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbDelete(store: IDBObjectStore, ids: IDBValidKey[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const id of ids) {\n store.delete(id);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbClear(store: IDBObjectStore): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n store.clear();\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\n/** Close all cached connections and delete all known databases. Used in test cleanup. */\nexport function closeAllConnections(): void {\n for (const db of _connections.values()) {\n db.close();\n }\n _connections.clear();\n _stores.clear();\n _openQueue = Promise.resolve();\n}\n\n/** Delete a database by name. Returns a promise that resolves when deleted. */\nexport function deleteDatabase(dbName: string): Promise<void> {\n const existing = _connections.get(dbName);\n existing?.close();\n _connections.delete(dbName);\n _stores.delete(dbName);\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(dbName);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n}\n","import { PersistentCollection } from '../PersistentCollection';\nimport { getStore, idbGetAll, idbGet, idbPut, idbDelete, idbClear } from './idb';\n\n/**\n * PersistentCollection backed by IndexedDB. Stores items individually by `id`\n * in a dedicated object store (named by `storageKey`).\n *\n * **Requires manual `hydrate()` call** (async storage).\n * Typically called in ViewModel's `onInit()`.\n *\n * Uses per-item strategy: each item is stored as a separate entry in the object store.\n * No JSON serialization needed — IndexedDB uses structured cloning.\n *\n * ```ts\n * class MessagesCollection extends IndexedDBCollection<Message> {\n * protected readonly storageKey = 'messages';\n * }\n *\n * // In ViewModel:\n * async onInit() {\n * await this.collection.hydrate();\n * if (this.collection.length === 0) this.load();\n * }\n * ```\n */\nexport abstract class IndexedDBCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** IndexedDB database name. Override to use a separate database. */\n static DB_NAME = 'mvc-kit';\n\n private get _dbName(): string {\n return (this.constructor as typeof IndexedDBCollection).DB_NAME;\n }\n\n private _getStore(mode: IDBTransactionMode) {\n return getStore(this._dbName, this.storageKey, mode);\n }\n\n // ── Persist interface (per-item strategy) ──\n\n protected async persistGetAll(): Promise<T[]> {\n const store = await this._getStore('readonly');\n return idbGetAll<T>(store);\n }\n\n protected async persistGet(id: T['id']): Promise<T | null> {\n const store = await this._getStore('readonly');\n return idbGet<T>(store, id as IDBValidKey);\n }\n\n protected async persistSet(items: T[]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbPut(store, items);\n }\n\n protected async persistRemove(ids: T['id'][]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbDelete(store, ids as IDBValidKey[]);\n }\n\n protected async persistClear(): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbClear(store);\n }\n}\n"],"names":["__DEV__","WebStorageCollection","PersistentCollection","id","raw","i","_items","err","_ids","_connections","_stores","_openQueue","openDB","dbName","storeName","existing","stores","result","doOpen","resolve","reject","probe","db","version","needsUpgrade","name","upgrade","udb","getStore","mode","idbGetAll","store","request","idbGet","idbPut","items","tx","item","idbDelete","ids","idbClear","IndexedDBCollection"],"mappings":";AAEA,MAAMA,IAAU,OAAO,kBAAoB,OAAe;AAcnD,MAAeC,UAEZC,EAAwB;AAAA;AAAA,EAEhC,OAAO,UAA+B;AAAA,EAE9B,gBAAgB;AAAA,EAExB,IAAY,WAAoB;AAC9B,WAAQ,KAAK,YAA4C,YAAY,YACjE,iBACA;AAAA,EACN;AAAA,EAEQ,kBAAwB;AAC9B,IAAI,KAAK,kBACT,KAAK,gBAAgB,IACrB,KAAK,aAAA;AAAA,EACP;AAAA;AAAA,EAIA,IAAI,QAAsB;AACxB,gBAAK,gBAAA,GACE,MAAM;AAAA,EACf;AAAA,EAEA,IAAI,QAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAiB;AACnB,gBAAK,gBAAA,GACE,MAAM;AAAA,EACf;AAAA,EAEA,IAAIC,GAA4B;AAC9B,gBAAK,gBAAA,GACE,MAAM,IAAIA,CAAE;AAAA,EACrB;AAAA,EAEA,IAAIA,GAAsB;AACxB,gBAAK,gBAAA,GACE,MAAM,IAAIA,CAAE;AAAA,EACrB;AAAA;AAAA,EAIU,gBAAqB;AAC7B,UAAMC,IAAM,KAAK,SAAS,QAAQ,KAAK,UAAU;AACjD,QAAI,CAACA,EAAK,QAAO,CAAA;AACjB,QAAI;AACF,aAAO,KAAK,YAAYA,CAAG;AAAA,IAC7B,QAAQ;AACN,aAAIJ,KACF,QAAQ;AAAA,QACN,4CAA4C,KAAK,UAAU;AAAA,MAAA,GAGxD,CAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEU,WAAWG,GAAuB;AAE1C,WADY,KAAK,cAAA,EACN,KAAK,CAACE,MAAMA,EAAE,OAAOF,CAAE,KAAK;AAAA,EACzC;AAAA,EAEU,WAAWG,GAAmB;AACtC,QAAI;AACF,WAAK,SAAS,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAAA,IACxE,SAASC,GAAK;AACZ,YAAIP,KAAWO,aAAe,gBAAgBA,EAAI,SAAS,wBACzD,QAAQ;AAAA,QACN,4CAA4C,KAAK,UAAU;AAAA,MAAA,GAIzDA;AAAA,IACR;AAAA,EACF;AAAA,EAEU,cAAcC,GAAuB;AAC7C,QAAI;AACF,WAAK,SAAS,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAAA,IACxE,SAASD,GAAK;AACZ,YAAIP,KAAWO,aAAe,gBAAgBA,EAAI,SAAS,wBACzD,QAAQ;AAAA,QACN,4CAA4C,KAAK,UAAU;AAAA,MAAA,GAIzDA;AAAA,IACR;AAAA,EACF;AAAA,EAEU,eAAqB;AAC7B,SAAK,SAAS,WAAW,KAAK,UAAU;AAAA,EAC1C;AACF;AC7GA,MAAME,wBAAmB,IAAA,GACnBC,wBAAc,IAAA;AACpB,IAAIC,IAA4B,QAAQ,QAAA;AAExC,SAASC,EAAOC,GAAgBC,GAAyC;AAEvE,QAAMC,IAAWN,EAAa,IAAII,CAAM;AACxC,MAAIE,GAAU;AACZ,QAAIA,EAAS,iBAAiB,SAASD,CAAS;AAC9C,aAAO,QAAQ,QAAQC,CAAQ;AAGjC,IAAAA,EAAS,MAAA,GACTN,EAAa,OAAOI,CAAM;AAAA,EAC5B;AAGA,MAAIG,IAASN,EAAQ,IAAIG,CAAM;AAC/B,EAAKG,MACHA,wBAAa,IAAA,GACbN,EAAQ,IAAIG,GAAQG,CAAM,IAE5BA,EAAO,IAAIF,CAAS;AAGpB,QAAMG,IAASN,EAAW,KAAK,MAAMO,EAAOL,GAAQG,CAAO,CAAC;AAC5D,SAAAL,IAAaM,EAAO,KAAK,MAAM;AAAA,EAAC,GAAG,MAAM;AAAA,EAAC,CAAC,GACpCA;AACT;AAEA,SAASC,EAAOL,GAAgBG,GAA2C;AAGzE,SADmBP,EAAa,IAAII,CAAM,GAC9B,MAAA,GACZJ,EAAa,OAAOI,CAAM,GAGnB,IAAI,QAAqB,CAACM,GAASC,MAAW;AACnD,UAAMC,IAAQ,UAAU,KAAKR,CAAM;AACnC,IAAAQ,EAAM,YAAY,MAAM;AACtB,YAAMC,IAAKD,EAAM,QACXE,IAAUD,EAAG;AAGnB,UAAIE,IAAe;AACnB,iBAAWC,KAAQT;AACjB,YAAI,CAACM,EAAG,iBAAiB,SAASG,CAAI,GAAG;AACvC,UAAAD,IAAe;AACf;AAAA,QACF;AAGF,UAAI,CAACA,GAAc;AACjB,QAAAf,EAAa,IAAII,GAAQS,CAAE,GAC3BH,EAAQG,CAAE;AACV;AAAA,MACF;AAGA,MAAAA,EAAG,MAAA;AACH,YAAMI,IAAU,UAAU,KAAKb,GAAQU,IAAU,CAAC;AAClD,MAAAG,EAAQ,kBAAkB,MAAM;AAC9B,cAAMC,IAAMD,EAAQ;AACpB,mBAAWD,KAAQT;AACjB,UAAKW,EAAI,iBAAiB,SAASF,CAAI,KACrCE,EAAI,kBAAkBF,GAAM,EAAE,SAAS,MAAM;AAAA,MAGnD,GACAC,EAAQ,YAAY,MAAM;AACxB,QAAAjB,EAAa,IAAII,GAAQa,EAAQ,MAAM,GACvCP,EAAQO,EAAQ,MAAM;AAAA,MACxB,GACAA,EAAQ,UAAU,MAAMN,EAAOM,EAAQ,KAAK;AAAA,IAC9C,GACAL,EAAM,UAAU,MAAMD,EAAOC,EAAM,KAAK;AAAA,EAC1C,CAAC;AACH;AAEO,SAASO,EACdf,GACAC,GACAe,GACyB;AACzB,SAAOjB,EAAOC,GAAQC,CAAS,EAAE,KAAK,CAACQ,MAC1BA,EAAG,YAAYR,GAAWe,CAAI,EAC/B,YAAYf,CAAS,CAChC;AACH;AAEO,SAASgB,EAAaC,GAAqC;AAChE,SAAO,IAAI,QAAQ,CAACZ,GAASC,MAAW;AACtC,UAAMY,IAAUD,EAAM,OAAA;AACtB,IAAAC,EAAQ,YAAY,MAAMb,EAAQa,EAAQ,MAAM,GAChDA,EAAQ,UAAU,MAAMZ,EAAOY,EAAQ,KAAK;AAAA,EAC9C,CAAC;AACH;AAEO,SAASC,EAAUF,GAAuB5B,GAAoC;AACnF,SAAO,IAAI,QAAQ,CAACgB,GAASC,MAAW;AACtC,UAAMY,IAAUD,EAAM,IAAI5B,CAAE;AAC5B,IAAA6B,EAAQ,YAAY,MAAMb,EAAQa,EAAQ,UAAU,IAAI,GACxDA,EAAQ,UAAU,MAAMZ,EAAOY,EAAQ,KAAK;AAAA,EAC9C,CAAC;AACH;AAEO,SAASE,EAAUH,GAAuBI,GAA2B;AAC1E,SAAO,IAAI,QAAQ,CAAChB,GAASC,MAAW;AACtC,UAAMgB,IAAKL,EAAM;AACjB,eAAWM,KAAQF;AACjB,MAAAJ,EAAM,IAAIM,CAAI;AAEhB,IAAAD,EAAG,aAAa,MAAMjB,EAAA,GACtBiB,EAAG,UAAU,MAAMhB,EAAOgB,EAAG,KAAK;AAAA,EACpC,CAAC;AACH;AAEO,SAASE,EAAUP,GAAuBQ,GAAmC;AAClF,SAAO,IAAI,QAAQ,CAACpB,GAASC,MAAW;AACtC,UAAMgB,IAAKL,EAAM;AACjB,eAAW5B,KAAMoC;AACf,MAAAR,EAAM,OAAO5B,CAAE;AAEjB,IAAAiC,EAAG,aAAa,MAAMjB,EAAA,GACtBiB,EAAG,UAAU,MAAMhB,EAAOgB,EAAG,KAAK;AAAA,EACpC,CAAC;AACH;AAEO,SAASI,EAAST,GAAsC;AAC7D,SAAO,IAAI,QAAQ,CAACZ,GAASC,MAAW;AACtC,UAAMgB,IAAKL,EAAM;AACjB,IAAAA,EAAM,MAAA,GACNK,EAAG,aAAa,MAAMjB,EAAA,GACtBiB,EAAG,UAAU,MAAMhB,EAAOgB,EAAG,KAAK;AAAA,EACpC,CAAC;AACH;ACpHO,MAAeK,UAEZvC,EAAwB;AAAA;AAAA,EAEhC,OAAO,UAAU;AAAA,EAEjB,IAAY,UAAkB;AAC5B,WAAQ,KAAK,YAA2C;AAAA,EAC1D;AAAA,EAEQ,UAAU2B,GAA0B;AAC1C,WAAOD,EAAS,KAAK,SAAS,KAAK,YAAYC,CAAI;AAAA,EACrD;AAAA;AAAA,EAIA,MAAgB,gBAA8B;AAC5C,UAAME,IAAQ,MAAM,KAAK,UAAU,UAAU;AAC7C,WAAOD,EAAaC,CAAK;AAAA,EAC3B;AAAA,EAEA,MAAgB,WAAW5B,GAAgC;AACzD,UAAM4B,IAAQ,MAAM,KAAK,UAAU,UAAU;AAC7C,WAAOE,EAAUF,GAAO5B,CAAiB;AAAA,EAC3C;AAAA,EAEA,MAAgB,WAAWgC,GAA2B;AACpD,UAAMJ,IAAQ,MAAM,KAAK,UAAU,WAAW;AAC9C,WAAOG,EAAOH,GAAOI,CAAK;AAAA,EAC5B;AAAA,EAEA,MAAgB,cAAcI,GAA+B;AAC3D,UAAMR,IAAQ,MAAM,KAAK,UAAU,WAAW;AAC9C,WAAOO,EAAUP,GAAOQ,CAAoB;AAAA,EAC9C;AAAA,EAEA,MAAgB,eAA8B;AAC5C,UAAMR,IAAQ,MAAM,KAAK,UAAU,WAAW;AAC9C,WAAOS,EAAST,CAAK;AAAA,EACvB;AACF;"}
|
|
1
|
+
{"version":3,"file":"web.js","sources":["../src/web/WebStorageCollection.ts","../src/web/idb.ts","../src/web/IndexedDBCollection.ts"],"sourcesContent":["import { PersistentCollection } from '../PersistentCollection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n/**\n * PersistentCollection backed by localStorage or sessionStorage.\n * Auto-hydrates on first access (sync storage — no need for manual `hydrate()` call).\n *\n * Uses blob strategy: all items stored as a single JSON string under `storageKey`.\n *\n * ```ts\n * class CartCollection extends WebStorageCollection<CartItem> {\n * protected readonly storageKey = 'cart';\n * }\n * ```\n */\nexport abstract class WebStorageCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** Which Web Storage backend to use. Override to 'session' for sessionStorage. */\n static STORAGE: 'local' | 'session' = 'local';\n\n private _autoHydrated = false;\n\n private get _storage(): Storage {\n return (this.constructor as typeof WebStorageCollection).STORAGE === 'session'\n ? sessionStorage\n : localStorage;\n }\n\n private _ensureHydrated(): void {\n if (this._autoHydrated) return;\n this._autoHydrated = true;\n this._hydrateSync();\n }\n\n // ── Override access points to trigger lazy hydration ──\n\n get items(): T[] {\n this._ensureHydrated();\n return super.items;\n }\n\n get state(): T[] {\n return this.items;\n }\n\n get length(): number {\n this._ensureHydrated();\n return super.length;\n }\n\n get(id: T['id']): T | undefined {\n this._ensureHydrated();\n return super.get(id);\n }\n\n has(id: T['id']): boolean {\n this._ensureHydrated();\n return super.has(id);\n }\n\n // ── Persist interface (blob strategy) ──\n\n protected persistGetAll(): T[] {\n const raw = this._storage.getItem(this.storageKey);\n if (!raw) return [];\n try {\n return this.deserialize(raw);\n } catch {\n if (__DEV__) {\n console.warn(\n `[mvc-kit] Corrupted data in storage key \"${this.storageKey}\". Ignoring stored data.`,\n );\n }\n return [];\n }\n }\n\n protected persistGet(id: T['id']): T | null {\n const all = this.persistGetAll();\n return all.find((i) => i.id === id) ?? null;\n }\n\n protected persistSet(_items: T[]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistRemove(_ids: T['id'][]): void {\n try {\n this._storage.setItem(this.storageKey, this.serialize([...this.items]));\n } catch (err) {\n if (__DEV__ && err instanceof DOMException && err.name === 'QuotaExceededError') {\n console.warn(\n `[mvc-kit] QuotaExceededError writing to \"${this.storageKey}\". ` +\n `Consider using IndexedDBCollection for larger datasets.`,\n );\n }\n throw err;\n }\n }\n\n protected persistClear(): void {\n this._storage.removeItem(this.storageKey);\n }\n}\n","/**\n * Shared IndexedDB connection manager.\n * Deduplicates indexedDB.open() calls and handles dynamic object store creation\n * by bumping the DB version when a new storageKey is encountered.\n */\n\nconst _connections = new Map<string, IDBDatabase>();\nconst _stores = new Map<string, Set<string>>(); // dbName → known store names\nlet _openQueue: Promise<void> = Promise.resolve(); // Sequential open queue\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n // If we already have a connection with the required store, reuse it\n const existing = _connections.get(dbName);\n if (existing) {\n if (existing.objectStoreNames.contains(storeName)) {\n return Promise.resolve(existing);\n }\n // Need to add a new store — close and reopen with bumped version\n existing.close();\n _connections.delete(dbName);\n }\n\n // Track known stores\n let stores = _stores.get(dbName);\n if (!stores) {\n stores = new Set();\n _stores.set(dbName, stores);\n }\n stores.add(storeName);\n\n // Serialize opens to prevent version conflicts\n const result = _openQueue.then(() => doOpen(dbName, stores!));\n _openQueue = result.then(() => {}, () => {}); // Absorb errors for the queue\n return result;\n}\n\nfunction doOpen(dbName: string, stores: Set<string>): Promise<IDBDatabase> {\n // Close existing cached connection if any (may have been opened by queued op)\n const existingDb = _connections.get(dbName);\n existingDb?.close();\n _connections.delete(dbName);\n\n // Probe current DB version (version-less open never triggers upgrade)\n return new Promise<IDBDatabase>((resolve, reject) => {\n const probe = indexedDB.open(dbName);\n probe.onsuccess = () => {\n const db = probe.result;\n const version = db.version;\n\n // Check if all required stores already exist\n let needsUpgrade = false;\n for (const name of stores) {\n if (!db.objectStoreNames.contains(name)) {\n needsUpgrade = true;\n break;\n }\n }\n\n if (!needsUpgrade) {\n _connections.set(dbName, db);\n resolve(db);\n return;\n }\n\n // Need new stores — close and reopen with bumped version\n db.close();\n const upgrade = indexedDB.open(dbName, version + 1);\n upgrade.onupgradeneeded = () => {\n const udb = upgrade.result;\n for (const name of stores) {\n if (!udb.objectStoreNames.contains(name)) {\n udb.createObjectStore(name, { keyPath: 'id' });\n }\n }\n };\n upgrade.onsuccess = () => {\n _connections.set(dbName, upgrade.result);\n resolve(upgrade.result);\n };\n upgrade.onerror = () => reject(upgrade.error);\n };\n probe.onerror = () => reject(probe.error);\n });\n}\n\nexport function getStore(\n dbName: string,\n storeName: string,\n mode: IDBTransactionMode,\n): Promise<IDBObjectStore> {\n return openDB(dbName, storeName).then((db) => {\n const tx = db.transaction(storeName, mode);\n return tx.objectStore(storeName);\n });\n}\n\nexport function idbGetAll<T>(store: IDBObjectStore): Promise<T[]> {\n return new Promise((resolve, reject) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbGet<T>(store: IDBObjectStore, id: IDBValidKey): Promise<T | null> {\n return new Promise((resolve, reject) => {\n const request = store.get(id);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport function idbPut<T>(store: IDBObjectStore, items: T[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const item of items) {\n store.put(item);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbDelete(store: IDBObjectStore, ids: IDBValidKey[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n for (const id of ids) {\n store.delete(id);\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nexport function idbClear(store: IDBObjectStore): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = store.transaction;\n store.clear();\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\n/** Close all cached connections and delete all known databases. Used in test cleanup. */\nexport function closeAllConnections(): void {\n for (const db of _connections.values()) {\n db.close();\n }\n _connections.clear();\n _stores.clear();\n _openQueue = Promise.resolve();\n}\n\n/** Delete a database by name. Returns a promise that resolves when deleted. */\nexport function deleteDatabase(dbName: string): Promise<void> {\n const existing = _connections.get(dbName);\n existing?.close();\n _connections.delete(dbName);\n _stores.delete(dbName);\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(dbName);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n}\n","import { PersistentCollection } from '../PersistentCollection';\nimport { getStore, idbGetAll, idbGet, idbPut, idbDelete, idbClear } from './idb';\n\n/**\n * PersistentCollection backed by IndexedDB. Stores items individually by `id`\n * in a dedicated object store (named by `storageKey`).\n *\n * **Requires manual `hydrate()` call** (async storage).\n * Typically called in ViewModel's `onInit()`.\n *\n * Uses per-item strategy: each item is stored as a separate entry in the object store.\n * No JSON serialization needed — IndexedDB uses structured cloning.\n *\n * ```ts\n * class MessagesCollection extends IndexedDBCollection<Message> {\n * protected readonly storageKey = 'messages';\n * }\n *\n * // In ViewModel:\n * async onInit() {\n * await this.collection.hydrate();\n * if (this.collection.length === 0) this.load();\n * }\n * ```\n */\nexport abstract class IndexedDBCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /** IndexedDB database name. Override to use a separate database. */\n static DB_NAME = 'mvc-kit';\n\n private get _dbName(): string {\n return (this.constructor as typeof IndexedDBCollection).DB_NAME;\n }\n\n private _getStore(mode: IDBTransactionMode) {\n return getStore(this._dbName, this.storageKey, mode);\n }\n\n // ── Persist interface (per-item strategy) ──\n\n protected async persistGetAll(): Promise<T[]> {\n const store = await this._getStore('readonly');\n return idbGetAll<T>(store);\n }\n\n protected async persistGet(id: T['id']): Promise<T | null> {\n const store = await this._getStore('readonly');\n return idbGet<T>(store, id as IDBValidKey);\n }\n\n protected async persistSet(items: T[]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbPut(store, items);\n }\n\n protected async persistRemove(ids: T['id'][]): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbDelete(store, ids as IDBValidKey[]);\n }\n\n protected async persistClear(): Promise<void> {\n const store = await this._getStore('readwrite');\n return idbClear(store);\n }\n}\n"],"names":["__DEV__","WebStorageCollection","PersistentCollection","id","raw","i","_items","err","_ids","_connections","_stores","_openQueue","openDB","dbName","storeName","existing","stores","result","doOpen","resolve","reject","probe","db","version","needsUpgrade","name","upgrade","udb","getStore","mode","idbGetAll","store","request","idbGet","idbPut","items","tx","item","idbDelete","ids","idbClear","IndexedDBCollection"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PersistentCollection-BFrgskju.js","sources":["../src/Collection.ts","../src/PersistentCollection.ts"],"sourcesContent":["import type { Listener, Subscribable } from './types';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\ntype CollectionState<T> = readonly T[];\ntype CollectionListener<T> = Listener<CollectionState<T>>;\n\n/**\n * Reactive typed array with CRUD and query methods.\n */\nexport class Collection<T extends { id: string | number }> implements Subscribable<CollectionState<T>> {\n /** Maximum number of items before FIFO eviction. 0 = unlimited. */\n static MAX_SIZE = 0;\n /** Time-to-live in milliseconds. 0 = no expiry. */\n static TTL = 0;\n\n private _items: readonly T[] = [];\n private _disposed = false;\n private _listeners = new Set<CollectionListener<T>>();\n private _index = new Map<T['id'], T>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _timestamps: Map<T['id'], number> | null = null;\n private _evictionTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(initialItems: T[] = []) {\n let result = [...initialItems];\n\n if (this._ttl > 0) {\n this._timestamps = new Map();\n const now = Date.now();\n for (const item of result) {\n this._timestamps.set(item.id, now);\n }\n }\n\n if (this._maxSize > 0 && result.length > this._maxSize) {\n // FIFO: trim from the front (oldest items)\n const excess = result.length - this._maxSize;\n const evicted = result.slice(0, excess);\n result = result.slice(excess);\n for (const item of evicted) {\n this._timestamps?.delete(item.id);\n }\n }\n\n this._items = Object.freeze(result);\n this.rebuildIndex();\n this._scheduleEvictionTimer();\n }\n\n /**\n * Alias for Subscribable compatibility.\n */\n get state(): readonly T[] {\n return this._items;\n }\n\n /** The raw readonly array of items. */\n get items(): readonly T[] {\n return this._items;\n }\n\n /** Number of items in the collection. */\n get length(): number {\n return this._items.length;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n // ── Config Accessors ──\n\n private get _maxSize(): number {\n return (this.constructor as typeof Collection).MAX_SIZE;\n }\n\n private get _ttl(): number {\n return (this.constructor as typeof Collection).TTL;\n }\n\n // ── CRUD Methods (notify listeners) ──\n\n /**\n * Add one or more items. Items with existing IDs are silently skipped.\n */\n add(...items: T[]): void {\n if (this._disposed) {\n throw new Error('Cannot add to disposed Collection');\n }\n\n if (items.length === 0) {\n return;\n }\n\n const seen = new Set<T['id']>();\n const newItems: T[] = [];\n for (const item of items) {\n if (!this._index.has(item.id) && !seen.has(item.id)) {\n newItems.push(item);\n seen.add(item.id);\n }\n }\n if (newItems.length === 0) return;\n\n const prev = this._items;\n let result = [...prev, ...newItems];\n\n for (const item of newItems) {\n this._index.set(item.id, item);\n }\n\n // Record timestamps for TTL\n if (this._timestamps) {\n const now = Date.now();\n for (const item of newItems) {\n this._timestamps.set(item.id, now);\n }\n }\n\n // Enforce capacity before freeze/notify\n if (this._maxSize > 0 && result.length > this._maxSize) {\n result = this._evictForCapacity(result);\n }\n\n this._items = Object.freeze(result);\n this.notify(prev);\n this._scheduleEvictionTimer();\n }\n\n /**\n * Add or replace items by ID. Existing items are replaced in-place\n * (preserving array position); new items are appended. Deduplicates\n * input — last occurrence wins. No-op if nothing changed (reference\n * comparison).\n */\n upsert(...items: T[]): void {\n if (this._disposed) {\n throw new Error('Cannot upsert on disposed Collection');\n }\n if (items.length === 0) return;\n\n // Deduplicate input — last occurrence wins\n const incoming = new Map<T['id'], T>();\n for (const item of items) {\n incoming.set(item.id, item);\n }\n\n const prev = this._items;\n let changed = false;\n const replaced = new Set<T['id']>();\n const newArray: T[] = [];\n\n // Replace existing items in-place\n for (const existing of prev) {\n if (incoming.has(existing.id)) {\n const replacement = incoming.get(existing.id)!;\n if (replacement !== existing) changed = true;\n newArray.push(replacement);\n replaced.add(existing.id);\n } else {\n newArray.push(existing);\n }\n }\n\n // Append genuinely new items\n for (const [id, item] of incoming) {\n if (!replaced.has(id)) {\n newArray.push(item);\n changed = true;\n }\n }\n\n if (!changed) return;\n\n // Record/refresh timestamps for TTL (upsert refreshes existing)\n if (this._timestamps) {\n const now = Date.now();\n for (const [id] of incoming) {\n this._timestamps.set(id, now);\n }\n }\n\n for (const [id, item] of incoming) {\n this._index.set(id, item);\n }\n\n // Enforce capacity before freeze/notify\n let result = newArray;\n if (this._maxSize > 0 && result.length > this._maxSize) {\n result = this._evictForCapacity(result);\n }\n\n this._items = Object.freeze(result);\n this.notify(prev);\n this._scheduleEvictionTimer();\n }\n\n /**\n * Remove items by id(s).\n */\n remove(...ids: T['id'][]): void {\n if (this._disposed) {\n throw new Error('Cannot remove from disposed Collection');\n }\n\n if (ids.length === 0) {\n return;\n }\n\n const idSet = new Set(ids);\n const filtered = this._items.filter(item => !idSet.has(item.id));\n\n if (filtered.length === this._items.length) {\n return; // No items removed\n }\n\n const prev = this._items;\n this._items = Object.freeze(filtered);\n\n for (const id of ids) {\n this._index.delete(id);\n this._timestamps?.delete(id);\n }\n\n this.notify(prev);\n this._scheduleEvictionTimer();\n }\n\n /**\n * Update an item by id with partial changes.\n */\n update(id: T['id'], changes: Partial<T>): void {\n if (this._disposed) {\n throw new Error('Cannot update disposed Collection');\n }\n\n const idx = this._items.findIndex(item => item.id === id);\n if (idx === -1) {\n return;\n }\n\n const existing = this._items[idx];\n const updated = { ...existing, ...changes, id }; // Ensure id is preserved\n\n // Check if anything actually changed\n const keys = Object.keys(changes) as (keyof T)[];\n const hasChanges = keys.some(key => changes[key] !== existing[key]);\n if (!hasChanges) {\n return;\n }\n\n const prev = this._items;\n const newItems = [...prev];\n newItems[idx] = updated;\n this._items = Object.freeze(newItems);\n this._index.set(id, updated);\n\n this.notify(prev);\n }\n\n /**\n * Replace all items.\n */\n reset(items: T[]): void {\n if (this._disposed) {\n throw new Error('Cannot reset disposed Collection');\n }\n\n const prev = this._items;\n\n // Record timestamps for TTL\n if (this._timestamps) {\n this._timestamps.clear();\n const now = Date.now();\n for (const item of items) {\n this._timestamps.set(item.id, now);\n }\n }\n\n let result = [...items];\n\n // Enforce capacity before freeze/notify\n if (this._maxSize > 0 && result.length > this._maxSize) {\n result = this._evictForCapacity(result);\n }\n\n this._items = Object.freeze(result);\n this.rebuildIndex();\n\n this.notify(prev);\n this._scheduleEvictionTimer();\n }\n\n /**\n * Remove all items.\n */\n clear(): void {\n if (this._disposed) {\n throw new Error('Cannot clear disposed Collection');\n }\n\n if (this._items.length === 0) {\n return;\n }\n\n const prev = this._items;\n this._items = Object.freeze([]);\n this._index.clear();\n this._timestamps?.clear();\n this._clearEvictionTimer();\n\n this.notify(prev);\n }\n\n /**\n * Snapshot current state, apply callback mutations, and return a rollback function.\n * Rollback restores items to pre-callback state regardless of later mutations.\n */\n optimistic(callback: () => void): () => void {\n if (this._disposed) {\n throw new Error('Cannot perform optimistic update on disposed Collection');\n }\n\n const snapshot = this._items;\n const timestampSnapshot = this._timestamps ? new Map(this._timestamps) : null;\n callback();\n\n let rolledBack = false;\n return () => {\n if (rolledBack || this._disposed) return;\n rolledBack = true;\n\n const prev = this._items;\n this._items = snapshot;\n if (timestampSnapshot) {\n this._timestamps = timestampSnapshot;\n }\n this.rebuildIndex();\n this.notify(prev);\n this._scheduleEvictionTimer();\n };\n }\n\n // ── Query Methods (pure, no notification) ──\n\n /**\n * Get item by id.\n */\n get(id: T['id']): T | undefined {\n return this._index.get(id);\n }\n\n /**\n * Check if item exists by id.\n */\n has(id: T['id']): boolean {\n return this._index.has(id);\n }\n\n /**\n * Find first item matching predicate.\n */\n find(predicate: (item: T) => boolean): T | undefined {\n return this._items.find(predicate);\n }\n\n /**\n * Filter items matching predicate.\n */\n filter(predicate: (item: T) => boolean): readonly T[] {\n return this._items.filter(predicate);\n }\n\n /**\n * Return sorted copy.\n */\n sorted(compareFn: (a: T, b: T) => number): readonly T[] {\n return [...this._items].sort(compareFn);\n }\n\n /**\n * Map items to new array.\n */\n map<U>(fn: (item: T) => U): readonly U[] {\n return this._items.map(fn);\n }\n\n // ── Subscribable interface ──\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: CollectionListener<T>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._clearEvictionTimer();\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n this._listeners.clear();\n this._index.clear();\n this._timestamps?.clear();\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n /**\n * Called before items are auto-evicted by capacity or TTL.\n * Override to filter which items get evicted, or veto entirely.\n *\n * @param items - Candidates for eviction\n * @param reason - Why eviction is happening\n * @returns void to proceed with all, false to veto, or T[] subset to evict only those\n */\n protected onEvict?(items: T[], reason: 'capacity' | 'ttl'): T[] | false | void;\n\n private notify(prev: readonly T[]): void {\n for (const listener of this._listeners) {\n listener(this._items, prev);\n }\n }\n\n private rebuildIndex(): void {\n this._index.clear();\n for (const item of this._items) {\n this._index.set(item.id, item);\n }\n }\n\n // ── Eviction Internals ──\n\n private _evictForCapacity(items: T[]): T[] {\n const excess = items.length - this._maxSize;\n if (excess <= 0) return items;\n\n const candidates = items.slice(0, excess);\n const toEvict = this._applyOnEvict(candidates, 'capacity');\n\n if (toEvict === false) return items; // veto\n\n if (toEvict.length === 0) return items; // nothing to evict\n\n const evictIds = new Set(toEvict.map(item => item.id));\n const result = items.filter(item => !evictIds.has(item.id));\n\n // Clean up index and timestamps for evicted items\n for (const item of toEvict) {\n this._index.delete(item.id);\n this._timestamps?.delete(item.id);\n }\n\n return result;\n }\n\n private _applyOnEvict(candidates: T[], reason: 'capacity' | 'ttl'): T[] | false {\n if (!this.onEvict) return candidates;\n\n const result = this.onEvict(candidates, reason);\n if (result === false) {\n // DEV warning when veto causes collection to exceed 2x MAX_SIZE\n if (__DEV__ && reason === 'capacity' && this._maxSize > 0) {\n const currentSize = this._items.length + candidates.length;\n if (currentSize > this._maxSize * 2) {\n console.warn(\n `[mvc-kit] Collection exceeded 2x MAX_SIZE (${currentSize}/${this._maxSize}). ` +\n `onEvict is vetoing eviction — this may cause unbounded growth.`\n );\n }\n }\n return false;\n }\n if (Array.isArray(result)) {\n // Only include items that are actually in the current items\n const candidateIds = new Set(candidates.map(c => c.id));\n return result.filter(item => candidateIds.has(item.id));\n }\n return candidates; // void = proceed with all\n }\n\n private _sweepExpired(): void {\n if (this._disposed || !this._timestamps || this._ttl <= 0) return;\n\n const now = Date.now();\n const ttl = this._ttl;\n const expired: T[] = [];\n\n for (const item of this._items) {\n const ts = this._timestamps.get(item.id);\n if (ts !== undefined && (now - ts) >= ttl) {\n expired.push(item);\n }\n }\n\n if (expired.length === 0) {\n this._scheduleEvictionTimer();\n return;\n }\n\n const toEvict = this._applyOnEvict(expired, 'ttl');\n\n if (toEvict === false) {\n this._scheduleEvictionTimer();\n return;\n }\n\n if (toEvict.length === 0) {\n this._scheduleEvictionTimer();\n return;\n }\n\n const evictIds = new Set(toEvict.map(item => item.id));\n const prev = this._items;\n this._items = Object.freeze(\n (prev as T[]).filter((item: T) => !evictIds.has(item.id))\n );\n\n for (const item of toEvict) {\n this._index.delete(item.id);\n this._timestamps.delete(item.id);\n }\n\n this.notify(prev);\n this._scheduleEvictionTimer();\n }\n\n private _scheduleEvictionTimer(): void {\n this._clearEvictionTimer();\n\n if (this._disposed || !this._timestamps || this._ttl <= 0 || this._timestamps.size === 0) return;\n\n const now = Date.now();\n const ttl = this._ttl;\n let earliest = Infinity;\n\n for (const ts of this._timestamps.values()) {\n if (ts < earliest) earliest = ts;\n }\n\n const delay = Math.max(0, (earliest + ttl) - now);\n this._evictionTimer = setTimeout(() => this._sweepExpired(), delay);\n }\n\n private _clearEvictionTimer(): void {\n if (this._evictionTimer !== null) {\n clearTimeout(this._evictionTimer);\n this._evictionTimer = null;\n }\n }\n}\n","import { Collection } from './Collection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// Track storageKey uniqueness in DEV\nconst _registeredKeys: Map<string, string> | null = __DEV__ ? new Map() : null;\n\n/**\n * Abstract base for Collections that persist to external storage.\n * Tracks deltas per mutation and flushes via debounced writes.\n * Subclasses implement the storage-specific `persist*` methods.\n */\nexport abstract class PersistentCollection<\n T extends { id: string | number },\n> extends Collection<T> {\n /** Debounce delay in ms for storage writes. 0 = immediate. */\n static WRITE_DELAY = 100;\n\n /** Unique key identifying this collection in storage. */\n protected abstract readonly storageKey: string;\n\n // ── Abstract persistence methods ──\n\n protected abstract persistGet(id: T['id']): T | null | Promise<T | null>;\n protected abstract persistGetAll(): T[] | Promise<T[]>;\n /** Upsert semantics — insert or replace the given items. */\n protected abstract persistSet(items: T[]): void | Promise<void>;\n protected abstract persistRemove(ids: T['id'][]): void | Promise<void>;\n protected abstract persistClear(): void | Promise<void>;\n\n // ── Serialization hooks ──\n\n /** Serialize items to a string. Used by string-based adapters (WebStorage, NativeCollection). */\n protected serialize(items: readonly T[]): string {\n return JSON.stringify(items);\n }\n\n /** Deserialize a string back to items. Used by string-based adapters. */\n protected deserialize(raw: string): T[] {\n return JSON.parse(raw);\n }\n\n // ── Error hook ──\n\n /** Called when a storage operation fails. Override for custom error handling. */\n protected onPersistError?(error: unknown): void;\n\n // ── Internal state ──\n\n private _hydrated = false;\n private _hydrating = false;\n private _persistenceReady = false;\n private _preHydrationWarned = false;\n private _pendingWrites = new Map<T['id'], T>();\n private _pendingRemoves = new Set<T['id']>();\n private _pendingClear = false;\n private _flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(initialItems: T[] = []) {\n super(initialItems);\n\n // Self-subscribe to detect mutations via diff.\n // storageKey may not be available yet (class field initializers run after super()),\n // but that's fine — the subscriber only fires on mutations, not during construction.\n const unsub = this.subscribe((current, prev) => {\n if (this._hydrating) return;\n this._ensurePersistenceReady();\n this._diffAndQueue(current, prev);\n this._scheduleSave();\n });\n this.addCleanup(unsub);\n }\n\n /**\n * DEV check for duplicate storageKey. Called lazily since storageKey is an abstract\n * field that isn't available during the parent constructor chain.\n */\n private _ensurePersistenceReady(): void {\n if (this._persistenceReady) return;\n this._persistenceReady = true;\n\n if (__DEV__ && _registeredKeys) {\n const className = this.constructor.name;\n const existing = _registeredKeys.get(this.storageKey);\n if (existing && existing !== className) {\n console.warn(\n `[mvc-kit] Duplicate storageKey \"${this.storageKey}\" used by \"${className}\" ` +\n `and \"${existing}\". Each PersistentCollection should have a unique storageKey.`,\n );\n }\n _registeredKeys.set(this.storageKey, className);\n }\n }\n\n // ── Public API ──\n\n /** Whether storage data has been loaded. */\n get hydrated(): boolean {\n return this._hydrated;\n }\n\n /**\n * Load data from storage into the collection. Idempotent — subsequent calls return current items.\n * Returns the items after hydration.\n */\n async hydrate(): Promise<readonly T[]> {\n if (this._hydrated) return this.items;\n this._ensurePersistenceReady();\n\n this._hydrating = true;\n try {\n const stored = await this.persistGetAll();\n if (stored.length > 0) {\n super.reset(stored);\n }\n this._hydrated = true;\n return this.items;\n } catch (err) {\n this._handlePersistError(err);\n this._hydrated = true;\n return this.items;\n } finally {\n this._hydrating = false;\n }\n }\n\n /**\n * Synchronous hydration for sync adapters (e.g., WebStorage).\n * Call from the **leaf class** constructor (after field initializers have run).\n */\n protected _hydrateSync(): void {\n if (this._hydrated) return;\n this._ensurePersistenceReady();\n\n this._hydrating = true;\n try {\n const stored = this.persistGetAll();\n if (stored instanceof Promise) {\n throw new Error('[mvc-kit] _hydrateSync called with async persistGetAll');\n }\n if (stored.length > 0) {\n super.reset(stored);\n }\n this._hydrated = true;\n } catch (err) {\n this._handlePersistError(err);\n this._hydrated = true;\n } finally {\n this._hydrating = false;\n }\n }\n\n /**\n * Clear all data from storage AND from the in-memory collection.\n */\n clearStorage(): void | Promise<void> {\n this._ensurePersistenceReady();\n\n // Clear pending queues — we're wiping everything\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n this._pendingClear = false;\n this._cancelSave();\n\n // Clear in-memory\n if (this.length > 0) {\n super.clear();\n }\n\n try {\n const result = this.persistClear();\n if (result instanceof Promise) {\n return result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n // ── Overrides for clear/reset tracking ──\n\n reset(items: T[]): void {\n this._pendingClear = true;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n for (const item of items) {\n this._pendingWrites.set(item.id, item);\n }\n this._hydrating = true; // Re-use flag to skip subscriber\n try {\n super.reset(items);\n } finally {\n this._hydrating = false;\n }\n this._scheduleSave();\n }\n\n clear(): void {\n this._pendingClear = true;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n this._hydrating = true;\n try {\n super.clear();\n } finally {\n this._hydrating = false;\n }\n this._scheduleSave();\n }\n\n // ── Override items getter for DEV pre-hydration warning ──\n\n get items(): readonly T[] {\n if (__DEV__ && !this._hydrated && !this._hydrating && !this._preHydrationWarned) {\n this._preHydrationWarned = true;\n console.warn(\n `[mvc-kit] Accessing items on \"${this.constructor.name}\" before hydrate() has been called. ` +\n `Data may be incomplete. Call hydrate() first.`,\n );\n }\n return super.items;\n }\n\n get state(): readonly T[] {\n return this.items;\n }\n\n // ── Dispose ──\n\n dispose(): void {\n if (this.disposed) return;\n\n // Flush any pending saves before disposing\n this._cancelSave();\n if (this._hasPending()) {\n try {\n const result = this._flush();\n if (result instanceof Promise) {\n result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n // DEV: unregister storageKey\n if (__DEV__ && _registeredKeys && this._persistenceReady) {\n _registeredKeys.delete(this.storageKey);\n }\n\n super.dispose();\n }\n\n // ── Private: delta tracking ──\n\n private _diffAndQueue(current: readonly T[], prev: readonly T[]): void {\n const prevMap = new Map<T['id'], T>();\n for (const item of prev) {\n prevMap.set(item.id, item);\n }\n\n const currentMap = new Map<T['id'], T>();\n for (const item of current) {\n currentMap.set(item.id, item);\n }\n\n // Added or updated: in current but not in prev, or different reference\n for (const item of current) {\n const prevItem = prevMap.get(item.id);\n if (!prevItem || prevItem !== item) {\n this._pendingWrites.set(item.id, item);\n this._pendingRemoves.delete(item.id);\n }\n }\n\n // Removed: in prev but not in current\n for (const item of prev) {\n if (!currentMap.has(item.id)) {\n this._pendingRemoves.add(item.id);\n this._pendingWrites.delete(item.id);\n }\n }\n }\n\n private _hasPending(): boolean {\n return this._pendingClear || this._pendingWrites.size > 0 || this._pendingRemoves.size > 0;\n }\n\n // ── Private: debounce + flush ──\n\n private _scheduleSave(): void {\n this._cancelSave();\n const delay = (this.constructor as typeof PersistentCollection).WRITE_DELAY;\n if (delay <= 0) {\n this._doFlush();\n return;\n }\n this._flushTimer = setTimeout(() => this._doFlush(), delay);\n }\n\n private _cancelSave(): void {\n if (this._flushTimer !== null) {\n clearTimeout(this._flushTimer);\n this._flushTimer = null;\n }\n }\n\n private _doFlush(): void {\n if (!this._hasPending()) return;\n try {\n const result = this._flush();\n if (result instanceof Promise) {\n result.catch((err) => this._handlePersistError(err));\n }\n } catch (err) {\n this._handlePersistError(err);\n }\n }\n\n private _flush(): void | Promise<void> {\n const doClear = this._pendingClear;\n const writes = this._pendingWrites.size > 0 ? [...this._pendingWrites.values()] : null;\n const removes = this._pendingRemoves.size > 0 ? [...this._pendingRemoves] : null;\n\n // Clear queues\n this._pendingClear = false;\n this._pendingWrites.clear();\n this._pendingRemoves.clear();\n\n if (doClear) {\n const clearResult = this.persistClear();\n if (clearResult instanceof Promise) {\n return clearResult.then(() => {\n if (writes) return this.persistSet(writes);\n });\n }\n if (writes) {\n return this.persistSet(writes);\n }\n return;\n }\n\n // Non-clear: removes then writes\n if (removes) {\n const removeResult = this.persistRemove(removes);\n if (removeResult instanceof Promise) {\n return removeResult.then(() => {\n if (writes) return this.persistSet(writes);\n });\n }\n }\n if (writes) {\n return this.persistSet(writes);\n }\n }\n\n // ── Private: error handling ──\n\n private _handlePersistError(err: unknown): void {\n if (this.onPersistError) {\n this.onPersistError(err);\n return;\n }\n if (__DEV__) {\n console.warn('[mvc-kit] Storage error:', err);\n }\n }\n}\n"],"names":["__DEV__","Collection","initialItems","result","now","item","excess","evicted","items","seen","newItems","prev","incoming","changed","replaced","newArray","existing","replacement","id","ids","idSet","filtered","changes","idx","updated","key","callback","snapshot","timestampSnapshot","rolledBack","predicate","compareFn","fn","listener","candidates","toEvict","evictIds","reason","currentSize","candidateIds","c","ttl","expired","ts","earliest","delay","_registeredKeys","PersistentCollection","raw","unsub","current","className","stored","err","prevMap","currentMap","prevItem","doClear","writes","removes","clearResult","removeResult"],"mappings":"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"}
|