@hurum/react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +149 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +145 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var core = require('@hurum/core');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/use-store.ts
|
|
8
|
+
var IS_DEV = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process?.env?.["NODE_ENV"] !== "production";
|
|
9
|
+
var singletonMap = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
var contextMap = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
var NULL_CONTEXT = react.createContext(null);
|
|
12
|
+
function getSingleton(def) {
|
|
13
|
+
let instance = singletonMap.get(def);
|
|
14
|
+
if (!instance) {
|
|
15
|
+
if (IS_DEV && typeof window === "undefined") {
|
|
16
|
+
console.warn(
|
|
17
|
+
"[hurum/react] Singleton accessed on the server. Use Provider + Store.create() for SSR."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
instance = def.create();
|
|
21
|
+
singletonMap.set(def, instance);
|
|
22
|
+
}
|
|
23
|
+
return instance;
|
|
24
|
+
}
|
|
25
|
+
function getContext(def) {
|
|
26
|
+
let ctx = contextMap.get(def);
|
|
27
|
+
if (!ctx) {
|
|
28
|
+
ctx = react.createContext(null);
|
|
29
|
+
contextMap.set(def, ctx);
|
|
30
|
+
}
|
|
31
|
+
return ctx;
|
|
32
|
+
}
|
|
33
|
+
function createFieldHook(getStore, fieldName) {
|
|
34
|
+
return function useField() {
|
|
35
|
+
const store = getStore();
|
|
36
|
+
const subscribe = react.useCallback(
|
|
37
|
+
(onStoreChange) => store.subscribe(onStoreChange),
|
|
38
|
+
[store]
|
|
39
|
+
);
|
|
40
|
+
const getSnapshot = react.useCallback(
|
|
41
|
+
() => store.getState()[fieldName],
|
|
42
|
+
[store]
|
|
43
|
+
);
|
|
44
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createUseProxy(getStore) {
|
|
48
|
+
const hookCache = /* @__PURE__ */ new Map();
|
|
49
|
+
return new Proxy({}, {
|
|
50
|
+
get(_target, prop) {
|
|
51
|
+
if (typeof prop !== "string") return void 0;
|
|
52
|
+
let hook = hookCache.get(prop);
|
|
53
|
+
if (!hook) {
|
|
54
|
+
hook = createFieldHook(getStore, prop);
|
|
55
|
+
hookCache.set(prop, hook);
|
|
56
|
+
}
|
|
57
|
+
return hook;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function useSelectorHook(store, fnOrSelector) {
|
|
62
|
+
if (core.isSelector(fnOrSelector)) {
|
|
63
|
+
return useSelectorFromObject(fnOrSelector);
|
|
64
|
+
}
|
|
65
|
+
return useSelectorFromFn(store, fnOrSelector);
|
|
66
|
+
}
|
|
67
|
+
function useSelectorFromObject(selector) {
|
|
68
|
+
const subscribe = react.useCallback(
|
|
69
|
+
(onStoreChange) => selector.subscribe(() => onStoreChange()),
|
|
70
|
+
[selector]
|
|
71
|
+
);
|
|
72
|
+
return react.useSyncExternalStore(subscribe, selector.get, selector.get);
|
|
73
|
+
}
|
|
74
|
+
function useSelectorFromFn(store, selectorFn) {
|
|
75
|
+
const fnRef = react.useRef(selectorFn);
|
|
76
|
+
fnRef.current = selectorFn;
|
|
77
|
+
const lastRef = react.useRef({ value: void 0, initialized: false });
|
|
78
|
+
const subscribe = react.useCallback(
|
|
79
|
+
(onStoreChange) => store.subscribe(onStoreChange),
|
|
80
|
+
[store]
|
|
81
|
+
);
|
|
82
|
+
const getSnapshot = react.useCallback(() => {
|
|
83
|
+
const state = store.getState();
|
|
84
|
+
const next = fnRef.current(state);
|
|
85
|
+
if (lastRef.current.initialized && core.structuralEqual(lastRef.current.value, next)) {
|
|
86
|
+
return lastRef.current.value;
|
|
87
|
+
}
|
|
88
|
+
lastRef.current = { value: next, initialized: true };
|
|
89
|
+
return next;
|
|
90
|
+
}, [store]);
|
|
91
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/use-store.ts
|
|
95
|
+
function useStore(defOrInstance) {
|
|
96
|
+
const isDef = core.isStoreDefinition(defOrInstance);
|
|
97
|
+
const ctx = react.useContext(isDef ? getContext(defOrInstance) : NULL_CONTEXT);
|
|
98
|
+
const store = isDef ? ctx ?? getSingleton(defOrInstance) : defOrInstance;
|
|
99
|
+
return useStoreHandle(store);
|
|
100
|
+
}
|
|
101
|
+
function useStoreHandle(store) {
|
|
102
|
+
const handle = react.useMemo(() => {
|
|
103
|
+
const useProxy = createUseProxy(
|
|
104
|
+
() => store
|
|
105
|
+
);
|
|
106
|
+
return {
|
|
107
|
+
use: useProxy,
|
|
108
|
+
useSelector: (fnOrSelector) => {
|
|
109
|
+
return useSelectorHook(store, fnOrSelector);
|
|
110
|
+
},
|
|
111
|
+
// Use store.send directly — it's already a Proxy with intent shortcuts.
|
|
112
|
+
// Don't use .bind() which would create a plain function losing the Proxy.
|
|
113
|
+
send: store.send,
|
|
114
|
+
cancel: store.cancel.bind(store),
|
|
115
|
+
cancelAll: store.cancelAll.bind(store),
|
|
116
|
+
getState: store.getState.bind(store),
|
|
117
|
+
subscribe: store.subscribe.bind(store),
|
|
118
|
+
dispose: store.dispose.bind(store),
|
|
119
|
+
scope: store.scope
|
|
120
|
+
};
|
|
121
|
+
}, [store]);
|
|
122
|
+
return handle;
|
|
123
|
+
}
|
|
124
|
+
function StoreProvider(props) {
|
|
125
|
+
const { of: def, children } = props;
|
|
126
|
+
const Context = getContext(def);
|
|
127
|
+
const externalStore = props.store;
|
|
128
|
+
const [autoStore] = react.useState(() => {
|
|
129
|
+
if (externalStore) return null;
|
|
130
|
+
return def.create(props);
|
|
131
|
+
});
|
|
132
|
+
const instance = externalStore ?? autoStore;
|
|
133
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Context.Provider, { value: instance, children });
|
|
134
|
+
}
|
|
135
|
+
function withProvider(def, WrappedComponent) {
|
|
136
|
+
const Context = getContext(def);
|
|
137
|
+
function WithProvider(props) {
|
|
138
|
+
const [store] = react.useState(() => def.create());
|
|
139
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Context.Provider, { value: store, children: /* @__PURE__ */ jsxRuntime.jsx(WrappedComponent, { ...props }) });
|
|
140
|
+
}
|
|
141
|
+
WithProvider.displayName = `withProvider(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
|
|
142
|
+
return WithProvider;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
exports.StoreProvider = StoreProvider;
|
|
146
|
+
exports.useStore = useStore;
|
|
147
|
+
exports.withProvider = withProvider;
|
|
148
|
+
//# sourceMappingURL=index.cjs.map
|
|
149
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/singleton.ts","../src/hooks.ts","../src/use-selector.ts","../src/use-store.ts","../src/store-provider.tsx","../src/with-provider.tsx"],"names":["createContext","useCallback","useSyncExternalStore","isSelector","useRef","structuralEqual","isStoreDefinition","useContext","useMemo","useState","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["// @hurum/react — Singleton management\n// Manages per-StoreDefinition singleton instances and React contexts\n\nimport { createContext } from 'react'\nimport type { StoreDefinition, StoreInstance } from '@hurum/core'\n\nconst IS_DEV = typeof globalThis !== 'undefined'\n && typeof (globalThis as { process?: { env?: Record<string, string> } }).process !== 'undefined'\n && (globalThis as { process?: { env?: Record<string, string> } }).process?.env?.['NODE_ENV'] !== 'production'\n\n// WeakMaps keyed by StoreDefinition for per-store metadata\nconst singletonMap = new WeakMap<StoreDefinition, StoreInstance>()\nconst contextMap = new WeakMap<StoreDefinition, React.Context<StoreInstance | null>>()\n\n/** Stable context that always returns null — used when useStore receives an instance directly */\nexport const NULL_CONTEXT = createContext<StoreInstance | null>(null)\n\n/**\n * Get or create the singleton instance for a StoreDefinition.\n * Warns in dev mode if called on the server.\n */\nexport function getSingleton(def: StoreDefinition): StoreInstance {\n let instance = singletonMap.get(def)\n if (!instance) {\n if (IS_DEV && typeof window === 'undefined') {\n console.warn(\n '[hurum/react] Singleton accessed on the server. Use Provider + Store.create() for SSR.',\n )\n }\n instance = def.create()\n singletonMap.set(def, instance)\n }\n return instance\n}\n\n/**\n * Get or create the React Context for a StoreDefinition.\n * Each StoreDefinition gets its own Context.\n */\nexport function getContext(def: StoreDefinition): React.Context<StoreInstance | null> {\n let ctx = contextMap.get(def)\n if (!ctx) {\n ctx = createContext<StoreInstance | null>(null)\n contextMap.set(def, ctx)\n }\n return ctx\n}\n","// @hurum/react — Core hooks using useSyncExternalStore\n\nimport { useCallback, useSyncExternalStore } from 'react'\nimport type { StoreInstance } from '@hurum/core'\n\n/**\n * Create a `use.fieldName()` hook for a specific field on a specific store instance.\n * Uses `useSyncExternalStore` internally.\n */\nfunction createFieldHook(getStore: () => StoreInstance, fieldName: string) {\n return function useField() {\n const store = getStore()\n const subscribe = useCallback(\n (onStoreChange: () => void) => store.subscribe(onStoreChange),\n [store],\n )\n const getSnapshot = useCallback(\n () => (store.getState() as Record<string, unknown>)[fieldName],\n [store],\n )\n // getServerSnapshot returns the same snapshot — the store instance\n // is already initialized with correct state on the server via Store.create()\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n }\n}\n\n/**\n * Create the `use.*` proxy for a store.\n * Accessing `use.fieldName` returns a hook that reads that field.\n */\nexport function createUseProxy(getStore: () => StoreInstance): Record<string, () => unknown> {\n const hookCache = new Map<string, () => unknown>()\n return new Proxy({} as Record<string, () => unknown>, {\n get(_target, prop) {\n if (typeof prop !== 'string') return undefined\n let hook = hookCache.get(prop)\n if (!hook) {\n hook = createFieldHook(getStore, prop)\n hookCache.set(prop, hook)\n }\n return hook\n },\n })\n}\n","// @hurum/react — useSelector hook\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport type { StoreInstance, ResolvedState, Selector } from '@hurum/core'\nimport { isSelector, structuralEqual } from '@hurum/core'\n\n/**\n * React hook for derived state from a store.\n *\n * Accepts either:\n * - An inline selector function: `(state) => derivedValue`\n * - A pre-built Selector object from `store.selector(fn)`\n *\n * Uses structural equality to prevent unnecessary re-renders.\n */\nexport function useSelectorHook<TRawState, TComputed, T>(\n store: StoreInstance<unknown, TRawState, TComputed>,\n fnOrSelector: ((state: ResolvedState<TRawState> & TComputed) => T) | Selector<T>,\n): T {\n if (isSelector(fnOrSelector)) {\n return useSelectorFromObject(fnOrSelector)\n }\n return useSelectorFromFn(store, fnOrSelector)\n}\n\nfunction useSelectorFromObject<T>(selector: Selector<T>): T {\n const subscribe = useCallback(\n (onStoreChange: () => void) => selector.subscribe(() => onStoreChange()),\n [selector],\n )\n return useSyncExternalStore(subscribe, selector.get, selector.get)\n}\n\nfunction useSelectorFromFn<TRawState, TComputed, T>(\n store: StoreInstance<unknown, TRawState, TComputed>,\n selectorFn: (state: ResolvedState<TRawState> & TComputed) => T,\n): T {\n // Ref for latest selector fn — keeps subscribe/getSnapshot stable\n const fnRef = useRef(selectorFn)\n fnRef.current = selectorFn\n\n // Ref for structural equality memoization\n const lastRef = useRef<{ value: T; initialized: boolean }>({ value: undefined as T, initialized: false })\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => store.subscribe(onStoreChange),\n [store],\n )\n\n const getSnapshot = useCallback((): T => {\n const state = store.getState()\n const next = fnRef.current(state)\n\n if (lastRef.current.initialized && structuralEqual(lastRef.current.value, next)) {\n return lastRef.current.value\n }\n\n lastRef.current = { value: next, initialized: true }\n return next\n }, [store])\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n","// @hurum/react — useStore hook\n\nimport { useContext, useMemo } from 'react'\nimport type { StoreDefinition, StoreInstance, IntentRef, ResolvedState, ScopeOf, SendFn, Selector } from '@hurum/core'\nimport { isStoreDefinition } from '@hurum/core'\nimport type { EventInstance } from '@hurum/core'\nimport { getContext, getSingleton, NULL_CONTEXT } from './singleton'\nimport { createUseProxy } from './hooks'\nimport { useSelectorHook } from './use-selector'\n\n/**\n * The object returned by useStore(). Provides:\n * - `.use.fieldName()` hooks for reading individual fields\n * - `.useSelector(fn)` for derived state with structural equality\n * - `.send()` for dispatching intents (supports PreparedIntent + named shortcuts)\n * - `.cancel()` / `.cancelAll()` for cancellation\n * - `.subscribe()` for event subscriptions\n * - `.scope` for nested store access\n * - `.getState()` for reading full state\n * - `.dispose()` for cleanup\n */\nexport interface UseStoreReturn<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {\n readonly use: {\n readonly [K in keyof (ResolvedState<TRawState> & TComputed)]: () => (ResolvedState<TRawState> & TComputed)[K]\n }\n /** Derived state hook with structural equality memoization. */\n useSelector<T>(fn: (state: ResolvedState<TRawState> & TComputed) => T): T\n useSelector<T>(selector: Selector<T>): T\n send: SendFn<TIntents>\n cancel(ref: IntentRef): void\n cancelAll(): void\n getState(): ResolvedState<TRawState> & TComputed\n subscribe(cb: (state: ResolvedState<TRawState> & TComputed) => void): () => void\n subscribe(type: 'events', cb: (event: EventInstance) => void): () => void\n dispose(): void\n readonly scope: ScopeOf<TRawState>\n}\n\n/**\n * Context-aware hook to get a store handle.\n *\n * Accepts either a StoreDefinition or a StoreInstance:\n * - `useStore(def)` — Inside Provider → scoped instance. Outside → singleton fallback.\n * - `useStore(instance)` — Uses the given instance directly.\n *\n * The returned object has `.use.fieldName()` hooks for granular subscriptions.\n */\nexport function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n def: StoreDefinition<TDeps, TRawState, TComputed, TIntents>,\n): UseStoreReturn<TRawState, TComputed, TIntents>\nexport function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n instance: StoreInstance<TDeps, TRawState, TComputed, TIntents>,\n): UseStoreReturn<TRawState, TComputed, TIntents>\nexport function useStore(\n defOrInstance: StoreDefinition | StoreInstance,\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): UseStoreReturn<any, any, any> {\n const isDef = isStoreDefinition(defOrInstance)\n // Always call useContext unconditionally (hooks rules). NULL_CONTEXT when receiving instance.\n const ctx = useContext(isDef ? getContext(defOrInstance as StoreDefinition) : NULL_CONTEXT)\n const store = isDef\n ? (ctx ?? getSingleton(defOrInstance as StoreDefinition))\n : (defOrInstance as StoreInstance)\n return useStoreHandle(store)\n}\n\nfunction useStoreHandle<TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n store: StoreInstance<unknown, TRawState, TComputed, TIntents>,\n): UseStoreReturn<TRawState, TComputed, TIntents> {\n const handle = useMemo(() => {\n const useProxy = createUseProxy(\n () => store,\n )\n return {\n use: useProxy as UseStoreReturn<TRawState, TComputed, TIntents>['use'],\n useSelector: <T>(fnOrSelector: ((state: ResolvedState<TRawState> & TComputed) => T) | Selector<T>): T => {\n return useSelectorHook(store, fnOrSelector)\n },\n // Use store.send directly — it's already a Proxy with intent shortcuts.\n // Don't use .bind() which would create a plain function losing the Proxy.\n send: store.send as SendFn<TIntents>,\n cancel: store.cancel.bind(store),\n cancelAll: store.cancelAll.bind(store),\n getState: store.getState.bind(store) as () => ResolvedState<TRawState> & TComputed,\n subscribe: store.subscribe.bind(store) as UseStoreReturn<TRawState, TComputed, TIntents>['subscribe'],\n dispose: store.dispose.bind(store),\n scope: store.scope as ScopeOf<TRawState>,\n }\n }, [store])\n\n return handle\n}\n","// @hurum/react — StoreProvider component\n\nimport { useState, type ReactNode } from 'react'\nimport type { StoreDefinition, StoreInstance } from '@hurum/core'\nimport { getContext } from './singleton'\n\nexport type StoreProviderProps =\n | {\n of: StoreDefinition\n store: StoreInstance\n children: ReactNode\n }\n | {\n of: StoreDefinition\n store?: undefined\n initialState?: unknown\n deps?: unknown\n children: ReactNode\n }\n\n/**\n * Provides a scoped store instance to descendant components.\n *\n * Two modes:\n * - `<StoreProvider of={Def} store={instance}>` — uses the given instance\n * - `<StoreProvider of={Def}>` — auto-creates a new instance (with optional initialState/deps)\n *\n * Does NOT auto-dispose on unmount (React Strict Mode compatible).\n */\nexport function StoreProvider(props: StoreProviderProps): ReactNode {\n const { of: def, children } = props\n const Context = getContext(def)\n const externalStore = props.store\n\n // Always call useState for hook order stability\n const [autoStore] = useState(() => {\n if (externalStore) return null\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return def.create(props as any)\n })\n\n const instance = externalStore ?? autoStore!\n\n return <Context.Provider value={instance}>{children}</Context.Provider>\n}\n","// @hurum/react — withProvider HOC\n\nimport { useState, type ComponentType } from 'react'\nimport type { StoreDefinition } from '@hurum/core'\nimport { getContext } from './singleton'\n\n/**\n * HOC that wraps a component with a fresh store instance on each mount.\n * Useful for modals, wizards, and other isolated UI.\n */\nexport function withProvider<P extends object>(\n def: StoreDefinition,\n WrappedComponent: ComponentType<P>,\n): ComponentType<P> {\n const Context = getContext(def)\n\n function WithProvider(props: P) {\n const [store] = useState(() => def.create())\n return (\n <Context.Provider value={store}>\n <WrappedComponent {...props} />\n </Context.Provider>\n )\n }\n\n WithProvider.displayName = `withProvider(${WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component'})`\n return WithProvider\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ResolvedState, Selector, SendFn, IntentRef, EventInstance, ScopeOf, StoreDefinition, StoreInstance } from '@hurum/core';
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The object returned by useStore(). Provides:
|
|
6
|
+
* - `.use.fieldName()` hooks for reading individual fields
|
|
7
|
+
* - `.useSelector(fn)` for derived state with structural equality
|
|
8
|
+
* - `.send()` for dispatching intents (supports PreparedIntent + named shortcuts)
|
|
9
|
+
* - `.cancel()` / `.cancelAll()` for cancellation
|
|
10
|
+
* - `.subscribe()` for event subscriptions
|
|
11
|
+
* - `.scope` for nested store access
|
|
12
|
+
* - `.getState()` for reading full state
|
|
13
|
+
* - `.dispose()` for cleanup
|
|
14
|
+
*/
|
|
15
|
+
interface UseStoreReturn<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {
|
|
16
|
+
readonly use: {
|
|
17
|
+
readonly [K in keyof (ResolvedState<TRawState> & TComputed)]: () => (ResolvedState<TRawState> & TComputed)[K];
|
|
18
|
+
};
|
|
19
|
+
/** Derived state hook with structural equality memoization. */
|
|
20
|
+
useSelector<T>(fn: (state: ResolvedState<TRawState> & TComputed) => T): T;
|
|
21
|
+
useSelector<T>(selector: Selector<T>): T;
|
|
22
|
+
send: SendFn<TIntents>;
|
|
23
|
+
cancel(ref: IntentRef): void;
|
|
24
|
+
cancelAll(): void;
|
|
25
|
+
getState(): ResolvedState<TRawState> & TComputed;
|
|
26
|
+
subscribe(cb: (state: ResolvedState<TRawState> & TComputed) => void): () => void;
|
|
27
|
+
subscribe(type: 'events', cb: (event: EventInstance) => void): () => void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
readonly scope: ScopeOf<TRawState>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Context-aware hook to get a store handle.
|
|
33
|
+
*
|
|
34
|
+
* Accepts either a StoreDefinition or a StoreInstance:
|
|
35
|
+
* - `useStore(def)` — Inside Provider → scoped instance. Outside → singleton fallback.
|
|
36
|
+
* - `useStore(instance)` — Uses the given instance directly.
|
|
37
|
+
*
|
|
38
|
+
* The returned object has `.use.fieldName()` hooks for granular subscriptions.
|
|
39
|
+
*/
|
|
40
|
+
declare function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(def: StoreDefinition<TDeps, TRawState, TComputed, TIntents>): UseStoreReturn<TRawState, TComputed, TIntents>;
|
|
41
|
+
declare function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(instance: StoreInstance<TDeps, TRawState, TComputed, TIntents>): UseStoreReturn<TRawState, TComputed, TIntents>;
|
|
42
|
+
|
|
43
|
+
type StoreProviderProps = {
|
|
44
|
+
of: StoreDefinition;
|
|
45
|
+
store: StoreInstance;
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
} | {
|
|
48
|
+
of: StoreDefinition;
|
|
49
|
+
store?: undefined;
|
|
50
|
+
initialState?: unknown;
|
|
51
|
+
deps?: unknown;
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Provides a scoped store instance to descendant components.
|
|
56
|
+
*
|
|
57
|
+
* Two modes:
|
|
58
|
+
* - `<StoreProvider of={Def} store={instance}>` — uses the given instance
|
|
59
|
+
* - `<StoreProvider of={Def}>` — auto-creates a new instance (with optional initialState/deps)
|
|
60
|
+
*
|
|
61
|
+
* Does NOT auto-dispose on unmount (React Strict Mode compatible).
|
|
62
|
+
*/
|
|
63
|
+
declare function StoreProvider(props: StoreProviderProps): ReactNode;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* HOC that wraps a component with a fresh store instance on each mount.
|
|
67
|
+
* Useful for modals, wizards, and other isolated UI.
|
|
68
|
+
*/
|
|
69
|
+
declare function withProvider<P extends object>(def: StoreDefinition, WrappedComponent: ComponentType<P>): ComponentType<P>;
|
|
70
|
+
|
|
71
|
+
export { StoreProvider, type StoreProviderProps, type UseStoreReturn, useStore, withProvider };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ResolvedState, Selector, SendFn, IntentRef, EventInstance, ScopeOf, StoreDefinition, StoreInstance } from '@hurum/core';
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The object returned by useStore(). Provides:
|
|
6
|
+
* - `.use.fieldName()` hooks for reading individual fields
|
|
7
|
+
* - `.useSelector(fn)` for derived state with structural equality
|
|
8
|
+
* - `.send()` for dispatching intents (supports PreparedIntent + named shortcuts)
|
|
9
|
+
* - `.cancel()` / `.cancelAll()` for cancellation
|
|
10
|
+
* - `.subscribe()` for event subscriptions
|
|
11
|
+
* - `.scope` for nested store access
|
|
12
|
+
* - `.getState()` for reading full state
|
|
13
|
+
* - `.dispose()` for cleanup
|
|
14
|
+
*/
|
|
15
|
+
interface UseStoreReturn<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {
|
|
16
|
+
readonly use: {
|
|
17
|
+
readonly [K in keyof (ResolvedState<TRawState> & TComputed)]: () => (ResolvedState<TRawState> & TComputed)[K];
|
|
18
|
+
};
|
|
19
|
+
/** Derived state hook with structural equality memoization. */
|
|
20
|
+
useSelector<T>(fn: (state: ResolvedState<TRawState> & TComputed) => T): T;
|
|
21
|
+
useSelector<T>(selector: Selector<T>): T;
|
|
22
|
+
send: SendFn<TIntents>;
|
|
23
|
+
cancel(ref: IntentRef): void;
|
|
24
|
+
cancelAll(): void;
|
|
25
|
+
getState(): ResolvedState<TRawState> & TComputed;
|
|
26
|
+
subscribe(cb: (state: ResolvedState<TRawState> & TComputed) => void): () => void;
|
|
27
|
+
subscribe(type: 'events', cb: (event: EventInstance) => void): () => void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
readonly scope: ScopeOf<TRawState>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Context-aware hook to get a store handle.
|
|
33
|
+
*
|
|
34
|
+
* Accepts either a StoreDefinition or a StoreInstance:
|
|
35
|
+
* - `useStore(def)` — Inside Provider → scoped instance. Outside → singleton fallback.
|
|
36
|
+
* - `useStore(instance)` — Uses the given instance directly.
|
|
37
|
+
*
|
|
38
|
+
* The returned object has `.use.fieldName()` hooks for granular subscriptions.
|
|
39
|
+
*/
|
|
40
|
+
declare function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(def: StoreDefinition<TDeps, TRawState, TComputed, TIntents>): UseStoreReturn<TRawState, TComputed, TIntents>;
|
|
41
|
+
declare function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(instance: StoreInstance<TDeps, TRawState, TComputed, TIntents>): UseStoreReturn<TRawState, TComputed, TIntents>;
|
|
42
|
+
|
|
43
|
+
type StoreProviderProps = {
|
|
44
|
+
of: StoreDefinition;
|
|
45
|
+
store: StoreInstance;
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
} | {
|
|
48
|
+
of: StoreDefinition;
|
|
49
|
+
store?: undefined;
|
|
50
|
+
initialState?: unknown;
|
|
51
|
+
deps?: unknown;
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Provides a scoped store instance to descendant components.
|
|
56
|
+
*
|
|
57
|
+
* Two modes:
|
|
58
|
+
* - `<StoreProvider of={Def} store={instance}>` — uses the given instance
|
|
59
|
+
* - `<StoreProvider of={Def}>` — auto-creates a new instance (with optional initialState/deps)
|
|
60
|
+
*
|
|
61
|
+
* Does NOT auto-dispose on unmount (React Strict Mode compatible).
|
|
62
|
+
*/
|
|
63
|
+
declare function StoreProvider(props: StoreProviderProps): ReactNode;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* HOC that wraps a component with a fresh store instance on each mount.
|
|
67
|
+
* Useful for modals, wizards, and other isolated UI.
|
|
68
|
+
*/
|
|
69
|
+
declare function withProvider<P extends object>(def: StoreDefinition, WrappedComponent: ComponentType<P>): ComponentType<P>;
|
|
70
|
+
|
|
71
|
+
export { StoreProvider, type StoreProviderProps, type UseStoreReturn, useStore, withProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, useState, useCallback, useSyncExternalStore, useRef } from 'react';
|
|
2
|
+
import { isStoreDefinition, isSelector, structuralEqual } from '@hurum/core';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/use-store.ts
|
|
6
|
+
var IS_DEV = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process?.env?.["NODE_ENV"] !== "production";
|
|
7
|
+
var singletonMap = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
var contextMap = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
var NULL_CONTEXT = createContext(null);
|
|
10
|
+
function getSingleton(def) {
|
|
11
|
+
let instance = singletonMap.get(def);
|
|
12
|
+
if (!instance) {
|
|
13
|
+
if (IS_DEV && typeof window === "undefined") {
|
|
14
|
+
console.warn(
|
|
15
|
+
"[hurum/react] Singleton accessed on the server. Use Provider + Store.create() for SSR."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
instance = def.create();
|
|
19
|
+
singletonMap.set(def, instance);
|
|
20
|
+
}
|
|
21
|
+
return instance;
|
|
22
|
+
}
|
|
23
|
+
function getContext(def) {
|
|
24
|
+
let ctx = contextMap.get(def);
|
|
25
|
+
if (!ctx) {
|
|
26
|
+
ctx = createContext(null);
|
|
27
|
+
contextMap.set(def, ctx);
|
|
28
|
+
}
|
|
29
|
+
return ctx;
|
|
30
|
+
}
|
|
31
|
+
function createFieldHook(getStore, fieldName) {
|
|
32
|
+
return function useField() {
|
|
33
|
+
const store = getStore();
|
|
34
|
+
const subscribe = useCallback(
|
|
35
|
+
(onStoreChange) => store.subscribe(onStoreChange),
|
|
36
|
+
[store]
|
|
37
|
+
);
|
|
38
|
+
const getSnapshot = useCallback(
|
|
39
|
+
() => store.getState()[fieldName],
|
|
40
|
+
[store]
|
|
41
|
+
);
|
|
42
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function createUseProxy(getStore) {
|
|
46
|
+
const hookCache = /* @__PURE__ */ new Map();
|
|
47
|
+
return new Proxy({}, {
|
|
48
|
+
get(_target, prop) {
|
|
49
|
+
if (typeof prop !== "string") return void 0;
|
|
50
|
+
let hook = hookCache.get(prop);
|
|
51
|
+
if (!hook) {
|
|
52
|
+
hook = createFieldHook(getStore, prop);
|
|
53
|
+
hookCache.set(prop, hook);
|
|
54
|
+
}
|
|
55
|
+
return hook;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function useSelectorHook(store, fnOrSelector) {
|
|
60
|
+
if (isSelector(fnOrSelector)) {
|
|
61
|
+
return useSelectorFromObject(fnOrSelector);
|
|
62
|
+
}
|
|
63
|
+
return useSelectorFromFn(store, fnOrSelector);
|
|
64
|
+
}
|
|
65
|
+
function useSelectorFromObject(selector) {
|
|
66
|
+
const subscribe = useCallback(
|
|
67
|
+
(onStoreChange) => selector.subscribe(() => onStoreChange()),
|
|
68
|
+
[selector]
|
|
69
|
+
);
|
|
70
|
+
return useSyncExternalStore(subscribe, selector.get, selector.get);
|
|
71
|
+
}
|
|
72
|
+
function useSelectorFromFn(store, selectorFn) {
|
|
73
|
+
const fnRef = useRef(selectorFn);
|
|
74
|
+
fnRef.current = selectorFn;
|
|
75
|
+
const lastRef = useRef({ value: void 0, initialized: false });
|
|
76
|
+
const subscribe = useCallback(
|
|
77
|
+
(onStoreChange) => store.subscribe(onStoreChange),
|
|
78
|
+
[store]
|
|
79
|
+
);
|
|
80
|
+
const getSnapshot = useCallback(() => {
|
|
81
|
+
const state = store.getState();
|
|
82
|
+
const next = fnRef.current(state);
|
|
83
|
+
if (lastRef.current.initialized && structuralEqual(lastRef.current.value, next)) {
|
|
84
|
+
return lastRef.current.value;
|
|
85
|
+
}
|
|
86
|
+
lastRef.current = { value: next, initialized: true };
|
|
87
|
+
return next;
|
|
88
|
+
}, [store]);
|
|
89
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/use-store.ts
|
|
93
|
+
function useStore(defOrInstance) {
|
|
94
|
+
const isDef = isStoreDefinition(defOrInstance);
|
|
95
|
+
const ctx = useContext(isDef ? getContext(defOrInstance) : NULL_CONTEXT);
|
|
96
|
+
const store = isDef ? ctx ?? getSingleton(defOrInstance) : defOrInstance;
|
|
97
|
+
return useStoreHandle(store);
|
|
98
|
+
}
|
|
99
|
+
function useStoreHandle(store) {
|
|
100
|
+
const handle = useMemo(() => {
|
|
101
|
+
const useProxy = createUseProxy(
|
|
102
|
+
() => store
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
use: useProxy,
|
|
106
|
+
useSelector: (fnOrSelector) => {
|
|
107
|
+
return useSelectorHook(store, fnOrSelector);
|
|
108
|
+
},
|
|
109
|
+
// Use store.send directly — it's already a Proxy with intent shortcuts.
|
|
110
|
+
// Don't use .bind() which would create a plain function losing the Proxy.
|
|
111
|
+
send: store.send,
|
|
112
|
+
cancel: store.cancel.bind(store),
|
|
113
|
+
cancelAll: store.cancelAll.bind(store),
|
|
114
|
+
getState: store.getState.bind(store),
|
|
115
|
+
subscribe: store.subscribe.bind(store),
|
|
116
|
+
dispose: store.dispose.bind(store),
|
|
117
|
+
scope: store.scope
|
|
118
|
+
};
|
|
119
|
+
}, [store]);
|
|
120
|
+
return handle;
|
|
121
|
+
}
|
|
122
|
+
function StoreProvider(props) {
|
|
123
|
+
const { of: def, children } = props;
|
|
124
|
+
const Context = getContext(def);
|
|
125
|
+
const externalStore = props.store;
|
|
126
|
+
const [autoStore] = useState(() => {
|
|
127
|
+
if (externalStore) return null;
|
|
128
|
+
return def.create(props);
|
|
129
|
+
});
|
|
130
|
+
const instance = externalStore ?? autoStore;
|
|
131
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: instance, children });
|
|
132
|
+
}
|
|
133
|
+
function withProvider(def, WrappedComponent) {
|
|
134
|
+
const Context = getContext(def);
|
|
135
|
+
function WithProvider(props) {
|
|
136
|
+
const [store] = useState(() => def.create());
|
|
137
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: store, children: /* @__PURE__ */ jsx(WrappedComponent, { ...props }) });
|
|
138
|
+
}
|
|
139
|
+
WithProvider.displayName = `withProvider(${WrappedComponent.displayName ?? WrappedComponent.name ?? "Component"})`;
|
|
140
|
+
return WithProvider;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { StoreProvider, useStore, withProvider };
|
|
144
|
+
//# sourceMappingURL=index.js.map
|
|
145
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/singleton.ts","../src/hooks.ts","../src/use-selector.ts","../src/use-store.ts","../src/store-provider.tsx","../src/with-provider.tsx"],"names":["useCallback","useSyncExternalStore","useState","jsx"],"mappings":"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","file":"index.js","sourcesContent":["// @hurum/react — Singleton management\n// Manages per-StoreDefinition singleton instances and React contexts\n\nimport { createContext } from 'react'\nimport type { StoreDefinition, StoreInstance } from '@hurum/core'\n\nconst IS_DEV = typeof globalThis !== 'undefined'\n && typeof (globalThis as { process?: { env?: Record<string, string> } }).process !== 'undefined'\n && (globalThis as { process?: { env?: Record<string, string> } }).process?.env?.['NODE_ENV'] !== 'production'\n\n// WeakMaps keyed by StoreDefinition for per-store metadata\nconst singletonMap = new WeakMap<StoreDefinition, StoreInstance>()\nconst contextMap = new WeakMap<StoreDefinition, React.Context<StoreInstance | null>>()\n\n/** Stable context that always returns null — used when useStore receives an instance directly */\nexport const NULL_CONTEXT = createContext<StoreInstance | null>(null)\n\n/**\n * Get or create the singleton instance for a StoreDefinition.\n * Warns in dev mode if called on the server.\n */\nexport function getSingleton(def: StoreDefinition): StoreInstance {\n let instance = singletonMap.get(def)\n if (!instance) {\n if (IS_DEV && typeof window === 'undefined') {\n console.warn(\n '[hurum/react] Singleton accessed on the server. Use Provider + Store.create() for SSR.',\n )\n }\n instance = def.create()\n singletonMap.set(def, instance)\n }\n return instance\n}\n\n/**\n * Get or create the React Context for a StoreDefinition.\n * Each StoreDefinition gets its own Context.\n */\nexport function getContext(def: StoreDefinition): React.Context<StoreInstance | null> {\n let ctx = contextMap.get(def)\n if (!ctx) {\n ctx = createContext<StoreInstance | null>(null)\n contextMap.set(def, ctx)\n }\n return ctx\n}\n","// @hurum/react — Core hooks using useSyncExternalStore\n\nimport { useCallback, useSyncExternalStore } from 'react'\nimport type { StoreInstance } from '@hurum/core'\n\n/**\n * Create a `use.fieldName()` hook for a specific field on a specific store instance.\n * Uses `useSyncExternalStore` internally.\n */\nfunction createFieldHook(getStore: () => StoreInstance, fieldName: string) {\n return function useField() {\n const store = getStore()\n const subscribe = useCallback(\n (onStoreChange: () => void) => store.subscribe(onStoreChange),\n [store],\n )\n const getSnapshot = useCallback(\n () => (store.getState() as Record<string, unknown>)[fieldName],\n [store],\n )\n // getServerSnapshot returns the same snapshot — the store instance\n // is already initialized with correct state on the server via Store.create()\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n }\n}\n\n/**\n * Create the `use.*` proxy for a store.\n * Accessing `use.fieldName` returns a hook that reads that field.\n */\nexport function createUseProxy(getStore: () => StoreInstance): Record<string, () => unknown> {\n const hookCache = new Map<string, () => unknown>()\n return new Proxy({} as Record<string, () => unknown>, {\n get(_target, prop) {\n if (typeof prop !== 'string') return undefined\n let hook = hookCache.get(prop)\n if (!hook) {\n hook = createFieldHook(getStore, prop)\n hookCache.set(prop, hook)\n }\n return hook\n },\n })\n}\n","// @hurum/react — useSelector hook\n\nimport { useCallback, useRef, useSyncExternalStore } from 'react'\nimport type { StoreInstance, ResolvedState, Selector } from '@hurum/core'\nimport { isSelector, structuralEqual } from '@hurum/core'\n\n/**\n * React hook for derived state from a store.\n *\n * Accepts either:\n * - An inline selector function: `(state) => derivedValue`\n * - A pre-built Selector object from `store.selector(fn)`\n *\n * Uses structural equality to prevent unnecessary re-renders.\n */\nexport function useSelectorHook<TRawState, TComputed, T>(\n store: StoreInstance<unknown, TRawState, TComputed>,\n fnOrSelector: ((state: ResolvedState<TRawState> & TComputed) => T) | Selector<T>,\n): T {\n if (isSelector(fnOrSelector)) {\n return useSelectorFromObject(fnOrSelector)\n }\n return useSelectorFromFn(store, fnOrSelector)\n}\n\nfunction useSelectorFromObject<T>(selector: Selector<T>): T {\n const subscribe = useCallback(\n (onStoreChange: () => void) => selector.subscribe(() => onStoreChange()),\n [selector],\n )\n return useSyncExternalStore(subscribe, selector.get, selector.get)\n}\n\nfunction useSelectorFromFn<TRawState, TComputed, T>(\n store: StoreInstance<unknown, TRawState, TComputed>,\n selectorFn: (state: ResolvedState<TRawState> & TComputed) => T,\n): T {\n // Ref for latest selector fn — keeps subscribe/getSnapshot stable\n const fnRef = useRef(selectorFn)\n fnRef.current = selectorFn\n\n // Ref for structural equality memoization\n const lastRef = useRef<{ value: T; initialized: boolean }>({ value: undefined as T, initialized: false })\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => store.subscribe(onStoreChange),\n [store],\n )\n\n const getSnapshot = useCallback((): T => {\n const state = store.getState()\n const next = fnRef.current(state)\n\n if (lastRef.current.initialized && structuralEqual(lastRef.current.value, next)) {\n return lastRef.current.value\n }\n\n lastRef.current = { value: next, initialized: true }\n return next\n }, [store])\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n","// @hurum/react — useStore hook\n\nimport { useContext, useMemo } from 'react'\nimport type { StoreDefinition, StoreInstance, IntentRef, ResolvedState, ScopeOf, SendFn, Selector } from '@hurum/core'\nimport { isStoreDefinition } from '@hurum/core'\nimport type { EventInstance } from '@hurum/core'\nimport { getContext, getSingleton, NULL_CONTEXT } from './singleton'\nimport { createUseProxy } from './hooks'\nimport { useSelectorHook } from './use-selector'\n\n/**\n * The object returned by useStore(). Provides:\n * - `.use.fieldName()` hooks for reading individual fields\n * - `.useSelector(fn)` for derived state with structural equality\n * - `.send()` for dispatching intents (supports PreparedIntent + named shortcuts)\n * - `.cancel()` / `.cancelAll()` for cancellation\n * - `.subscribe()` for event subscriptions\n * - `.scope` for nested store access\n * - `.getState()` for reading full state\n * - `.dispose()` for cleanup\n */\nexport interface UseStoreReturn<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {\n readonly use: {\n readonly [K in keyof (ResolvedState<TRawState> & TComputed)]: () => (ResolvedState<TRawState> & TComputed)[K]\n }\n /** Derived state hook with structural equality memoization. */\n useSelector<T>(fn: (state: ResolvedState<TRawState> & TComputed) => T): T\n useSelector<T>(selector: Selector<T>): T\n send: SendFn<TIntents>\n cancel(ref: IntentRef): void\n cancelAll(): void\n getState(): ResolvedState<TRawState> & TComputed\n subscribe(cb: (state: ResolvedState<TRawState> & TComputed) => void): () => void\n subscribe(type: 'events', cb: (event: EventInstance) => void): () => void\n dispose(): void\n readonly scope: ScopeOf<TRawState>\n}\n\n/**\n * Context-aware hook to get a store handle.\n *\n * Accepts either a StoreDefinition or a StoreInstance:\n * - `useStore(def)` — Inside Provider → scoped instance. Outside → singleton fallback.\n * - `useStore(instance)` — Uses the given instance directly.\n *\n * The returned object has `.use.fieldName()` hooks for granular subscriptions.\n */\nexport function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n def: StoreDefinition<TDeps, TRawState, TComputed, TIntents>,\n): UseStoreReturn<TRawState, TComputed, TIntents>\nexport function useStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n instance: StoreInstance<TDeps, TRawState, TComputed, TIntents>,\n): UseStoreReturn<TRawState, TComputed, TIntents>\nexport function useStore(\n defOrInstance: StoreDefinition | StoreInstance,\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): UseStoreReturn<any, any, any> {\n const isDef = isStoreDefinition(defOrInstance)\n // Always call useContext unconditionally (hooks rules). NULL_CONTEXT when receiving instance.\n const ctx = useContext(isDef ? getContext(defOrInstance as StoreDefinition) : NULL_CONTEXT)\n const store = isDef\n ? (ctx ?? getSingleton(defOrInstance as StoreDefinition))\n : (defOrInstance as StoreInstance)\n return useStoreHandle(store)\n}\n\nfunction useStoreHandle<TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n store: StoreInstance<unknown, TRawState, TComputed, TIntents>,\n): UseStoreReturn<TRawState, TComputed, TIntents> {\n const handle = useMemo(() => {\n const useProxy = createUseProxy(\n () => store,\n )\n return {\n use: useProxy as UseStoreReturn<TRawState, TComputed, TIntents>['use'],\n useSelector: <T>(fnOrSelector: ((state: ResolvedState<TRawState> & TComputed) => T) | Selector<T>): T => {\n return useSelectorHook(store, fnOrSelector)\n },\n // Use store.send directly — it's already a Proxy with intent shortcuts.\n // Don't use .bind() which would create a plain function losing the Proxy.\n send: store.send as SendFn<TIntents>,\n cancel: store.cancel.bind(store),\n cancelAll: store.cancelAll.bind(store),\n getState: store.getState.bind(store) as () => ResolvedState<TRawState> & TComputed,\n subscribe: store.subscribe.bind(store) as UseStoreReturn<TRawState, TComputed, TIntents>['subscribe'],\n dispose: store.dispose.bind(store),\n scope: store.scope as ScopeOf<TRawState>,\n }\n }, [store])\n\n return handle\n}\n","// @hurum/react — StoreProvider component\n\nimport { useState, type ReactNode } from 'react'\nimport type { StoreDefinition, StoreInstance } from '@hurum/core'\nimport { getContext } from './singleton'\n\nexport type StoreProviderProps =\n | {\n of: StoreDefinition\n store: StoreInstance\n children: ReactNode\n }\n | {\n of: StoreDefinition\n store?: undefined\n initialState?: unknown\n deps?: unknown\n children: ReactNode\n }\n\n/**\n * Provides a scoped store instance to descendant components.\n *\n * Two modes:\n * - `<StoreProvider of={Def} store={instance}>` — uses the given instance\n * - `<StoreProvider of={Def}>` — auto-creates a new instance (with optional initialState/deps)\n *\n * Does NOT auto-dispose on unmount (React Strict Mode compatible).\n */\nexport function StoreProvider(props: StoreProviderProps): ReactNode {\n const { of: def, children } = props\n const Context = getContext(def)\n const externalStore = props.store\n\n // Always call useState for hook order stability\n const [autoStore] = useState(() => {\n if (externalStore) return null\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return def.create(props as any)\n })\n\n const instance = externalStore ?? autoStore!\n\n return <Context.Provider value={instance}>{children}</Context.Provider>\n}\n","// @hurum/react — withProvider HOC\n\nimport { useState, type ComponentType } from 'react'\nimport type { StoreDefinition } from '@hurum/core'\nimport { getContext } from './singleton'\n\n/**\n * HOC that wraps a component with a fresh store instance on each mount.\n * Useful for modals, wizards, and other isolated UI.\n */\nexport function withProvider<P extends object>(\n def: StoreDefinition,\n WrappedComponent: ComponentType<P>,\n): ComponentType<P> {\n const Context = getContext(def)\n\n function WithProvider(props: P) {\n const [store] = useState(() => def.create())\n return (\n <Context.Provider value={store}>\n <WrappedComponent {...props} />\n </Context.Provider>\n )\n }\n\n WithProvider.displayName = `withProvider(${WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component'})`\n return WithProvider\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hurum/react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React bindings for @hurum/core — useStore, StoreProvider, withProvider",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@hurum/core": "0.0.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@testing-library/react": "^16.2.0",
|
|
32
|
+
"@types/react": "^19.0.8",
|
|
33
|
+
"react": "^19.0.0",
|
|
34
|
+
"react-dom": "^19.0.0",
|
|
35
|
+
"tsup": "^8.4.0",
|
|
36
|
+
"vitest": "^3.0.5",
|
|
37
|
+
"typescript": "^5.7.3",
|
|
38
|
+
"jsdom": "^26.0.0"
|
|
39
|
+
},
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"test:typecheck": "vitest typecheck",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"clean": "rm -rf dist .turbo"
|
|
49
|
+
}
|
|
50
|
+
}
|