storion 0.9.0 → 0.10.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/CHANGELOG.md +62 -4
- package/README.md +65 -2004
- package/dist/async/abortable.d.ts +295 -0
- package/dist/async/abortable.d.ts.map +1 -0
- package/dist/async/async.d.ts +86 -5
- package/dist/async/async.d.ts.map +1 -1
- package/dist/async/context.d.ts +15 -0
- package/dist/async/context.d.ts.map +1 -0
- package/dist/async/index.d.ts +16 -3
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +407 -137
- package/dist/async/safe.d.ts +221 -0
- package/dist/async/safe.d.ts.map +1 -0
- package/dist/async/types.d.ts +77 -29
- package/dist/async/types.d.ts.map +1 -1
- package/dist/async/wrappers.d.ts +217 -0
- package/dist/async/wrappers.d.ts.map +1 -0
- package/dist/core/effect.d.ts +34 -26
- package/dist/core/effect.d.ts.map +1 -1
- package/dist/core/equality.d.ts +25 -0
- package/dist/core/equality.d.ts.map +1 -1
- package/dist/core/focus.d.ts +20 -0
- package/dist/core/focus.d.ts.map +1 -0
- package/dist/core/focusHelpers.d.ts +258 -0
- package/dist/core/focusHelpers.d.ts.map +1 -0
- package/dist/core/middleware.d.ts +4 -4
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/storeContext.d.ts +2 -9
- package/dist/core/storeContext.d.ts.map +1 -1
- package/dist/dev.d.ts +0 -10
- package/dist/dev.d.ts.map +1 -1
- package/dist/{index-C8B6Mo8r.js → effect-BDQU8Voz.js} +1241 -583
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/network/index.d.ts +69 -0
- package/dist/network/index.d.ts.map +1 -0
- package/dist/network/retry.d.ts +53 -0
- package/dist/network/retry.d.ts.map +1 -0
- package/dist/network/services.d.ts +58 -0
- package/dist/network/services.d.ts.map +1 -0
- package/dist/network/store.d.ts +36 -0
- package/dist/network/store.d.ts.map +1 -0
- package/dist/network/utils.d.ts +9 -0
- package/dist/network/utils.d.ts.map +1 -0
- package/dist/persist/index.d.ts +1 -1
- package/dist/persist/index.d.ts.map +1 -1
- package/dist/persist/index.js +11 -9
- package/dist/persist/persist.d.ts +14 -14
- package/dist/persist/persist.d.ts.map +1 -1
- package/dist/pool.d.ts +77 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +245 -244
- package/dist/react/stable.d.ts +27 -0
- package/dist/react/stable.d.ts.map +1 -0
- package/dist/react/useStore.d.ts +38 -13
- package/dist/react/useStore.d.ts.map +1 -1
- package/dist/react/withStore.d.ts.map +1 -1
- package/dist/storion.js +911 -37
- package/dist/trigger.d.ts +12 -7
- package/dist/trigger.d.ts.map +1 -1
- package/dist/types.d.ts +133 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/storeTuple.d.ts +7 -0
- package/dist/utils/storeTuple.d.ts.map +1 -0
- package/package.json +5 -1
- package/dist/collection.d.ts +0 -34
- package/dist/collection.d.ts.map +0 -1
- package/dist/core/proxy.d.ts +0 -47
- package/dist/core/proxy.d.ts.map +0 -1
- package/dist/effect-C6h0PDDI.js +0 -446
- package/dist/isPromiseLike-bFkfHAbm.js +0 -6
- package/dist/react/useLocalStore.d.ts +0 -48
- package/dist/react/useLocalStore.d.ts.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import { memo, useRef, useMemo, createElement, createContext, useContext, useReducer, useEffect, useLayoutEffect,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { P as ProviderMissingError,
|
|
8
|
-
import { E, H, I,
|
|
4
|
+
import { memo, useRef, useMemo, createElement, createContext, useContext, useId, useReducer, useState, useEffect, useLayoutEffect, forwardRef } from "react";
|
|
5
|
+
import { container } from "../storion.js";
|
|
6
|
+
import { append, applyExcept, applyFor, clamp, decrement, divide, forStores, increment, list, map, merge, multiply, prepend, reset, resolver, toggle, trigger, withMeta } from "../storion.js";
|
|
7
|
+
import { P as ProviderMissingError, w as withHooks, d as AsyncFunctionError, t as tryStabilize, h as strictEqual, j as ScopedOutsideSelectorError, s as storeTuple, i as isSpec, k as STORION_TYPE, l as resolveEquality, a as store } from "../effect-BDQU8Voz.js";
|
|
8
|
+
import { E, H, I, L, z, S, y, x, n, v, e, o, m, p, B, q, u } from "../effect-BDQU8Voz.js";
|
|
9
|
+
import { e as emitter } from "../emitter-j4rC71vY.js";
|
|
9
10
|
import { jsx } from "react/jsx-runtime";
|
|
10
11
|
import { m as m2 } from "../meta-40r-AZfe.js";
|
|
11
12
|
const StoreContext = createContext(null);
|
|
@@ -35,185 +36,28 @@ function useContainer() {
|
|
|
35
36
|
}
|
|
36
37
|
return ctx;
|
|
37
38
|
}
|
|
38
|
-
function checkIsDev() {
|
|
39
|
-
if (typeof __DEV__ !== "undefined") {
|
|
40
|
-
return __DEV__;
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
return process.env.NODE_ENV !== "production";
|
|
44
|
-
} catch {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function dev(fn) {
|
|
49
|
-
const isDev = checkIsDev();
|
|
50
|
-
if (fn) {
|
|
51
|
-
if (isDev) {
|
|
52
|
-
fn();
|
|
53
|
-
}
|
|
54
|
-
return isDev;
|
|
55
|
-
}
|
|
56
|
-
return isDev;
|
|
57
|
-
}
|
|
58
|
-
((dev2) => {
|
|
59
|
-
function log(message, ...args) {
|
|
60
|
-
if (checkIsDev()) {
|
|
61
|
-
console.log(`[rextive] ${message}`, ...args);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
dev2.log = log;
|
|
65
|
-
function warn(message, ...args) {
|
|
66
|
-
if (checkIsDev()) {
|
|
67
|
-
console.warn(`[rextive] ${message}`, ...args);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
dev2.warn = warn;
|
|
71
|
-
function error(message, ...args) {
|
|
72
|
-
if (checkIsDev()) {
|
|
73
|
-
console.error(`[rextive] ${message}`, ...args);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
dev2.error = error;
|
|
77
|
-
function assert(condition, message) {
|
|
78
|
-
if (checkIsDev()) {
|
|
79
|
-
if (!condition) {
|
|
80
|
-
throw new Error(`[rextive] Assertion failed: ${message}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
dev2.assert = assert;
|
|
85
|
-
})(dev || (dev = {}));
|
|
86
|
-
const isServer = typeof window === "undefined";
|
|
87
|
-
const useIsomorphicLayoutEffect = isServer ? useEffect : useLayoutEffect;
|
|
88
|
-
const shouldScheduleDispose = !isServer && typeof useLayoutEffect === "function" && dev();
|
|
89
|
-
function useLocalStore(spec) {
|
|
90
|
-
var _a;
|
|
91
|
-
const [, forceUpdate] = useReducer((x2) => x2 + 1, 0);
|
|
92
|
-
const prevControllerRef = useRef(null);
|
|
93
|
-
const controller = useMemo(() => new LocalStoreController(spec), [spec]);
|
|
94
|
-
if (prevControllerRef.current !== controller) {
|
|
95
|
-
(_a = prevControllerRef.current) == null ? void 0 : _a.dispose();
|
|
96
|
-
prevControllerRef.current = controller;
|
|
97
|
-
}
|
|
98
|
-
const store2 = controller.getStore();
|
|
99
|
-
useIsomorphicLayoutEffect(() => {
|
|
100
|
-
controller.commit();
|
|
101
|
-
const unsubscribe = store2.subscribe(() => forceUpdate());
|
|
102
|
-
return () => {
|
|
103
|
-
unsubscribe();
|
|
104
|
-
controller.uncommit();
|
|
105
|
-
};
|
|
106
|
-
}, [controller, store2]);
|
|
107
|
-
return useMemo(
|
|
108
|
-
() => [
|
|
109
|
-
store2.state,
|
|
110
|
-
store2.actions,
|
|
111
|
-
{ dirty: store2.dirty, reset: store2.reset }
|
|
112
|
-
],
|
|
113
|
-
[store2]
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
class LocalStoreController {
|
|
117
|
-
constructor(spec) {
|
|
118
|
-
/** Whether the effect has committed (is active) */
|
|
119
|
-
__publicField(this, "_committed", false);
|
|
120
|
-
/** Whether this controller has been disposed */
|
|
121
|
-
__publicField(this, "_disposed", false);
|
|
122
|
-
/** Isolated container for this local store */
|
|
123
|
-
__publicField(this, "_container");
|
|
124
|
-
/** The store instance */
|
|
125
|
-
__publicField(this, "_store");
|
|
126
|
-
/**
|
|
127
|
-
* Dispose the controller and its store.
|
|
128
|
-
* Safe to call multiple times.
|
|
129
|
-
*/
|
|
130
|
-
__publicField(this, "dispose", () => {
|
|
131
|
-
var _a, _b;
|
|
132
|
-
if (this._disposed) return;
|
|
133
|
-
this._disposed = true;
|
|
134
|
-
(_a = this._store) == null ? void 0 : _a.dispose();
|
|
135
|
-
(_b = this._container) == null ? void 0 : _b.clear();
|
|
136
|
-
this._store = void 0;
|
|
137
|
-
this._container = void 0;
|
|
138
|
-
});
|
|
139
|
-
/**
|
|
140
|
-
* Schedule disposal check via microtask.
|
|
141
|
-
*
|
|
142
|
-
* This deferred check is crucial for StrictMode:
|
|
143
|
-
* - StrictMode runs cleanup then effect again synchronously
|
|
144
|
-
* - The microtask runs AFTER the re-commit, so store survives
|
|
145
|
-
* - On real unmount, no re-commit happens, so disposal proceeds
|
|
146
|
-
*/
|
|
147
|
-
__publicField(this, "_disposeIfUnused", () => {
|
|
148
|
-
if (this._committed || this._disposed) return;
|
|
149
|
-
if (shouldScheduleDispose) {
|
|
150
|
-
Promise.resolve().then(() => {
|
|
151
|
-
if (!this._committed) {
|
|
152
|
-
this.dispose();
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
} else {
|
|
156
|
-
this.dispose();
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
/**
|
|
160
|
-
* Get or create the store instance.
|
|
161
|
-
* Throws if the store has dependencies (local stores must be isolated).
|
|
162
|
-
*/
|
|
163
|
-
__publicField(this, "getStore", () => {
|
|
164
|
-
var _a, _b;
|
|
165
|
-
if (this._store) return this._store;
|
|
166
|
-
if (!this._container) {
|
|
167
|
-
this._container = container();
|
|
168
|
-
}
|
|
169
|
-
this._store = this._container.get(this.spec);
|
|
170
|
-
const depsCount = ((_b = (_a = this._store) == null ? void 0 : _a.deps) == null ? void 0 : _b.length) ?? 0;
|
|
171
|
-
if (depsCount > 0) {
|
|
172
|
-
this.dispose();
|
|
173
|
-
throw new LocalStoreDependencyError(
|
|
174
|
-
this.spec.displayName,
|
|
175
|
-
depsCount
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
if (shouldScheduleDispose) {
|
|
179
|
-
this._disposeIfUnused();
|
|
180
|
-
}
|
|
181
|
-
return this._store;
|
|
182
|
-
});
|
|
183
|
-
/**
|
|
184
|
-
* Mark as committed (effect is active).
|
|
185
|
-
* Called at the start of useLayoutEffect.
|
|
186
|
-
*/
|
|
187
|
-
__publicField(this, "commit", () => {
|
|
188
|
-
this._committed = true;
|
|
189
|
-
});
|
|
190
|
-
/**
|
|
191
|
-
* Mark as uncommitted (effect cleaned up).
|
|
192
|
-
* Schedules deferred disposal check.
|
|
193
|
-
*/
|
|
194
|
-
__publicField(this, "uncommit", () => {
|
|
195
|
-
this._committed = false;
|
|
196
|
-
this._disposeIfUnused();
|
|
197
|
-
});
|
|
198
|
-
this.spec = spec;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
39
|
function useStoreWithContainer(selector, container2) {
|
|
40
|
+
const id = useId();
|
|
202
41
|
const [, forceUpdate] = useReducer((x2) => x2 + 1, 0);
|
|
203
42
|
const [refs] = useState(() => ({
|
|
204
|
-
|
|
205
|
-
stableFns: /* @__PURE__ */ new Map(),
|
|
43
|
+
prevValues: /* @__PURE__ */ new Map(),
|
|
206
44
|
trackedDeps: /* @__PURE__ */ new Map(),
|
|
207
45
|
subscriptions: /* @__PURE__ */ new Map(),
|
|
208
|
-
id
|
|
46
|
+
id,
|
|
209
47
|
onceRan: false
|
|
210
48
|
}));
|
|
49
|
+
const [scopeController] = useState(
|
|
50
|
+
() => new ScopeController(container2)
|
|
51
|
+
);
|
|
52
|
+
const [selectorExecution] = useState(() => ({ active: false }));
|
|
53
|
+
const [scheduledEffects] = useState(() => []);
|
|
211
54
|
refs.trackedDeps.clear();
|
|
212
55
|
const shouldRunOnce = !refs.onceRan;
|
|
213
56
|
const selectorContext = useMemo(() => {
|
|
214
57
|
const ctx = {
|
|
215
58
|
[STORION_TYPE]: "selector.context",
|
|
216
59
|
id: refs.id,
|
|
60
|
+
container: container2,
|
|
217
61
|
// Implementation handles both StoreSpec and Factory overloads
|
|
218
62
|
get(specOrFactory) {
|
|
219
63
|
if (!isSpec(specOrFactory)) {
|
|
@@ -226,10 +70,6 @@ function useStoreWithContainer(selector, container2) {
|
|
|
226
70
|
actions: instance.actions
|
|
227
71
|
});
|
|
228
72
|
},
|
|
229
|
-
// Create a fresh instance from a parameterized factory (bypasses cache)
|
|
230
|
-
create(factory, ...args) {
|
|
231
|
-
return container2.create(factory, ...args);
|
|
232
|
-
},
|
|
233
73
|
mixin(mixin, ...args) {
|
|
234
74
|
return mixin(ctx, ...args);
|
|
235
75
|
},
|
|
@@ -237,42 +77,65 @@ function useStoreWithContainer(selector, container2) {
|
|
|
237
77
|
if (shouldRunOnce) {
|
|
238
78
|
callback();
|
|
239
79
|
}
|
|
80
|
+
},
|
|
81
|
+
scoped(spec) {
|
|
82
|
+
if (!selectorExecution.active) {
|
|
83
|
+
throw new ScopedOutsideSelectorError();
|
|
84
|
+
}
|
|
85
|
+
const instance = scopeController.get(spec);
|
|
86
|
+
return storeTuple(instance);
|
|
240
87
|
}
|
|
241
88
|
};
|
|
242
89
|
return ctx;
|
|
243
|
-
}, [container2, shouldRunOnce]);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
90
|
+
}, [container2, shouldRunOnce, scopeController]);
|
|
91
|
+
let output;
|
|
92
|
+
selectorExecution.active = true;
|
|
93
|
+
try {
|
|
94
|
+
output = withHooks(
|
|
95
|
+
{
|
|
96
|
+
onRead: (event) => {
|
|
97
|
+
refs.trackedDeps.set(event.key, event);
|
|
98
|
+
},
|
|
99
|
+
// Collect effects to run in useEffect (not immediately)
|
|
100
|
+
// This enables effect() calls in selector to access component scope
|
|
101
|
+
scheduleEffect: (runEffect) => {
|
|
102
|
+
scheduledEffects.push(runEffect);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
const result = selector(selectorContext);
|
|
107
|
+
if (result && typeof result.then === "function") {
|
|
108
|
+
throw new AsyncFunctionError(
|
|
109
|
+
"useStore selector",
|
|
110
|
+
"Do not return a Promise from the selector function."
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const out = Array.isArray(result) ? [] : {};
|
|
114
|
+
for (const [key, value] of Object.entries(result)) {
|
|
115
|
+
const prev = refs.prevValues.get(key);
|
|
116
|
+
const [stableValue] = tryStabilize(prev, value, strictEqual);
|
|
117
|
+
out[key] = stableValue;
|
|
118
|
+
if (prev) {
|
|
119
|
+
prev.value = stableValue;
|
|
120
|
+
} else {
|
|
121
|
+
refs.prevValues.set(key, { value: stableValue });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
248
125
|
}
|
|
249
|
-
},
|
|
250
|
-
() => selector(selectorContext)
|
|
251
|
-
);
|
|
252
|
-
if (shouldRunOnce) {
|
|
253
|
-
refs.onceRan = true;
|
|
254
|
-
}
|
|
255
|
-
if (result && typeof result.then === "function") {
|
|
256
|
-
throw new AsyncFunctionError(
|
|
257
|
-
"useStore selector",
|
|
258
|
-
"Do not return a Promise from the selector function."
|
|
259
126
|
);
|
|
127
|
+
} finally {
|
|
128
|
+
selectorExecution.active = false;
|
|
260
129
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
for (const [key, value] of Object.entries(result)) {
|
|
264
|
-
if (typeof value === "function") {
|
|
265
|
-
if (!refs.stableFns.has(key)) {
|
|
266
|
-
refs.stableFns.set(key, (...args) => {
|
|
267
|
-
var _a, _b;
|
|
268
|
-
return (_b = (_a = refs.fresh) == null ? void 0 : _a[key]) == null ? void 0 : _b.call(_a, ...args);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
output[key] = refs.stableFns.get(key);
|
|
272
|
-
} else {
|
|
273
|
-
output[key] = value;
|
|
274
|
-
}
|
|
130
|
+
if (shouldRunOnce) {
|
|
131
|
+
refs.onceRan = true;
|
|
275
132
|
}
|
|
133
|
+
useIsomorphicLayoutEffect(() => {
|
|
134
|
+
scopeController.commit();
|
|
135
|
+
return () => {
|
|
136
|
+
scopeController.uncommit();
|
|
137
|
+
};
|
|
138
|
+
}, [scopeController]);
|
|
276
139
|
const trackedKeysToken = [...refs.trackedDeps.keys()].sort().join("|");
|
|
277
140
|
useEffect(() => {
|
|
278
141
|
var _a;
|
|
@@ -297,15 +160,107 @@ function useStoreWithContainer(selector, container2) {
|
|
|
297
160
|
refs.subscriptions.clear();
|
|
298
161
|
};
|
|
299
162
|
}, [trackedKeysToken]);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const disposers = emitter();
|
|
165
|
+
try {
|
|
166
|
+
for (const runEffect of scheduledEffects) {
|
|
167
|
+
disposers.on(runEffect());
|
|
168
|
+
}
|
|
169
|
+
} catch (ex) {
|
|
170
|
+
disposers.emitAndClear();
|
|
171
|
+
throw ex;
|
|
172
|
+
}
|
|
173
|
+
return () => {
|
|
174
|
+
disposers.emitAndClear();
|
|
175
|
+
};
|
|
176
|
+
});
|
|
300
177
|
return output;
|
|
301
178
|
}
|
|
302
|
-
function useStore(
|
|
303
|
-
const isSpec2 = typeof selectorOrSpec === "object" && selectorOrSpec !== null && "options" in selectorOrSpec;
|
|
304
|
-
if (isSpec2) {
|
|
305
|
-
return useLocalStore(selectorOrSpec);
|
|
306
|
-
}
|
|
179
|
+
function useStore(selector) {
|
|
307
180
|
const container2 = useContainer();
|
|
308
|
-
return useStoreWithContainer(
|
|
181
|
+
return useStoreWithContainer(selector, container2);
|
|
182
|
+
}
|
|
183
|
+
const isServer = typeof window === "undefined";
|
|
184
|
+
const useIsomorphicLayoutEffect = isServer ? useEffect : useLayoutEffect;
|
|
185
|
+
const shouldScheduleDispose = !isServer && typeof useLayoutEffect === "function";
|
|
186
|
+
class ScopeController {
|
|
187
|
+
constructor(container2) {
|
|
188
|
+
/** Whether the effect has committed (is active) */
|
|
189
|
+
__publicField(this, "_committed", false);
|
|
190
|
+
/** Whether this controller has been disposed */
|
|
191
|
+
__publicField(this, "_disposed", false);
|
|
192
|
+
/** Whether a disposal check microtask is pending */
|
|
193
|
+
__publicField(this, "_pendingDisposalCheck", false);
|
|
194
|
+
__publicField(this, "_stores", /* @__PURE__ */ new Map());
|
|
195
|
+
/**
|
|
196
|
+
* Dispose the controller and its store.
|
|
197
|
+
* Safe to call multiple times.
|
|
198
|
+
*/
|
|
199
|
+
__publicField(this, "dispose", () => {
|
|
200
|
+
if (this._disposed) return;
|
|
201
|
+
this._disposed = true;
|
|
202
|
+
for (const store2 of this._stores.values()) {
|
|
203
|
+
store2.dispose();
|
|
204
|
+
}
|
|
205
|
+
this._stores.clear();
|
|
206
|
+
});
|
|
207
|
+
/**
|
|
208
|
+
* Schedule disposal check via microtask.
|
|
209
|
+
*
|
|
210
|
+
* This deferred check is crucial for StrictMode:
|
|
211
|
+
* - StrictMode runs cleanup then effect again synchronously
|
|
212
|
+
* - The microtask runs AFTER the re-commit, so store survives
|
|
213
|
+
* - On real unmount, no re-commit happens, so disposal proceeds
|
|
214
|
+
*/
|
|
215
|
+
__publicField(this, "_disposeIfUnused", () => {
|
|
216
|
+
if (this._committed || this._disposed || this._pendingDisposalCheck) return;
|
|
217
|
+
if (shouldScheduleDispose) {
|
|
218
|
+
this._pendingDisposalCheck = true;
|
|
219
|
+
Promise.resolve().then(() => {
|
|
220
|
+
this._pendingDisposalCheck = false;
|
|
221
|
+
if (!this._committed) {
|
|
222
|
+
this.dispose();
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
this.dispose();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
/**
|
|
230
|
+
* Get or create the store instance.
|
|
231
|
+
*/
|
|
232
|
+
__publicField(this, "get", (spec) => {
|
|
233
|
+
if (this._disposed) {
|
|
234
|
+
throw new Error("ScopeController has been disposed");
|
|
235
|
+
}
|
|
236
|
+
let store2 = this._stores.get(spec);
|
|
237
|
+
if (!store2) {
|
|
238
|
+
store2 = this.container.create(spec);
|
|
239
|
+
this._stores.set(spec, store2);
|
|
240
|
+
}
|
|
241
|
+
if (shouldScheduleDispose && !this._committed) {
|
|
242
|
+
this._disposeIfUnused();
|
|
243
|
+
}
|
|
244
|
+
return store2;
|
|
245
|
+
});
|
|
246
|
+
/**
|
|
247
|
+
* Mark as committed (effect is active).
|
|
248
|
+
* Called at the start of useLayoutEffect.
|
|
249
|
+
*/
|
|
250
|
+
__publicField(this, "commit", () => {
|
|
251
|
+
this._committed = true;
|
|
252
|
+
this._pendingDisposalCheck = false;
|
|
253
|
+
});
|
|
254
|
+
/**
|
|
255
|
+
* Mark as uncommitted (effect cleaned up).
|
|
256
|
+
* Schedules deferred disposal check.
|
|
257
|
+
*/
|
|
258
|
+
__publicField(this, "uncommit", () => {
|
|
259
|
+
this._committed = false;
|
|
260
|
+
this._disposeIfUnused();
|
|
261
|
+
});
|
|
262
|
+
this.container = container2;
|
|
263
|
+
}
|
|
309
264
|
}
|
|
310
265
|
function createWithStore(useContextHook) {
|
|
311
266
|
return function boundWithStore2(hook, renderOrOptions, maybeOptions) {
|
|
@@ -380,7 +335,9 @@ function createWithStore(useContextHook) {
|
|
|
380
335
|
return hoc;
|
|
381
336
|
};
|
|
382
337
|
}
|
|
383
|
-
const boundWithStore = createWithStore(
|
|
338
|
+
const boundWithStore = createWithStore(
|
|
339
|
+
useStore
|
|
340
|
+
);
|
|
384
341
|
function withStore(hook, renderOrOptions, maybeOptions) {
|
|
385
342
|
return boundWithStore(hook, renderOrOptions, maybeOptions);
|
|
386
343
|
}
|
|
@@ -403,52 +360,96 @@ function create(storeOptions, containerOptions) {
|
|
|
403
360
|
const withStore2 = createWithStore(useCreatedStoreContext);
|
|
404
361
|
return [instance, useCreatedStore, withStore2];
|
|
405
362
|
}
|
|
363
|
+
function stable(Component, customEquality) {
|
|
364
|
+
const isForwardRefComponent = Component.$$typeof === Symbol.for("react.forward_ref");
|
|
365
|
+
const expectsRef = isForwardRefComponent || typeof Component === "function" && Component.length >= 2;
|
|
366
|
+
const StableComponent = forwardRef((props, ref) => {
|
|
367
|
+
const inputProps = props;
|
|
368
|
+
const [refs] = useState(() => ({
|
|
369
|
+
prevValues: /* @__PURE__ */ new Map(),
|
|
370
|
+
equalityFns: /* @__PURE__ */ new Map()
|
|
371
|
+
}));
|
|
372
|
+
const stableProps = {};
|
|
373
|
+
for (const key of Object.keys(inputProps)) {
|
|
374
|
+
const keyStr = key;
|
|
375
|
+
const value = inputProps[key];
|
|
376
|
+
if (!refs.equalityFns.has(keyStr)) {
|
|
377
|
+
const equalityConfig = customEquality == null ? void 0 : customEquality[key];
|
|
378
|
+
refs.equalityFns.set(keyStr, resolveEquality(equalityConfig));
|
|
379
|
+
}
|
|
380
|
+
const equalityFn = refs.equalityFns.get(keyStr);
|
|
381
|
+
const prev = refs.prevValues.get(keyStr);
|
|
382
|
+
const [stableValue] = tryStabilize(prev, value, equalityFn);
|
|
383
|
+
stableProps[key] = stableValue;
|
|
384
|
+
if (prev) {
|
|
385
|
+
prev.value = stableValue;
|
|
386
|
+
} else {
|
|
387
|
+
refs.prevValues.set(keyStr, { value: stableValue });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (isForwardRefComponent) {
|
|
391
|
+
return /* @__PURE__ */ jsx(Component, { ...stableProps, ref });
|
|
392
|
+
}
|
|
393
|
+
if (expectsRef) {
|
|
394
|
+
return Component(
|
|
395
|
+
stableProps,
|
|
396
|
+
ref
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
const Comp = Component;
|
|
400
|
+
return /* @__PURE__ */ jsx(Comp, { ...stableProps });
|
|
401
|
+
});
|
|
402
|
+
const componentName = Component.displayName || Component.name || "Component";
|
|
403
|
+
StableComponent.displayName = `Stable(${componentName})`;
|
|
404
|
+
return StableComponent;
|
|
405
|
+
}
|
|
406
406
|
export {
|
|
407
407
|
AsyncFunctionError,
|
|
408
408
|
E as EffectRefreshError,
|
|
409
409
|
H as HooksContextError,
|
|
410
410
|
I as InvalidActionError,
|
|
411
|
-
|
|
412
|
-
LocalStoreDependencyError,
|
|
411
|
+
L as LifetimeMismatchError,
|
|
412
|
+
z as LocalStoreDependencyError,
|
|
413
413
|
ProviderMissingError,
|
|
414
414
|
STORION_TYPE,
|
|
415
|
-
|
|
416
|
-
|
|
415
|
+
S as SetupPhaseError,
|
|
416
|
+
y as StoreDisposedError,
|
|
417
417
|
StoreProvider,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
418
|
+
x as StorionError,
|
|
419
|
+
append,
|
|
420
|
+
applyExcept,
|
|
421
|
+
applyFor,
|
|
422
|
+
n as batch,
|
|
423
|
+
clamp,
|
|
422
424
|
container,
|
|
423
425
|
create,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
k as isSelectorContext,
|
|
436
|
-
isSpec,
|
|
437
|
-
e as isStore,
|
|
438
|
-
j as isStoreContext,
|
|
439
|
-
b as isStorion,
|
|
440
|
-
z as isWrappedFn,
|
|
426
|
+
decrement,
|
|
427
|
+
v as deepEqual,
|
|
428
|
+
divide,
|
|
429
|
+
e as effect,
|
|
430
|
+
o as equality,
|
|
431
|
+
forStores,
|
|
432
|
+
increment,
|
|
433
|
+
m as is,
|
|
434
|
+
list,
|
|
435
|
+
map,
|
|
436
|
+
merge,
|
|
441
437
|
m2 as meta,
|
|
438
|
+
multiply,
|
|
442
439
|
p as pick,
|
|
443
|
-
|
|
440
|
+
B as pool,
|
|
441
|
+
prepend,
|
|
442
|
+
reset,
|
|
443
|
+
resolver,
|
|
444
|
+
q as shallowEqual,
|
|
445
|
+
stable,
|
|
444
446
|
store,
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
strictEqual,
|
|
448
|
+
toggle,
|
|
449
|
+
trigger,
|
|
450
|
+
u as untrack,
|
|
449
451
|
useContainer,
|
|
450
452
|
useStore,
|
|
451
|
-
|
|
452
|
-
withStore
|
|
453
|
-
x as wrapFn
|
|
453
|
+
withMeta,
|
|
454
|
+
withStore
|
|
454
455
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ComponentType, ForwardRefExoticComponent, RefAttributes, ForwardRefRenderFunction } from 'react';
|
|
2
|
+
import { Equality } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for per-prop equality checking.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* stable(Component, {
|
|
10
|
+
* data: "shallow", // Use shallow equality
|
|
11
|
+
* items: "deep", // Use deep equality
|
|
12
|
+
* config: (a, b) => a.id === b.id, // Custom equality
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export type PropEqualityConfig<TProps extends object> = {
|
|
17
|
+
[K in keyof TProps]?: Equality<TProps[K]>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Wrap a component with automatic prop stabilization.
|
|
21
|
+
*
|
|
22
|
+
* @param Component - The component to wrap (function component, forwardRef, or render function)
|
|
23
|
+
* @param customEquality - Optional per-prop equality configuration
|
|
24
|
+
* @returns A forwardRef component with stabilized props
|
|
25
|
+
*/
|
|
26
|
+
export declare function stable<TProps extends object, TRef = unknown>(Component: ComponentType<TProps> | ForwardRefRenderFunction<TRef, TProps>, customEquality?: PropEqualityConfig<NoInfer<TProps>>): ForwardRefExoticComponent<TProps & RefAttributes<TRef>>;
|
|
27
|
+
//# sourceMappingURL=stable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stable.d.ts","sourceRoot":"","sources":["../../src/react/stable.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAE9B,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAOzC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,CAAC,MAAM,SAAS,MAAM,IAAI;KACrD,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;CAC1C,CAAC;AAiBF;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,MAAM,SAAS,MAAM,EAAE,IAAI,GAAG,OAAO,EAC1D,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,wBAAwB,CAAC,IAAI,EAAE,MAAM,CAAC,EACzE,cAAc,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GACnD,yBAAyB,CAAC,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CA+EzD"}
|
package/dist/react/useStore.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LocalStoreResult } from './useLocalStore';
|
|
1
|
+
import { StoreContainer, Selector, StableResult } from '../types';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Core hook implementation that accepts container as parameter.
|
|
@@ -9,24 +8,50 @@ export declare function useStoreWithContainer<T extends object>(selector: Select
|
|
|
9
8
|
/**
|
|
10
9
|
* React hook to consume stores with automatic optimization.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Multi-store access via `get()` for global stores
|
|
13
|
+
* - Component-local stores via `scoped()` (auto-disposed on unmount)
|
|
14
|
+
* - Component-scoped effects via `effect()` with access to external values
|
|
15
|
+
* - Auto-stable functions (never cause re-renders)
|
|
16
|
+
* - Fine-grained updates (only re-renders when selected values change)
|
|
14
17
|
*
|
|
15
|
-
* @
|
|
16
|
-
* The store is isolated, disposed on unmount, and cannot have dependencies.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
18
|
+
* @example Basic usage
|
|
19
19
|
* ```tsx
|
|
20
|
-
* // With selector - access global stores
|
|
21
20
|
* const { count, increment } = useStore(({ get }) => {
|
|
22
|
-
* const [state, actions] = get(
|
|
21
|
+
* const [state, actions] = get(counterStore);
|
|
23
22
|
* return { count: state.count, increment: actions.increment };
|
|
24
23
|
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example Component-local stores
|
|
27
|
+
* ```tsx
|
|
28
|
+
* const { form } = useStore(({ scoped }) => {
|
|
29
|
+
* const [formState, formActions] = scoped(formStore);
|
|
30
|
+
* return { form: { ...formState, ...formActions } };
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example Effects with access to external values (refs, props, hooks)
|
|
35
|
+
* ```tsx
|
|
36
|
+
* function SearchPage() {
|
|
37
|
+
* const inputRef = useRef<HTMLInputElement>(null);
|
|
38
|
+
* const location = useLocation();
|
|
39
|
+
*
|
|
40
|
+
* const { query } = useStore(({ get }) => {
|
|
41
|
+
* const [state] = get(searchStore);
|
|
42
|
+
*
|
|
43
|
+
* // Effect runs in useEffect - has access to refs, props, and hooks
|
|
44
|
+
* // Auto-tracks store state, re-runs when tracked values change
|
|
45
|
+
* effect(() => {
|
|
46
|
+
* if (location.pathname === '/search' && state.isReady) {
|
|
47
|
+
* inputRef.current?.focus();
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
25
50
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
51
|
+
* return { query: state.query };
|
|
52
|
+
* });
|
|
53
|
+
* }
|
|
28
54
|
* ```
|
|
29
55
|
*/
|
|
30
56
|
export declare function useStore<T extends object>(selector: Selector<T>): StableResult<T>;
|
|
31
|
-
export declare function useStore<TState extends StateBase, TActions extends ActionsBase>(spec: StoreSpec<TState, TActions>): LocalStoreResult<TState, TActions>;
|
|
32
57
|
//# sourceMappingURL=useStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../../src/react/useStore.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../../src/react/useStore.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,EAKL,KAAK,cAAc,EAEnB,KAAK,QAAQ,EACb,KAAK,YAAY,EAElB,MAAM,UAAU,CAAC;AA6BlB;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACpD,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EACrB,SAAS,EAAE,cAAc,GACxB,YAAY,CAAC,CAAC,CAAC,CAyNjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EACvC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GACpB,YAAY,CAAC,CAAC,CAAC,CAKjB"}
|