juststore 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -473,22 +473,25 @@ The store root provides path-based methods for dynamic access:
473
473
 
474
474
  ### State Methods
475
475
 
476
- | Method | Description |
477
- | ---------------------------------------- | ------------------------------------------------------- |
478
- | `.use()` | Subscribe and read value (triggers re-render on change) |
479
- | `.useDebounce(ms)` | Subscribe with debounced updates |
480
- | `.useState()` | Returns `[value, setValue]` tuple |
481
- | `.value` | Read without subscribing |
482
- | `.set(value)` | Update value |
483
- | `.set(fn)` | Functional update |
484
- | `.reset()` | Delete value at path |
485
- | `.subscribe(fn)` | Subscribe to changes (for effects) |
486
- | `.rename(oldKey, newKey, notifyObject?)` | Rename a key in an object |
487
- | `.notify()` | Manually trigger subscribers |
488
- | `.useCompute(fn)` | Derive a computed value |
489
- | `.derived({ from, to })` | Create bidirectional transform |
490
- | `.Render({ children })` | Render prop component |
491
- | `.Show({ children, on })` | Conditional render component |
476
+ | Method | Description |
477
+ | ---------------------------------------- | ----------------------------------------------------------------------- |
478
+ | `.use()` | Subscribe and read value (triggers re-render on change) |
479
+ | `.useDebounce(ms)` | Subscribe with debounced updates |
480
+ | `.useState()` | Returns `[value, setValue]` tuple |
481
+ | `.value` | Read without subscribing |
482
+ | `.set(value)` | Update value |
483
+ | `.set(fn)` | Functional update |
484
+ | `.reset()` | Delete value at path |
485
+ | `.subscribe(fn)` | Subscribe to changes (for effects) |
486
+ | `.rename(oldKey, newKey, notifyObject?)` | Rename a key in an object |
487
+ | `.notify()` | Manually trigger subscribers |
488
+ | `.useCompute(fn)` | Derive a computed value |
489
+ | `.derived({ from, to })` | Create bidirectional transform |
490
+ | `.ensureArray()` | Ensure the value is an array |
491
+ | `.ensureObject()` | Ensure the value is an object |
492
+ | `.withDefault(defaultValue)` | Return a new state with a default value, and make the type non-nullable |
493
+ | `.Render({ children })` | Render prop component |
494
+ | `.Show({ children, on })` | Conditional render component |
492
495
 
493
496
  ## License
494
497
 
package/dist/form.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { FieldPath, FieldPathValue, FieldValues, Primitive } from './path';
2
- import type { ArrayProxy, ExcludeNullUndefined, ObjectMutationMethods, ValueState } from './types';
1
+ import type { FieldPath, FieldPathValue, FieldValues, IsEqual } from './path';
2
+ import type { ArrayProxy, IsNullable, MaybeNullable, ObjectMutationMethods, ValueState } from './types';
3
3
  export { useForm, type CreateFormOptions, type DeepNonNullable, type FormArrayState, type FormObjectState, type FormState, type FormStore, type FormValueState };
4
4
  /**
5
5
  * Common form field methods available on every form state node.
@@ -12,19 +12,35 @@ type FormCommon = {
12
12
  /** Manually set a validation error. */
13
13
  setError: (error: string | undefined) => void;
14
14
  };
15
- type FormState<T> = [ExcludeNullUndefined<T>] extends [readonly (infer U)[]] ? FormArrayState<U> : [T] extends [FieldValues] ? FormObjectState<T> : [ExcludeNullUndefined<T>] extends [FieldValues] ? FormObjectState<ExcludeNullUndefined<T>> : FormValueState<T>;
16
- interface FormValueState<T> extends ValueState<T>, FormCommon {
15
+ type FormState<T> = IsEqual<T, unknown> extends true ? never : [NonNullable<T>] extends [readonly (infer U)[]] ? FormArrayState<U, IsNullable<T>> : [NonNullable<T>] extends [FieldValues] ? FormObjectState<NonNullable<T>, IsNullable<T>> : FormValueState<T>;
16
+ interface FormValueState<T> extends Omit<ValueState<T>, 'withDefault' | 'derived'>, FormCommon {
17
+ /** Return a new state with a default value, and make the type non-nullable */
18
+ withDefault(defaultValue: T): FormState<NonNullable<T>>;
19
+ /** Virtual state derived from the current value.
20
+ *
21
+ * @returns ArrayState if the derived value is an array, ObjectState if the derived value is an object, otherwise State.
22
+ * @example
23
+ * const state = store.a.b.c.derived({
24
+ * from: value => value + 1,
25
+ * to: value => value - 1
26
+ * })
27
+ * state.use() // returns the derived value
28
+ * state.set(10) // sets the derived value
29
+ * state.reset() // resets the derived value
30
+ */
31
+ derived: <R>({ from, to }: {
32
+ from?: (value: T | undefined) => R;
33
+ to?: (value: R) => T | undefined;
34
+ }) => FormState<R>;
17
35
  }
18
- type FormArrayElementState<T> = T extends Primitive ? FormValueState<T> : FormState<T>;
19
- interface FormArrayState<T> extends FormValueState<T[]>, ArrayProxy<T, FormArrayElementState<T>> {
20
- }
21
- type FormObjectState<T extends FieldValues> = {
36
+ type FormArrayState<T, Nullable extends boolean = false, TT = MaybeNullable<T[], Nullable>> = IsEqual<T, unknown> extends true ? never : FormValueState<TT[]> & ArrayProxy<TT, FormState<TT>>;
37
+ type FormObjectState<T extends FieldValues, Nullable extends boolean = false> = {
22
38
  [K in keyof T]-?: FormState<T[K]>;
23
- } & FormValueState<T> & ObjectMutationMethods;
39
+ } & FormValueState<MaybeNullable<T, Nullable>> & ObjectMutationMethods;
24
40
  /** Type for nested objects with proxy methods */
25
- type DeepNonNullable<T> = [ExcludeNullUndefined<T>] extends [readonly (infer U)[]] ? U[] : [ExcludeNullUndefined<T>] extends [FieldValues] ? {
26
- [K in keyof ExcludeNullUndefined<T>]-?: DeepNonNullable<ExcludeNullUndefined<T>[K]>;
27
- } : ExcludeNullUndefined<T>;
41
+ type DeepNonNullable<T> = [NonNullable<T>] extends [readonly (infer U)[]] ? U[] : [NonNullable<T>] extends [FieldValues] ? {
42
+ [K in keyof NonNullable<T>]-?: DeepNonNullable<NonNullable<T>[K]>;
43
+ } : NonNullable<T>;
28
44
  /**
29
45
  * The form store type, combining form state with validation and submission handling.
30
46
  */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ export type * from './form';
1
2
  export { useForm } from './form';
2
- export type { CreateFormOptions, FormState, FormStore } from './form';
3
3
  export { useMemoryStore, type MemoryStore } from './memory';
4
4
  export { createMixedState, type MixedState } from './mixed_state';
5
5
  export type * from './path';
package/dist/memory.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { FieldValues } from './path';
2
- import type { State } from './types';
2
+ import type { State, ValueState } from './types';
3
3
  export { useMemoryStore, type MemoryStore };
4
4
  /**
5
5
  * A component local store with React bindings.
@@ -10,7 +10,7 @@ export { useMemoryStore, type MemoryStore };
10
10
  * - Type-safe paths using FieldPath.
11
11
  * - Dynamic deep access via Proxy for ergonomic usage like `state.a.b.c.use()` and `state.a.b.c.set(v)`.
12
12
  */
13
- type MemoryStore<T extends FieldValues> = State<T> & {
13
+ type MemoryStore<T extends FieldValues> = ValueState<T> & {
14
14
  [K in keyof T]-?: State<T[K]>;
15
15
  };
16
16
  /**
@@ -1,4 +1,4 @@
1
- import type { Prettify, State, ValueState } from './types';
1
+ import type { Prettify, ValueState } from './types';
2
2
  export { createMixedState, type MixedState };
3
3
  /**
4
4
  * A combined state that aggregates multiple independent states into a tuple.
@@ -21,5 +21,5 @@ type MixedState<T extends readonly unknown[]> = Prettify<Pick<ValueState<Readonl
21
21
  * </mixedState.Render>
22
22
  */
23
23
  declare function createMixedState<T extends readonly unknown[]>(...states: {
24
- [K in keyof T]-?: State<T[K]>;
24
+ [K in keyof T]-?: ValueState<T[K]>;
25
25
  }): MixedState<T>;
package/dist/node.js CHANGED
@@ -87,10 +87,13 @@ function createNode(storeApi, path, cache, extensions, from = unchanged, to = un
87
87
  return () => storeApi.notify(path);
88
88
  }
89
89
  if (prop === 'ensureArray') {
90
- return () => createNode(storeApi, path, cache, extensions, ensureArray, unchanged);
90
+ return () => createNode(storeApi, path, cache, extensions, value => ensureArray(value, from), unchanged);
91
91
  }
92
92
  if (prop === 'ensureObject') {
93
- return () => createNode(storeApi, path, cache, extensions, ensureObject, unchanged);
93
+ return () => createNode(storeApi, path, cache, extensions, value => ensureObject(value, from), to);
94
+ }
95
+ if (prop === 'withDefault') {
96
+ return (defaultValue) => createNode(storeApi, path, cache, extensions, value => withDefault(value, defaultValue, from), to);
94
97
  }
95
98
  if (isObjectMethod(prop)) {
96
99
  const derivedValue = from(storeApi.value(path));
@@ -112,7 +115,7 @@ function createNode(storeApi, path, cache, extensions, from = unchanged, to = un
112
115
  if (prop === 'at') {
113
116
  return (index) => {
114
117
  const nextPath = path ? `${path}.${index}` : String(index);
115
- return createNode(storeApi, nextPath, cache, extensions, from, to);
118
+ return createNode(storeApi, nextPath, cache, extensions);
116
119
  };
117
120
  }
118
121
  if (prop === 'length') {
@@ -221,8 +224,7 @@ function createNode(storeApi, path, cache, extensions, from = unchanged, to = un
221
224
  }
222
225
  if (typeof prop === 'string' || typeof prop === 'number') {
223
226
  const nextPath = path ? `${path}.${prop}` : String(prop);
224
- // Always return a proxy
225
- return createNode(storeApi, nextPath, cache, extensions, from, to);
227
+ return createNode(storeApi, nextPath, cache, extensions);
226
228
  }
227
229
  return undefined;
228
230
  },
@@ -263,17 +265,26 @@ function isObjectMethod(prop) {
263
265
  function unchanged(value) {
264
266
  return value;
265
267
  }
266
- function ensureArray(value) {
268
+ const EMPTY_ARRAY = [];
269
+ const EMPTY_OBJECT = {};
270
+ function ensureArray(value, from) {
271
+ if (value === undefined || value === null)
272
+ return EMPTY_ARRAY;
273
+ const array = from(value);
274
+ if (Array.isArray(array))
275
+ return array;
276
+ return EMPTY_ARRAY;
277
+ }
278
+ function ensureObject(value, from) {
267
279
  if (value === undefined || value === null)
268
- return [];
269
- if (Array.isArray(value))
270
- return value;
271
- return [];
280
+ return EMPTY_OBJECT;
281
+ const obj = from(value);
282
+ if (typeof obj === 'object')
283
+ return obj;
284
+ return EMPTY_OBJECT;
272
285
  }
273
- function ensureObject(value) {
286
+ function withDefault(value, defaultValue, from) {
274
287
  if (value === undefined || value === null)
275
- return {};
276
- if (typeof value === 'object')
277
- return value;
278
- return {};
288
+ return defaultValue; // defaultValue should've already matched the type
289
+ return from(value);
279
290
  }
package/dist/types.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import type { FieldPath, FieldPathValue, FieldValues, Primitive } from './path';
2
- export type { AllowedKeys, ArrayElementState, ArrayProxy, ArrayState, DerivedStateProps, ExcludeNullUndefined, ObjectMutationMethods, ObjectState, Prettify, State, StoreRenderProps, StoreRoot, StoreSetStateAction, StoreShowProps, StoreUse, ValueState };
1
+ import type { FieldPath, FieldPathValue, FieldValues, IsEqual } from './path';
2
+ export type { AllowedKeys, ArrayProxy, ArrayState, DerivedStateProps, IsNullable, MaybeNullable, ObjectMutationMethods, ObjectState, Prettify, State, StoreRenderProps, StoreRoot, StoreSetStateAction, StoreShowProps, StoreUse, ValueState };
3
3
  type Prettify<T> = {
4
4
  [K in keyof T]: T[K];
5
5
  } & {};
6
6
  type AllowedKeys<T> = Exclude<keyof T, keyof ValueState<unknown> | keyof ObjectMutationMethods>;
7
- type ArrayMutationMethods<T> = Pick<Array<T>, 'push' | 'pop' | 'shift' | 'unshift' | 'splice' | 'reverse' | 'sort' | 'fill' | 'copyWithin'>;
7
+ type ArrayMutationMethods<T> = Prettify<Pick<Array<T>, 'push' | 'pop' | 'shift' | 'unshift' | 'splice' | 'reverse' | 'sort' | 'fill' | 'copyWithin'>>;
8
8
  /** Type for array proxy with index access */
9
- type ArrayProxy<T, ElementState = ArrayElementState<T>> = Prettify<ArrayMutationMethods<T>> & {
9
+ type ArrayProxy<T, ElementState = State<T>> = ArrayMutationMethods<T> & {
10
10
  /** Read without subscribing. Returns array or undefined for missing paths. */
11
11
  readonly value: T[];
12
12
  /**
@@ -80,9 +80,11 @@ type ValueState<T> = {
80
80
  /** Compute a derived value from the current value, similar to useState + useMemo */
81
81
  useCompute: <R>(fn: (value: T) => R) => R;
82
82
  /** Ensure the value is an array. */
83
- ensureArray<U>(): ArrayState<U>;
83
+ ensureArray(): NonNullable<T> extends (infer U)[] ? ArrayState<U> : never;
84
84
  /** Ensure the value is an object. */
85
- ensureObject<U extends FieldValues>(): ObjectState<U>;
85
+ ensureObject(): NonNullable<T> extends FieldValues ? ObjectState<NonNullable<T>> : never;
86
+ /** Return a new state with a default value, and make the type non-nullable */
87
+ withDefault(defaultValue: T): State<NonNullable<T>>;
86
88
  /** Virtual state derived from the current value.
87
89
  *
88
90
  * @returns ArrayState if the derived value is an array, ObjectState if the derived value is an object, otherwise State.
@@ -123,14 +125,13 @@ type ValueState<T> = {
123
125
  on: (value: T) => boolean;
124
126
  }) => React.ReactNode;
125
127
  };
126
- type ExcludeNullUndefined<T> = Exclude<T, undefined | null>;
127
- type ArrayElementState<T> = T extends Primitive ? ValueState<T> : State<T>;
128
- type State<T> = [ExcludeNullUndefined<T>] extends [readonly (infer U)[]] ? ArrayState<U> : [T] extends [FieldValues] ? ObjectState<T> : [ExcludeNullUndefined<T>] extends [FieldValues] ? ObjectState<ExcludeNullUndefined<T>> : ValueState<T>;
129
- interface ArrayState<T> extends ValueState<T[]>, ArrayProxy<T> {
130
- }
131
- type ObjectState<T extends FieldValues> = {
128
+ type MaybeNullable<T, Nullable extends boolean = false> = Nullable extends true ? T | undefined : T;
129
+ type IsNullable<T> = T extends undefined | null ? true : false;
130
+ type State<T> = IsEqual<T, unknown> extends true ? never : [NonNullable<T>] extends [readonly (infer U)[]] ? ArrayState<U, IsNullable<T>> : [NonNullable<T>] extends [FieldValues] ? ObjectState<NonNullable<T>, IsNullable<T>> : ValueState<T>;
131
+ type ArrayState<T, Nullable extends boolean = false> = IsEqual<T, unknown> extends true ? never : ValueState<MaybeNullable<T[], Nullable>> & ArrayProxy<T>;
132
+ type ObjectState<T extends FieldValues, Nullable extends boolean = false> = {
132
133
  [K in keyof T]-?: State<T[K]>;
133
- } & ValueState<T> & ObjectMutationMethods;
134
+ } & ValueState<MaybeNullable<T, Nullable>> & ObjectMutationMethods;
134
135
  /** Props for Store.Render helper. */
135
136
  type StoreRenderProps<T extends FieldValues, P extends FieldPath<T>> = {
136
137
  path: P;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juststore",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "A small, expressive, and type-safe state management library for React.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",