@victorylabs/params 0.2.0 → 0.3.0

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/react.cjs CHANGED
@@ -20,14 +20,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/react/index.ts
21
21
  var react_exports = {};
22
22
  __export(react_exports, {
23
+ useDebouncedValue: () => useDebouncedValue,
23
24
  useFieldInput: () => useFieldInput,
24
25
  useFieldValue: () => useFieldValue,
25
26
  useParams: () => useParams
26
27
  });
27
28
  module.exports = __toCommonJS(react_exports);
28
29
 
30
+ // src/react/use-debounced-value.ts
31
+ var import_react = require("react");
32
+ function useDebouncedValue(value, ms) {
33
+ const [debounced, setDebounced] = (0, import_react.useState)(value);
34
+ (0, import_react.useEffect)(() => {
35
+ if (ms <= 0) {
36
+ setDebounced(value);
37
+ return;
38
+ }
39
+ const id = setTimeout(() => setDebounced(value), ms);
40
+ return () => clearTimeout(id);
41
+ }, [value, ms]);
42
+ return debounced;
43
+ }
44
+
29
45
  // src/react/use-field-input.tsx
30
- var import_react2 = require("react");
46
+ var import_react3 = require("react");
31
47
 
32
48
  // src/schema.ts
33
49
  function isStandardSchema(spec) {
@@ -121,10 +137,10 @@ function extractEnumValues(spec) {
121
137
  }
122
138
 
123
139
  // src/react/use-field-value.ts
124
- var import_react = require("react");
140
+ var import_react2 = require("react");
125
141
  function useFieldValue(store, path) {
126
- const [, forceRender] = (0, import_react.useReducer)((tick) => tick + 1, 0);
127
- (0, import_react.useEffect)(() => store.subscribe(path, forceRender), [store, path]);
142
+ const [, forceRender] = (0, import_react2.useReducer)((tick) => tick + 1, 0);
143
+ (0, import_react2.useEffect)(() => store.subscribe(path, forceRender), [store, path]);
128
144
  return store.getValue(path);
129
145
  }
130
146
 
@@ -133,10 +149,10 @@ function useFieldInput(store, path) {
133
149
  const storeValue = useFieldValue(store, path);
134
150
  const config = store.getFieldConfig(path);
135
151
  const debounceMs = config?.debounce ?? 0;
136
- const [shadow, setShadow] = (0, import_react2.useState)(null);
137
- const debouncerRef = (0, import_react2.useRef)(null);
138
- const lastStoreValueRef = (0, import_react2.useRef)(storeValue);
139
- (0, import_react2.useEffect)(() => {
152
+ const [shadow, setShadow] = (0, import_react3.useState)(null);
153
+ const debouncerRef = (0, import_react3.useRef)(null);
154
+ const lastStoreValueRef = (0, import_react3.useRef)(storeValue);
155
+ (0, import_react3.useEffect)(() => {
140
156
  if (Object.is(lastStoreValueRef.current, storeValue)) return;
141
157
  lastStoreValueRef.current = storeValue;
142
158
  if (shadow !== null && shadow !== defaultSerialize(storeValue)) {
@@ -147,19 +163,19 @@ function useFieldInput(store, path) {
147
163
  if (debounceMs > 0 && debouncerRef.current === null) {
148
164
  debouncerRef.current = createDebouncer(store, path, debounceMs, () => setShadow(null));
149
165
  }
150
- const onChange = (0, import_react2.useCallback)(
166
+ const onChange = (0, import_react3.useCallback)(
151
167
  (event) => {
152
168
  const next = event.target.value;
153
169
  if (debounceMs > 0) {
154
170
  setShadow(next);
155
171
  debouncerRef.current?.trigger(next);
156
172
  } else {
157
- store.set(path, next);
173
+ store.setField(path, next);
158
174
  }
159
175
  },
160
176
  [store, path, debounceMs]
161
177
  );
162
- const onBlur = (0, import_react2.useCallback)(() => {
178
+ const onBlur = (0, import_react3.useCallback)(() => {
163
179
  debouncerRef.current?.flush();
164
180
  }, []);
165
181
  return {
@@ -174,7 +190,7 @@ function createDebouncer(store, path, ms, onCommit) {
174
190
  let pendingValue = null;
175
191
  const commit = () => {
176
192
  if (pendingValue !== null) {
177
- store.set(path, pendingValue);
193
+ store.setField(path, pendingValue);
178
194
  pendingValue = null;
179
195
  onCommit();
180
196
  }
@@ -202,10 +218,10 @@ function createDebouncer(store, path, ms, onCommit) {
202
218
  }
203
219
 
204
220
  // src/react/use-params.tsx
205
- var import_react4 = require("react");
221
+ var import_react5 = require("react");
206
222
 
207
223
  // src/react/use-shared-store.ts
208
- var import_react3 = require("react");
224
+ var import_react4 = require("react");
209
225
 
210
226
  // src/dev.ts
211
227
  var isDev = (() => {
@@ -485,17 +501,43 @@ var ParamsStore = class {
485
501
  getFieldConfig(path) {
486
502
  return this.fieldConfigs[path];
487
503
  }
488
- set(pathOrPartial, valueOrOptions, maybeOptions) {
504
+ // ─── Writes ───────────────────────────────────────────────────────────
505
+ /**
506
+ * Apply a partial update — write multiple fields at once.
507
+ *
508
+ * `set(partial)` is the canonical write form. The path-and-value form
509
+ * (`set(path, value)` historically) is now `setField(path, value)` —
510
+ * see {@link setField}. The split keeps `set`'s single-signature
511
+ * inference clean for the common `set({ [key]: value })` pattern, which
512
+ * previously required `as Partial<T>` casts because TypeScript couldn't
513
+ * resolve the overload from a generic-keyed object literal.
514
+ *
515
+ * @example
516
+ * ```ts
517
+ * p.set({ page: 1, sort: 'updated' })
518
+ * p.set({ [key]: value }) // works without a cast now
519
+ * ```
520
+ */
521
+ set(partial, options) {
489
522
  if (this.disposed) return;
490
- let updates;
491
- let options;
492
- if (typeof pathOrPartial === "string") {
493
- updates = { [pathOrPartial]: valueOrOptions };
494
- options = maybeOptions;
495
- } else {
496
- updates = pathOrPartial;
497
- options = valueOrOptions;
498
- }
523
+ this.applyUpdates(partial, options);
524
+ }
525
+ /**
526
+ * Write a single field by path. Equivalent to `set({ [path]: value })`
527
+ * but skips the object-literal allocation and avoids the inference issue
528
+ * that motivated splitting the API.
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * p.setField('query', 'react')
533
+ * p.setField('filters.tags', ['ts', 'react'])
534
+ * ```
535
+ */
536
+ setField(path, value, options) {
537
+ if (this.disposed) return;
538
+ this.applyUpdates({ [path]: value }, options);
539
+ }
540
+ applyUpdates(updates, options) {
499
541
  const initialChanges = {};
500
542
  for (const [path, v] of Object.entries(updates)) {
501
543
  const old = deepGet(this.values, path);
@@ -527,13 +569,13 @@ var ParamsStore = class {
527
569
  /** Boolean-flip helper. */
528
570
  toggle(path, options) {
529
571
  const current = this.getValue(path);
530
- this.set(path, !current, options);
572
+ this.setField(path, !current, options);
531
573
  }
532
574
  /** Push a value onto an array field. */
533
575
  append(path, value, options) {
534
576
  const current = this.getValue(path);
535
577
  if (!Array.isArray(current)) return;
536
- this.set(path, [...current, value], options);
578
+ this.setField(path, [...current, value], options);
537
579
  }
538
580
  /** Remove the first array element matching `value` by deepEqual. */
539
581
  remove(path, value, options) {
@@ -541,14 +583,14 @@ var ParamsStore = class {
541
583
  if (!Array.isArray(current)) return;
542
584
  const idx = current.findIndex((item) => deepEqual(item, value));
543
585
  if (idx === -1) return;
544
- this.set(path, [...current.slice(0, idx), ...current.slice(idx + 1)], options);
586
+ this.setField(path, [...current.slice(0, idx), ...current.slice(idx + 1)], options);
545
587
  }
546
588
  /** Remove the array element at the given index. */
547
589
  removeAt(path, index, options) {
548
590
  const current = this.getValue(path);
549
591
  if (!Array.isArray(current)) return;
550
592
  if (index < 0 || index >= current.length) return;
551
- this.set(path, [...current.slice(0, index), ...current.slice(index + 1)], options);
593
+ this.setField(path, [...current.slice(0, index), ...current.slice(index + 1)], options);
552
594
  }
553
595
  cycle(path, valuesOrOptions, maybeOptions) {
554
596
  let resolved;
@@ -574,12 +616,12 @@ var ParamsStore = class {
574
616
  const current = this.getValue(path);
575
617
  const idx = resolved.findIndex((o) => deepEqual(o, current));
576
618
  const next = idx === -1 ? resolved[0] : resolved[(idx + 1) % resolved.length];
577
- this.set(path, next, setOpts);
619
+ this.setField(path, next, setOpts);
578
620
  }
579
621
  /** Reset a single field to its default. */
580
622
  clear(path, options) {
581
623
  const def = deepGet(this.defaults, path);
582
- this.set(path, def, options);
624
+ this.setField(path, def, options);
583
625
  }
584
626
  /** Reset all fields to defaults; optional partial overrides + SetOptions. */
585
627
  reset(values, options) {
@@ -833,11 +875,11 @@ function warnOnDuplicateName(def) {
833
875
 
834
876
  // src/react/use-shared-store.ts
835
877
  function useSharedStore(def) {
836
- const storeRef = (0, import_react3.useRef)(null);
878
+ const storeRef = (0, import_react4.useRef)(null);
837
879
  if (storeRef.current === null) {
838
880
  storeRef.current = acquire(def);
839
881
  }
840
- (0, import_react3.useEffect)(
882
+ (0, import_react4.useEffect)(
841
883
  () => () => {
842
884
  release(def);
843
885
  storeRef.current = null;
@@ -850,35 +892,33 @@ function useSharedStore(def) {
850
892
  // src/react/use-params.tsx
851
893
  function useParams(def) {
852
894
  const store = useSharedStore(def);
853
- const [, forceRender] = (0, import_react4.useReducer)((tick) => tick + 1, 0);
854
- (0, import_react4.useEffect)(() => store.subscribe("", forceRender), [store]);
855
- const set = (0, import_react4.useCallback)(
856
- ((pathOrPartial, valueOrOptions, maybeOptions) => {
857
- if (typeof pathOrPartial === "string") {
858
- store.set(pathOrPartial, valueOrOptions, maybeOptions);
859
- } else {
860
- store.set(pathOrPartial, valueOrOptions);
861
- }
862
- }),
895
+ const [, forceRender] = (0, import_react5.useReducer)((tick) => tick + 1, 0);
896
+ (0, import_react5.useEffect)(() => store.subscribe("", forceRender), [store]);
897
+ const set = (0, import_react5.useCallback)(
898
+ (partial, options) => store.set(partial, options),
899
+ [store]
900
+ );
901
+ const setField = (0, import_react5.useCallback)(
902
+ (path, value2, options) => store.setField(path, value2, options),
863
903
  [store]
864
904
  );
865
- const toggle = (0, import_react4.useCallback)(
905
+ const toggle = (0, import_react5.useCallback)(
866
906
  (path, options) => store.toggle(path, options),
867
907
  [store]
868
908
  );
869
- const append = (0, import_react4.useCallback)(
909
+ const append = (0, import_react5.useCallback)(
870
910
  (path, value2, options) => store.append(path, value2, options),
871
911
  [store]
872
912
  );
873
- const removeFn = (0, import_react4.useCallback)(
913
+ const removeFn = (0, import_react5.useCallback)(
874
914
  (path, value2, options) => store.remove(path, value2, options),
875
915
  [store]
876
916
  );
877
- const removeAt = (0, import_react4.useCallback)(
917
+ const removeAt = (0, import_react5.useCallback)(
878
918
  (path, index, options) => store.removeAt(path, index, options),
879
919
  [store]
880
920
  );
881
- const cycle = (0, import_react4.useCallback)(
921
+ const cycle = (0, import_react5.useCallback)(
882
922
  ((path, valuesOrOptions, maybeOptions) => {
883
923
  if (Array.isArray(valuesOrOptions)) {
884
924
  store.cycle(path, valuesOrOptions, maybeOptions);
@@ -890,32 +930,36 @@ function useParams(def) {
890
930
  }),
891
931
  [store]
892
932
  );
893
- const clear = (0, import_react4.useCallback)(
933
+ const clear = (0, import_react5.useCallback)(
894
934
  (path, options) => store.clear(path, options),
895
935
  [store]
896
936
  );
897
- const reset = (0, import_react4.useCallback)(
937
+ const reset = (0, import_react5.useCallback)(
898
938
  (values, options) => store.reset(values, options),
899
939
  [store]
900
940
  );
901
- const subscribe = (0, import_react4.useCallback)(
941
+ const subscribe = (0, import_react5.useCallback)(
902
942
  (path, listener) => store.subscribe(path, listener),
903
943
  [store]
904
944
  );
905
- const toQuery = (0, import_react4.useCallback)(
945
+ const toQuery = (0, import_react5.useCallback)(
906
946
  (overrides) => store.toQuery(overrides),
907
947
  [store]
908
948
  );
909
- const href = (0, import_react4.useCallback)((overrides) => store.href(overrides), [store]);
949
+ const href = (0, import_react5.useCallback)((overrides) => store.href(overrides), [store]);
910
950
  const value = (path) => useFieldValue(store, path);
911
- const deferred = (path) => (0, import_react4.useDeferredValue)(useFieldValue(store, path));
951
+ const deferred = (path) => (0, import_react5.useDeferredValue)(useFieldValue(store, path));
912
952
  const input = (path) => useFieldInput(store, path);
913
953
  return {
954
+ // Cast: store.getValues() returns Partial<T> for the storage layer's
955
+ // sake, but every field in ParamsDefinition declares a default so the
956
+ // hydrated view is structurally complete. See ParamsController.values.
914
957
  values: store.getValues(),
915
958
  value,
916
959
  deferred,
917
960
  input,
918
961
  set,
962
+ setField,
919
963
  toggle,
920
964
  append,
921
965
  remove: removeFn,
@@ -931,6 +975,7 @@ function useParams(def) {
931
975
  }
932
976
  // Annotate the CommonJS export names for ESM import in node:
933
977
  0 && (module.exports = {
978
+ useDebouncedValue,
934
979
  useFieldInput,
935
980
  useFieldValue,
936
981
  useParams