juststore 1.2.0 → 1.2.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/src/node.js DELETED
@@ -1,374 +0,0 @@
1
- /** biome-ignore-all lint/suspicious/noExplicitAny: <T is not known at this point> */
2
- /** biome-ignore-all lint/suspicious/noAssignInExpressions: <getter methods are cached> */
3
- import { getStableKeys } from "./impl";
4
- export { createNode, createRootNode };
5
- /**
6
- * Creates the root proxy node for dynamic path access.
7
- *
8
- * This is an internal function that wraps a store API in a Proxy, enabling
9
- * property-chain syntax like `store.user.profile.name.use()`.
10
- *
11
- * @param storeApi - The underlying store API with path-based methods
12
- * @param initialPath - Starting path segment (default: empty string for root)
13
- * @returns A proxy that intercepts property access and returns nested proxies or state methods
14
- */
15
- function createRootNode(storeApi, initialPath = "") {
16
- const proxyCache = new Map();
17
- return createNode(storeApi, initialPath, proxyCache);
18
- }
19
- /**
20
- * Creates a proxy node for a specific path in the store.
21
- *
22
- * The proxy intercepts property access to provide state methods (use, set, value, etc.)
23
- * and recursively creates child proxies for nested paths. Supports derived state
24
- * transformations via the `from` and `to` parameters.
25
- *
26
- * @param storeApi - The underlying store API
27
- * @param path - Dot-separated path to this node (e.g., "user.profile.name")
28
- * @param cache - Shared cache to avoid recreating proxies for the same path
29
- * @param extensions - Optional custom getters/setters (used by form handling)
30
- * @param from - Transform function applied when reading values (for derived state)
31
- * @param to - Transform function applied when writing values (for derived state)
32
- * @returns A proxy implementing the State interface for the given path
33
- */
34
- function createNode(storeApi, path, cache, extensions, from = unchanged, to = unchanged) {
35
- const isDerived = from !== unchanged || to !== unchanged;
36
- const fieldName = path.split(".").pop();
37
- if (!isDerived && cache.has(path)) {
38
- return cache.get(path);
39
- }
40
- const proxy = new Proxy({}, {
41
- get(_target, prop) {
42
- if (prop === "field") {
43
- return fieldName;
44
- }
45
- if (prop === "use") {
46
- return (_target._use ??= () => from(storeApi.use(path)));
47
- }
48
- if (prop === "useDebounce") {
49
- return (_target._useDebounce ??= (delay) => from(storeApi.useDebounce(path, delay)));
50
- }
51
- if (prop === "useState") {
52
- return (_target._useState ??= () => {
53
- const value = storeApi.use(path);
54
- return [from(value), (next) => storeApi.set(path, to(next))];
55
- });
56
- }
57
- if (prop === "value") {
58
- return from(storeApi.value(path));
59
- }
60
- if (prop === "set") {
61
- return (_target._set ??= (value, skipUpdate) => storeApi.set(path, to(value), skipUpdate));
62
- }
63
- if (prop === "reset") {
64
- return (_target._reset ??= () => storeApi.reset(path));
65
- }
66
- if (prop === "subscribe") {
67
- return (_target._subscribe ??= (listener) => storeApi.subscribe(path, (value) => listener(to(value))));
68
- }
69
- if (prop === "useCompute") {
70
- return (_target._useCompute ??= (fn) => {
71
- return storeApi.useCompute(path, (value) => fn(from(value)));
72
- });
73
- }
74
- if (prop === "derived") {
75
- if (isDerived) {
76
- throw new Error(`Derived method cannot be called on a derived node: ${path}`);
77
- }
78
- return (_target._derived ??= ({ from, to, }) => createNode(storeApi, path, cache, extensions, from, to));
79
- }
80
- if (prop === "notify") {
81
- return (_target._notify ??= () => storeApi.notify(path));
82
- }
83
- if (prop === "ensureArray") {
84
- return (_target._ensureArray ??= () => {
85
- const cacheKey = `${path}.__juststore_ensureArray`;
86
- if (!isDerived && cache.has(cacheKey)) {
87
- return cache.get(cacheKey);
88
- }
89
- const node = createNode(storeApi, path, cache, extensions, (value) => ensureArray(value, from), unchanged);
90
- if (!isDerived) {
91
- cache.set(cacheKey, node);
92
- }
93
- return node;
94
- });
95
- }
96
- if (prop === "ensureObject") {
97
- return (_target._ensureObject ??= () => {
98
- const cacheKey = `${path}.__juststore_ensureObject`;
99
- if (!isDerived && cache.has(cacheKey)) {
100
- return cache.get(cacheKey);
101
- }
102
- const node = createNode(storeApi, path, cache, extensions, (value) => ensureObject(value, from), to);
103
- if (!isDerived) {
104
- cache.set(cacheKey, node);
105
- }
106
- return node;
107
- });
108
- }
109
- if (prop === "withDefault") {
110
- return (_target._withDefault ??= (defaultValue) => createNode(storeApi, path, cache, extensions, (value) => withDefault(value, defaultValue, from), to));
111
- }
112
- if (isObjectMethod(prop)) {
113
- const derivedValue = from(storeApi.value(path));
114
- if (derivedValue !== undefined && typeof derivedValue !== "object") {
115
- throw new Error(`Expected object at path ${path}, got ${typeof derivedValue}`);
116
- }
117
- if (prop === "rename") {
118
- return (_target._rename ??= (oldKey, newKey) => storeApi.rename(path, oldKey, newKey));
119
- }
120
- if (prop === "keys") {
121
- const cacheKey = `${path}.__juststore_keys`;
122
- if (!isDerived && cache.has(cacheKey)) {
123
- return cache.get(cacheKey);
124
- }
125
- const keysNode = createKeysNode(storeApi, path, (value) => ensureObject(value, from));
126
- if (!isDerived) {
127
- cache.set(cacheKey, keysNode);
128
- }
129
- return keysNode;
130
- }
131
- }
132
- if (isArrayMethod(prop)) {
133
- if (prop === "useLength") {
134
- return (_target._useLength ??= () => storeApi.useCompute(path, (value) => {
135
- const arr = from(value);
136
- return Array.isArray(arr) ? arr.length : 0;
137
- }));
138
- }
139
- const derivedValue = from(storeApi.value(path));
140
- if (derivedValue !== undefined && !Array.isArray(derivedValue)) {
141
- throw new Error(`Expected array at path ${path}, got ${typeof derivedValue}`);
142
- }
143
- const currentArray = derivedValue ? [...derivedValue] : [];
144
- if (prop === "at") {
145
- return (_target._at ??= (index) => {
146
- const nextPath = path ? `${path}.${index}` : String(index);
147
- return createNode(storeApi, nextPath, cache, extensions);
148
- });
149
- }
150
- if (prop === "length") {
151
- return currentArray.length;
152
- }
153
- // Array mutation methods
154
- if (prop === "push") {
155
- return (_target._push ??= (...items) => {
156
- // We need to fetch the current array at call time, not bind time
157
- const arr = from(storeApi.value(path)) ?? [];
158
- const newArray = [...arr, ...items];
159
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
160
- return newArray.length;
161
- });
162
- }
163
- if (prop === "pop") {
164
- return (_target._pop ??= () => {
165
- const arr = from(storeApi.value(path)) ?? [];
166
- if (arr.length === 0)
167
- return undefined;
168
- const newArray = arr.slice(0, -1);
169
- const poppedItem = arr[arr.length - 1];
170
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
171
- return poppedItem;
172
- });
173
- }
174
- if (prop === "shift") {
175
- return (_target._shift ??= () => {
176
- const arr = from(storeApi.value(path)) ?? [];
177
- if (arr.length === 0)
178
- return undefined;
179
- const newArray = arr.slice(1);
180
- const shiftedItem = arr[0];
181
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
182
- return shiftedItem;
183
- });
184
- }
185
- if (prop === "unshift") {
186
- return (_target._unshift ??= (...items) => {
187
- const arr = from(storeApi.value(path)) ?? [];
188
- const newArray = [...items, ...arr];
189
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
190
- return newArray.length;
191
- });
192
- }
193
- if (prop === "splice") {
194
- return (_target._splice ??= (start, deleteCount, ...items) => {
195
- const arr = from(storeApi.value(path)) ?? [];
196
- const newArray = [...arr];
197
- const deletedItems = newArray.splice(start, deleteCount ?? 0, ...items);
198
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
199
- return deletedItems;
200
- });
201
- }
202
- if (prop === "reverse") {
203
- return (_target._reverse ??= () => {
204
- const arr = from(storeApi.value(path));
205
- if (!Array.isArray(arr))
206
- return [];
207
- const newArray = [...arr];
208
- newArray.reverse();
209
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
210
- return newArray;
211
- });
212
- }
213
- if (prop === "sort") {
214
- return (_target._sort ??= (compareFn) => {
215
- const arr = from(storeApi.value(path));
216
- if (!Array.isArray(arr))
217
- return [];
218
- const newArray = [...arr];
219
- newArray.sort(compareFn);
220
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
221
- return newArray;
222
- });
223
- }
224
- if (prop === "fill") {
225
- return (_target._fill ??= (value, start, end) => {
226
- const arr = from(storeApi.value(path));
227
- if (!Array.isArray(arr))
228
- return [];
229
- const newArray = [...arr];
230
- newArray.fill(value, start, end);
231
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
232
- return newArray;
233
- });
234
- }
235
- if (prop === "copyWithin") {
236
- return (_target._copyWithin ??= (target, start, end) => {
237
- const arr = from(storeApi.value(path));
238
- if (!Array.isArray(arr))
239
- return [];
240
- const newArray = [...arr];
241
- newArray.copyWithin(target, start, end);
242
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
243
- return newArray;
244
- });
245
- }
246
- if (prop === "sortedInsert") {
247
- return (_target._sortedInsert ??= (cmp, ...items) => {
248
- const arr = from(storeApi.value(path));
249
- if (!Array.isArray(arr))
250
- return [];
251
- if (typeof cmp !== "function")
252
- return arr.length;
253
- const newArray = [...arr];
254
- for (const item of items) {
255
- let left = 0;
256
- let right = newArray.length;
257
- // Binary search to find insertion point
258
- while (left < right) {
259
- const mid = (left + right) >>> 1;
260
- if (cmp(newArray[mid], item) <= 0) {
261
- left = mid + 1;
262
- }
263
- else {
264
- right = mid;
265
- }
266
- }
267
- // Insert at the found position
268
- newArray.splice(left, 0, item);
269
- }
270
- storeApi.set(path, isDerived ? newArray.map(to) : newArray);
271
- return newArray.length;
272
- });
273
- }
274
- }
275
- if (extensions?.[prop]?.get) {
276
- return extensions[prop]?.get(path);
277
- }
278
- if (typeof prop === "string" || typeof prop === "number") {
279
- const nextPath = path ? `${path}.${prop}` : String(prop);
280
- return createNode(storeApi, nextPath, cache, extensions);
281
- }
282
- return undefined;
283
- },
284
- set(_target, prop, value) {
285
- if (extensions?.[prop]?.set) {
286
- return extensions[prop]?.set(path, value);
287
- }
288
- if (typeof prop === "string" || typeof prop === "number") {
289
- const nextPath = path ? `${path}.${prop}` : String(prop);
290
- storeApi.set(nextPath, to(value));
291
- return true;
292
- }
293
- return false;
294
- },
295
- });
296
- if (!isDerived) {
297
- cache.set(path, proxy);
298
- }
299
- return proxy;
300
- }
301
- function isArrayMethod(prop) {
302
- return (prop === "at" ||
303
- prop === "length" ||
304
- prop === "useLength" ||
305
- prop === "push" ||
306
- prop === "pop" ||
307
- prop === "shift" ||
308
- prop === "unshift" ||
309
- prop === "splice" ||
310
- prop === "reverse" ||
311
- prop === "sort" ||
312
- prop === "fill" ||
313
- prop === "copyWithin" ||
314
- prop === "sortedInsert");
315
- }
316
- function isObjectMethod(prop) {
317
- return prop === "rename" || prop === "keys";
318
- }
319
- function unchanged(value) {
320
- return value;
321
- }
322
- const EMPTY_ARRAY = [];
323
- const EMPTY_OBJECT = {};
324
- function createKeysNode(storeApi, path, getObjectValue) {
325
- const signalPath = path ? `${path}.__juststore_keys` : "__juststore_keys";
326
- const computeKeys = () => {
327
- return getStableKeys(getObjectValue(storeApi.value(path)));
328
- };
329
- return new Proxy({}, {
330
- get(_target, prop) {
331
- if (prop === "use") {
332
- return (_target._use ??= () => storeApi.useCompute(signalPath, computeKeys));
333
- }
334
- if (prop === "value")
335
- return computeKeys();
336
- if (prop === "useCompute") {
337
- return (_target._useCompute ??= (fn) => {
338
- return storeApi.useCompute(signalPath, () => fn(computeKeys()));
339
- });
340
- }
341
- if (prop === "Render") {
342
- return (_target._Render ??= ({ children, }) => children(storeApi.useCompute(signalPath, computeKeys), () => { }));
343
- }
344
- if (prop === "Show") {
345
- return (_target._Show ??= ({ children, on, }) => {
346
- const show = storeApi.useCompute(signalPath, () => on(computeKeys()), [on]);
347
- return show ? children : null;
348
- });
349
- }
350
- return undefined;
351
- },
352
- });
353
- }
354
- function ensureArray(value, from) {
355
- if (value === undefined || value === null)
356
- return EMPTY_ARRAY;
357
- const array = from(value);
358
- if (Array.isArray(array))
359
- return array;
360
- return EMPTY_ARRAY;
361
- }
362
- function ensureObject(value, from) {
363
- if (value === undefined || value === null)
364
- return EMPTY_OBJECT;
365
- const obj = from(value);
366
- if (obj && typeof obj === "object" && !Array.isArray(obj))
367
- return obj;
368
- return EMPTY_OBJECT;
369
- }
370
- function withDefault(value, defaultValue, from) {
371
- if (value === undefined || value === null)
372
- return defaultValue; // defaultValue should've already matched the type
373
- return from(value);
374
- }
@@ -1,136 +0,0 @@
1
- /** biome-ignore-all lint/suspicious/noExplicitAny: <Intended> */
2
- export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
3
- export type BrowserNativeObject = Date | FileList | File;
4
- /**
5
- * Type which given a tuple type returns its own keys, i.e. only its indices.
6
- * @typeParam T - tuple type
7
- * @example
8
- * ```
9
- * TupleKeys<[number, string]> = '0' | '1'
10
- * ```
11
- */
12
- export type TupleKeys<T extends ReadonlyArray<any>> = Exclude<keyof T, keyof any[]>;
13
- /**
14
- * Helper type for recursively constructing paths through a type.
15
- * This actually constructs the strings and recurses into nested
16
- * object types.
17
- *
18
- * See {@link ArrayPath}
19
- */
20
- type ArrayPathImpl<K extends string | number, V, TraversedTypes> = V extends Primitive | BrowserNativeObject ? IsAny<V> extends true ? string : never : V extends ReadonlyArray<infer U> ? U extends Primitive | BrowserNativeObject ? IsAny<V> extends true ? string : never : true extends AnyIsEqual<TraversedTypes, V> ? never : `${K}` | `${K}.${ArrayPathInternal<V, TraversedTypes | V>}` : true extends AnyIsEqual<TraversedTypes, V> ? never : `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`;
21
- /**
22
- * Checks whether the type is any
23
- * See {@link https://stackoverflow.com/a/49928360/3406963}
24
- * @typeParam T - type which may be any
25
- * ```
26
- * IsAny<any> = true
27
- * IsAny<string> = false
28
- * ```
29
- */
30
- export type IsAny<T> = 0 extends 1 & T ? true : false;
31
- /**
32
- * Checks whether T1 can be exactly (mutually) assigned to T2
33
- * @typeParam T1 - type to check
34
- * @typeParam T2 - type to check against
35
- * ```
36
- * IsEqual<string, string> = true
37
- * IsEqual<'foo', 'foo'> = true
38
- * IsEqual<string, number> = false
39
- * IsEqual<string, number> = false
40
- * IsEqual<string, 'foo'> = false
41
- * IsEqual<'foo', string> = false
42
- * IsEqual<'foo' | 'bar', 'foo'> = boolean // 'foo' is assignable, but 'bar' is not (true | false) -> boolean
43
- * ```
44
- */
45
- export type IsEqual<T1, T2> = T1 extends T2 ? (<G>() => G extends T1 ? 1 : 2) extends <G>() => G extends T2 ? 1 : 2 ? true : false : false;
46
- /**
47
- * Helper function to break apart T1 and check if any are equal to T2
48
- *
49
- * See {@link IsEqual}
50
- */
51
- type AnyIsEqual<T1, T2> = T1 extends T2 ? IsEqual<T1, T2> extends true ? true : never : never;
52
- /**
53
- * Type to query whether an array type T is a tuple type.
54
- * @typeParam T - type which may be an array or tuple
55
- * @example
56
- * ```
57
- * IsTuple<[number]> = true
58
- * IsTuple<number[]> = false
59
- * ```
60
- */
61
- export type IsTuple<T extends ReadonlyArray<any>> = number extends T["length"] ? false : true;
62
- /**
63
- * Type which can be used to index an array or tuple type.
64
- */
65
- export type ArrayKey = number;
66
- /**
67
- * Helper type for recursively constructing paths through a type.
68
- * This obscures the internal type param TraversedTypes from exported contract.
69
- *
70
- * See {@link ArrayPath}
71
- */
72
- type ArrayPathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
73
- [K in TupleKeys<T>]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
74
- }[TupleKeys<T>] : ArrayPathImpl<ArrayKey, V, TraversedTypes> : {
75
- [K in keyof T]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
76
- }[keyof T];
77
- /**
78
- * Type which eagerly collects all paths through a type which point to an array
79
- * type.
80
- * @typeParam T - type which should be introspected.
81
- * @example
82
- * ```
83
- * Path<{foo: {bar: string[], baz: number[]}}> = 'foo.bar' | 'foo.baz'
84
- * ```
85
- */
86
- export type ArrayPath<T> = T extends any ? ArrayPathInternal<T> : never;
87
- /**
88
- * Type which eagerly collects all paths through a type
89
- * @typeParam T - type which should be introspected
90
- * @example
91
- * ```
92
- * Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
93
- * ```
94
- */
95
- export type Path<T> = T extends any ? PathInternal<T> : never;
96
- /**
97
- * See {@link Path}
98
- */
99
- export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>;
100
- /**
101
- * Helper type for recursively constructing paths through a type.
102
- * This actually constructs the strings and recurses into nested
103
- * object types.
104
- *
105
- * See {@link Path}
106
- */
107
- type PathImpl<K extends string | number, V, TraversedTypes> = V extends Primitive | BrowserNativeObject ? `${K}` : true extends AnyIsEqual<TraversedTypes, V> ? `${K}` : `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}`;
108
- /**
109
- * Helper type for recursively constructing paths through a type.
110
- * This obscures the internal type param TraversedTypes from exported contract.
111
- *
112
- * See {@link Path}
113
- */
114
- type PathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
115
- [K in TupleKeys<T>]-?: PathImpl<K & string, T[K], TraversedTypes>;
116
- }[TupleKeys<T>] : PathImpl<ArrayKey, V, TraversedTypes> : {
117
- [K in keyof T]-?: PathImpl<K & string, T[K], TraversedTypes>;
118
- }[keyof T];
119
- /**
120
- * Type to evaluate the type which the given path points to.
121
- * @typeParam T - deeply nested type which is indexed by the path
122
- * @typeParam P - path into the deeply nested type
123
- * @example
124
- * ```
125
- * PathValue<{foo: {bar: string}}, 'foo.bar'> = string
126
- * PathValue<[number, string], '1'> = string
127
- * ```
128
- */
129
- export type PathValue<T, P extends Path<T> | ArrayPath<T>> = PathValueImpl<T, P>;
130
- type PathValueImpl<T, P extends string> = T extends any ? P extends `${infer K}.${infer R}` ? K extends keyof T ? undefined extends T[K] ? PathValueImpl<T[K], R> | undefined : PathValueImpl<T[K], R> : K extends `${ArrayKey}` ? T extends ReadonlyArray<infer V> ? PathValueImpl<V, R> : never : never : P extends keyof T ? T[P] : P extends `${ArrayKey}` ? T extends ReadonlyArray<infer V> ? V : undefined extends T ? undefined : never : never : never;
131
- /**
132
- * See {@link PathValue}
133
- */
134
- export type FieldPathValue<TFieldValues extends FieldValues, TFieldPath extends FieldPath<TFieldValues>> = Exclude<PathValue<TFieldValues, TFieldPath>, Function>;
135
- export type FieldValues = Record<string, any>;
136
- export {};
package/dist/src/path.js DELETED
@@ -1,26 +0,0 @@
1
- /*
2
- Copied and modified from https://github.com/react-hook-form/react-hook-form
3
-
4
- MIT License
5
-
6
- Copyright (c) 2019-present Beier(Bill) Luo
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in all
16
- copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
- SOFTWARE.
25
- */
26
- export {};
@@ -1,23 +0,0 @@
1
- import type { FieldValues } from "./path";
2
- import type { StoreRoot } from "./types";
3
- export { createStoreRoot, type StoreOptions };
4
- /**
5
- * Configuration options for store creation.
6
- */
7
- type StoreOptions = {
8
- /** When true, the store only uses memory and does not persist to localStorage */
9
- memoryOnly?: boolean;
10
- };
11
- /**
12
- * Creates the core store API with path-based methods.
13
- *
14
- * Uses a Proxy pattern for lazy initialization and caching of methods,
15
- * similar to the atom implementation. Methods are only created when first accessed
16
- * and then cached for subsequent use.
17
- *
18
- * @param namespace - Unique identifier for the store
19
- * @param defaultValue - Initial state merged with any persisted data
20
- * @param options - Configuration options
21
- * @returns A proxy object providing both path-based and dynamic property access to the store
22
- */
23
- declare function createStoreRoot<T extends FieldValues>(namespace: string, defaultValue: T, options?: StoreOptions): StoreRoot<T>;
package/dist/src/root.js DELETED
@@ -1,81 +0,0 @@
1
- import { useCallback } from "react";
2
- import { getNestedValue, getSnapshot, isRecord, joinPath, notifyListeners, produce, rename, setLeaf, subscribe, useCompute, useDebounce, useObject, } from "./impl";
3
- import { createRootNode } from "./node";
4
- export { createStoreRoot };
5
- /**
6
- * Creates the core store API with path-based methods.
7
- *
8
- * Uses a Proxy pattern for lazy initialization and caching of methods,
9
- * similar to the atom implementation. Methods are only created when first accessed
10
- * and then cached for subsequent use.
11
- *
12
- * @param namespace - Unique identifier for the store
13
- * @param defaultValue - Initial state merged with any persisted data
14
- * @param options - Configuration options
15
- * @returns A proxy object providing both path-based and dynamic property access to the store
16
- */
17
- function createStoreRoot(namespace, defaultValue, options = {}) {
18
- "use memo";
19
- const memoryOnly = options?.memoryOnly ?? false;
20
- // merge with default value and save in memory only
21
- produce(namespace, mergeWithDefaults(defaultValue, getSnapshot(namespace, memoryOnly)), true, true);
22
- const storeApi = {
23
- state: (path) => createRootNode(storeApi, path),
24
- use: (path) => useObject(namespace, path, memoryOnly),
25
- useDebounce: (path, delay) => useDebounce(namespace, path, delay, memoryOnly),
26
- set: (path, value, skipUpdate = false) => {
27
- if (typeof value !== "function") {
28
- return setLeaf(namespace, path, value, skipUpdate, memoryOnly);
29
- }
30
- const currentValue = storeApi.value(path);
31
- const newValue = value(currentValue);
32
- return setLeaf(namespace, path, newValue, skipUpdate, memoryOnly);
33
- },
34
- value: (path) => getSnapshot(joinPath(namespace, path), memoryOnly),
35
- reset: (path) => {
36
- return produce(joinPath(namespace, path), getNestedValue(defaultValue, path), false, memoryOnly);
37
- },
38
- rename: (path, oldKey, newKey) => rename(joinPath(namespace, path), oldKey, newKey, memoryOnly),
39
- subscribe: (path, listener) => {
40
- const fullPath = joinPath(namespace, path);
41
- const unsubscribe = subscribe(fullPath, () => listener(getSnapshot(fullPath, memoryOnly)));
42
- return unsubscribe;
43
- },
44
- useCompute: (path, fn, deps) => {
45
- return useCompute(namespace, path, fn, deps, memoryOnly);
46
- },
47
- notify: (path) => {
48
- const value = getNestedValue(getSnapshot(namespace, memoryOnly), path);
49
- return notifyListeners(joinPath(namespace, path), value, value, {
50
- skipRoot: true,
51
- skipChildren: true,
52
- forceNotify: true,
53
- });
54
- },
55
- useState: (path) => {
56
- const setValue = useCallback((value) => {
57
- storeApi.set(path, value, false);
58
- }, [path]);
59
- return [
60
- useObject(namespace, path, memoryOnly),
61
- setValue,
62
- ];
63
- },
64
- };
65
- return storeApi;
66
- }
67
- function mergeWithDefaults(defaultValue, existingValue) {
68
- if (existingValue === undefined) {
69
- return defaultValue;
70
- }
71
- if (!isRecord(defaultValue) || !isRecord(existingValue)) {
72
- return existingValue;
73
- }
74
- const defaults = defaultValue;
75
- const existing = existingValue;
76
- const merged = { ...existing };
77
- for (const key of Object.keys(defaults)) {
78
- merged[key] = mergeWithDefaults(defaults[key], existing[key]);
79
- }
80
- return merged;
81
- }
@@ -1,4 +0,0 @@
1
- export { getExternalKeyOrder, getStableKeys, setExternalKeyOrder };
2
- declare function getExternalKeyOrder(target: object): string[] | undefined;
3
- declare function setExternalKeyOrder(target: object, keys: string[]): void;
4
- declare function getStableKeys(value: unknown): string[];
@@ -1,31 +0,0 @@
1
- import { isRecord } from "./impl";
2
- export { getExternalKeyOrder, getStableKeys, setExternalKeyOrder };
3
- const externalKeyOrder = new WeakMap();
4
- function getExternalKeyOrder(target) {
5
- return externalKeyOrder.get(target);
6
- }
7
- function setExternalKeyOrder(target, keys) {
8
- externalKeyOrder.set(target, keys);
9
- }
10
- function getStableKeys(value) {
11
- if (!isRecord(value))
12
- return [];
13
- const target = value;
14
- const existing = externalKeyOrder.get(target);
15
- if (existing) {
16
- const next = existing.filter((k) => Object.hasOwn(target, k));
17
- const nextSet = new Set(next);
18
- for (const k of Object.keys(target)) {
19
- if (nextSet.has(k))
20
- continue;
21
- next.push(k);
22
- }
23
- if (next.length !== existing.length) {
24
- setExternalKeyOrder(target, next);
25
- }
26
- return next;
27
- }
28
- const keys = Object.keys(target);
29
- setExternalKeyOrder(target, keys);
30
- return keys;
31
- }