@fumari/stf 1.0.1 → 1.0.3

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.d.ts CHANGED
@@ -2,60 +2,9 @@ import { t as FieldKey } from "./types-Do_aTV5I.js";
2
2
  import { ReactNode } from "react";
3
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
5
- //#region src/lib/stf.d.ts
6
- interface Stf {
7
- dataEngine: DataEngine;
8
- }
9
- declare function StfProvider({
10
- value,
11
- children
12
- }: {
13
- value: Stf;
14
- children: ReactNode;
15
- }): react_jsx_runtime0.JSX.Element;
16
- declare function useStf(options: {
17
- /**
18
- * Note: the passed object will be modified in place, use `structuredClone()` to keep the original object unchanged.
19
- */
20
- defaultValues?: DefaultValue<Record<string, unknown>>;
21
- }): Stf;
22
- declare function useDataEngine(instance?: Stf | DataEngine): DataEngine;
23
- interface ArrayItemInfo {
24
- field: FieldKey;
25
- index: number;
26
- }
27
- declare function useArray(field: FieldKey, options?: {
28
- defaultValue?: DefaultValue<unknown[]>;
29
- }): {
30
- items: ArrayItemInfo[];
31
- insertItem(itemValue?: unknown): void;
32
- removeItem(index: number): void;
33
- };
34
- type PropertyItemInfo<T> = {
35
- kind: 'fixed' | 'fallback';
36
- field: FieldKey;
37
- key: string;
38
- info: T;
39
- } | {
40
- kind: 'pattern';
41
- field: FieldKey;
42
- key: string;
43
- pattern: string;
44
- info: T;
45
- };
46
- declare function useObject<T>(field: FieldKey, options: {
47
- defaultValue?: DefaultValue<object>;
48
- properties: Record<string, T>;
49
- patternProperties?: Record<string, T>;
50
- fallback?: T;
51
- }): {
52
- properties: PropertyItemInfo<T>[];
53
- onAppend(name: string, value?: unknown): void;
54
- onDelete(name: string): unknown;
55
- };
56
- //#endregion
57
5
  //#region src/lib/data-engine.d.ts
58
6
  type DefaultValue<T = unknown> = T | (() => T);
7
+ declare function getDefaultValue<T>(defaultValue: DefaultValue<T>): T;
59
8
  interface DataEngineListener {
60
9
  /**
61
10
  * when specified, only call the listener for events affecting the specified field.
@@ -75,10 +24,6 @@ interface DataEngineListener {
75
24
  * when `field` is specified, this is also fired when parent is deleted.
76
25
  */
77
26
  onDelete?: (key: FieldKey, ctx: OnDeleteContext) => void;
78
- /**
79
- * For listener attached to a namespace's engine, this is fired when the namespace is removed.
80
- */
81
- onUnmount?: () => void;
82
27
  }
83
28
  interface OnUpdateContext {
84
29
  /**
@@ -96,9 +41,16 @@ interface OnInitContext {
96
41
  /** custom data */
97
42
  custom?: Record<string, unknown>;
98
43
  }
44
+ interface NamespaceConfig {
45
+ reset?: (ctx: {
46
+ engine: DataEngine;
47
+ }) => void;
48
+ }
99
49
  declare class DataEngine {
100
50
  private data;
101
- private readonly namespaces;
51
+ readonly namespaces: Map<string, {
52
+ engine: DataEngine;
53
+ } & NamespaceConfig>;
102
54
  private readonly listeners;
103
55
  constructor(defaultValues?: DefaultValue<NonNullable<object>>);
104
56
  listen(listener: DataEngineListener): void;
@@ -106,11 +58,11 @@ declare class DataEngine {
106
58
  getData(): object;
107
59
  /**
108
60
  * init a field
109
- * @param key the key of field
61
+ * @param field the key of field
110
62
  * @param defaultValue the initial value, the field is also created for `undefined`
111
63
  * @returns the value of initialized field, or the current value of field if already initialized
112
64
  */
113
- init(key: FieldKey, defaultValue?: DefaultValue, ctx?: OnInitContext): unknown;
65
+ init(field: FieldKey, defaultValue?: DefaultValue, ctx?: OnInitContext): unknown;
114
66
  delete(key: FieldKey, ctx?: OnDeleteContext): unknown | undefined;
115
67
  get(key: FieldKey): unknown;
116
68
  /**
@@ -121,10 +73,63 @@ declare class DataEngine {
121
73
  /**
122
74
  * create an isolated data engine
123
75
  */
124
- namespace(namespace: string, initialValue?: DefaultValue<NonNullable<object>>): DataEngine;
76
+ namespace(namespace: string, initialValue?: DefaultValue<NonNullable<object>>, config?: NamespaceConfig): DataEngine;
125
77
  reset(data: NonNullable<object>): void;
126
- clearNamespaces(): void;
127
78
  }
79
+ //#endregion
80
+ //#region src/lib/stf.d.ts
81
+ interface Stf {
82
+ dataEngine: DataEngine;
83
+ }
84
+ declare function StfProvider({
85
+ value,
86
+ children
87
+ }: {
88
+ value: Stf;
89
+ children: ReactNode;
90
+ }): react_jsx_runtime0.JSX.Element;
91
+ declare function useStf(options: {
92
+ /**
93
+ * Note: the passed object will be modified in place, use `structuredClone()` to keep the original object unchanged.
94
+ */
95
+ defaultValues?: DefaultValue<Record<string, unknown>>;
96
+ }): Stf;
97
+ declare function useDataEngine(instance?: Stf | DataEngine): DataEngine;
98
+ interface ArrayItemInfo {
99
+ field: FieldKey;
100
+ index: number;
101
+ }
102
+ declare function useArray(field: FieldKey, options?: {
103
+ defaultValue?: DefaultValue<unknown[]>;
104
+ }): {
105
+ items: ArrayItemInfo[];
106
+ insertItem(itemValue?: unknown): void;
107
+ removeItem(index: number): void;
108
+ };
109
+ type PropertyItemInfo<T> = {
110
+ kind: 'fixed' | 'fallback';
111
+ field: FieldKey;
112
+ key: string;
113
+ info: T;
114
+ } | {
115
+ kind: 'pattern';
116
+ field: FieldKey;
117
+ key: string;
118
+ pattern: string;
119
+ info: T;
120
+ };
121
+ declare function useObject<T>(field: FieldKey, options: {
122
+ defaultValue?: DefaultValue<object>; /** ignore fixed properties unless defined */
123
+ lazy?: boolean;
124
+ properties: Record<string, T>;
125
+ patternProperties?: Record<string, T>;
126
+ fallback?: T;
127
+ }): {
128
+ properties: PropertyItemInfo<T>[];
129
+ _objectKeys: string[];
130
+ onAppend(name: string, value?: unknown): void;
131
+ onDelete(name: string): unknown;
132
+ };
128
133
  declare function useFieldValue<V = unknown>(key: FieldKey, options?: {
129
134
  stf?: Stf | DataEngine;
130
135
  defaultValue?: DefaultValue;
@@ -145,4 +150,4 @@ declare function useNamespace(options: {
145
150
  stf?: Stf | DataEngine;
146
151
  }): DataEngine;
147
152
  //#endregion
148
- export { ArrayItemInfo, DataEngine, DataEngineListener, DefaultValue, FieldKey, OnDeleteContext, OnInitContext, OnUpdateContext, PropertyItemInfo, Stf, StfProvider, useArray, useDataEngine, useFieldValue, useListener, useNamespace, useObject, useStf };
153
+ export { ArrayItemInfo, DataEngine, DataEngineListener, DefaultValue, FieldKey, NamespaceConfig, OnDeleteContext, OnInitContext, OnUpdateContext, PropertyItemInfo, Stf, StfProvider, getDefaultValue, useArray, useDataEngine, useFieldValue, useListener, useNamespace, useObject, useStf };
package/dist/index.js CHANGED
@@ -4,115 +4,6 @@ import { deepEqual, fieldKeyStartsWith, isPlainObject, objectGet, objectSet, str
4
4
  import { createContext, use, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
 
7
- //#region src/lib/stf.tsx
8
- const Context = createContext(null);
9
- function StfProvider({ value, children }) {
10
- return /* @__PURE__ */ jsx(Context, {
11
- value,
12
- children
13
- });
14
- }
15
- function useStf(options) {
16
- const { defaultValues } = options;
17
- const dataEngine = useMemo(() => new DataEngine(defaultValues), []);
18
- return useMemo(() => ({ dataEngine }), [dataEngine]);
19
- }
20
- function useDataEngine(instance) {
21
- if (instance instanceof DataEngine) return instance;
22
- if (instance) return instance.dataEngine;
23
- return use(Context).dataEngine;
24
- }
25
- function useArray(field, options = {}) {
26
- const engine = useDataEngine();
27
- const [items] = useFieldValue(field, {
28
- defaultValue: options.defaultValue,
29
- compute(value) {
30
- const items = [];
31
- if (Array.isArray(value)) for (let i = 0; i < value.length; i++) items.push({
32
- field: [...field, i],
33
- index: i
34
- });
35
- return items;
36
- },
37
- isChanged(prev, next) {
38
- return prev.length !== next.length;
39
- }
40
- });
41
- return {
42
- items,
43
- insertItem(itemValue) {
44
- const value = engine.get(field);
45
- engine.update(field, Array.isArray(value) ? [...value, itemValue] : [itemValue]);
46
- },
47
- removeItem(index) {
48
- engine.delete([...field, index]);
49
- }
50
- };
51
- }
52
- function useObject(field, options) {
53
- const engine = useDataEngine();
54
- const [objectKeys] = useFieldValue(field, {
55
- defaultValue: options.defaultValue,
56
- compute(currentValue) {
57
- return currentValue ? Object.keys(currentValue) : [];
58
- },
59
- isChanged(prev, next) {
60
- return !deepEqual(prev, next);
61
- }
62
- });
63
- return {
64
- properties: useMemo(() => {
65
- const properties = [];
66
- const unknownKeys = new Set(objectKeys);
67
- for (const [key, prop] of Object.entries(options.properties)) {
68
- unknownKeys.delete(key);
69
- properties.push({
70
- kind: "fixed",
71
- field: [...field, key],
72
- key,
73
- info: prop
74
- });
75
- }
76
- for (const [pattern, prop] of Object.entries(options.patternProperties ?? {})) {
77
- const regex = RegExp(pattern);
78
- for (const key of unknownKeys) {
79
- if (!key.match(regex)) continue;
80
- unknownKeys.delete(key);
81
- properties.push({
82
- kind: "pattern",
83
- info: prop,
84
- key,
85
- pattern,
86
- field: [...field, key]
87
- });
88
- }
89
- }
90
- if (options.fallback) for (const key of unknownKeys) properties.push({
91
- kind: "fallback",
92
- field: [...field, key],
93
- key,
94
- info: options.fallback
95
- });
96
- return properties;
97
- }, [
98
- field,
99
- objectKeys,
100
- options.fallback,
101
- options.patternProperties,
102
- options.properties
103
- ]),
104
- onAppend(name, value) {
105
- name = name.trim();
106
- if (name.length === 0) return;
107
- engine.init([...field, name], value);
108
- },
109
- onDelete(name) {
110
- return engine.delete([...field, name]);
111
- }
112
- };
113
- }
114
-
115
- //#endregion
116
7
  //#region src/lib/data-engine.ts
117
8
  function getDefaultValue(defaultValue) {
118
9
  return typeof defaultValue === "function" ? defaultValue() : defaultValue;
@@ -160,10 +51,6 @@ var ListenerManager = class {
160
51
  for (const v of listeners) v.onDelete?.(field, ctx);
161
52
  }
162
53
  }
163
- onUnmount() {
164
- for (const v of this.unindexed) v.onUnmount?.();
165
- for (const listeners of this.indexed.values()) for (const v of listeners) v.onUnmount?.();
166
- }
167
54
  };
168
55
  var DataEngine = class DataEngine {
169
56
  constructor(defaultValues = {}) {
@@ -182,37 +69,44 @@ var DataEngine = class DataEngine {
182
69
  }
183
70
  /**
184
71
  * init a field
185
- * @param key the key of field
72
+ * @param field the key of field
186
73
  * @param defaultValue the initial value, the field is also created for `undefined`
187
74
  * @returns the value of initialized field, or the current value of field if already initialized
188
75
  */
189
- init(key, defaultValue, ctx = {}) {
190
- if (key.length === 0) return this.data;
76
+ init(field, defaultValue, ctx = {}) {
77
+ if (field.length === 0) return this.data;
191
78
  const parentKey = [];
192
79
  const parentUpdateCtx = {
193
80
  swallow: true,
194
81
  ...ctx
195
82
  };
196
- let cur = this.data;
197
- for (let i = 0; i < key.length; i++) {
198
- const propKey = key[i];
199
- const propValue = cur[propKey];
200
- if (i === key.length - 1) {
201
- if (propValue !== void 0) return propValue;
202
- cur[propKey] = getDefaultValue(defaultValue);
203
- this.listeners.onUpdate(parentKey, parentUpdateCtx);
204
- this.listeners.onInit(key, ctx);
205
- return cur[propKey];
206
- } else if (isPlainObject(propValue)) {
207
- cur = propValue;
208
- parentKey.push(propKey);
83
+ let parent = this.data;
84
+ const fieldsToInit = [];
85
+ let initStart = null;
86
+ for (let i = 0; i < field.length; i++) {
87
+ const key = field[i];
88
+ const value = parent[key];
89
+ if (i === field.length - 1) {
90
+ if (value !== void 0) return value;
91
+ const out = parent[key] = getDefaultValue(defaultValue);
92
+ fieldsToInit.push(field);
93
+ for (const initField of fieldsToInit) this.listeners.onInit(initField, ctx);
94
+ this.listeners.onUpdate(initStart ?? parentKey, parentUpdateCtx);
95
+ return out;
96
+ } else if (isPlainObject(value) || Array.isArray(value)) {
97
+ parent = value;
98
+ parentKey.push(key);
209
99
  } else {
210
- cur = cur[propKey] = {};
211
- this.listeners.onUpdate(parentKey, parentUpdateCtx);
212
- parentKey.push(propKey);
213
- if (propValue !== void 0) {
214
- console.warn(`the original value of field ${parentKey.join(".")} is overidden, this might be unexpected.`);
100
+ const nextKey = field[i + 1];
101
+ parent = parent[key] = typeof nextKey === "number" ? new Array(nextKey + 1) : {};
102
+ if (value === void 0) {
103
+ initStart ??= [...parentKey];
104
+ parentKey.push(key);
105
+ fieldsToInit.push([...parentKey]);
106
+ } else {
107
+ parentKey.push(key);
215
108
  this.listeners.onUpdate(parentKey, parentUpdateCtx);
109
+ console.warn(`the original value of field ${parentKey.join(".")} is overidden, this might be unexpected.`);
216
110
  }
217
111
  }
218
112
  }
@@ -222,16 +116,18 @@ var DataEngine = class DataEngine {
222
116
  const parentKey = key.slice(0, -1);
223
117
  const prop = key[key.length - 1];
224
118
  const parent = this.get(parentKey);
225
- if (Array.isArray(parent) && typeof prop === "number") {
119
+ if (typeof prop === "number" && Array.isArray(parent)) {
120
+ if (parent.length === 0) return;
121
+ const isLast = prop === parent.length - 1;
226
122
  const deleted = parent.splice(prop, 1);
227
123
  if (deleted.length === 0) return;
228
124
  this.listeners.onDelete(key, ctx);
229
125
  this.listeners.onUpdate(parentKey, {
230
- swallow: false,
126
+ swallow: isLast,
231
127
  ...ctx
232
128
  });
233
129
  return deleted[0];
234
- } else if (isPlainObject(parent)) {
130
+ } else if (isPlainObject(parent) && prop in parent) {
235
131
  const temp = parent[prop];
236
132
  delete parent[prop];
237
133
  this.listeners.onDelete(key, ctx);
@@ -264,44 +160,156 @@ var DataEngine = class DataEngine {
264
160
  /**
265
161
  * create an isolated data engine
266
162
  */
267
- namespace(namespace, initialValue) {
163
+ namespace(namespace, initialValue, config) {
268
164
  let child = this.namespaces.get(namespace);
269
165
  if (!child) {
270
- child = new DataEngine(initialValue);
166
+ child = {
167
+ engine: new DataEngine(initialValue),
168
+ ...config
169
+ };
271
170
  this.namespaces.set(namespace, child);
272
- }
273
- return child;
171
+ } else Object.assign(child, config);
172
+ return child.engine;
274
173
  }
275
174
  reset(data) {
276
175
  this.update([], data);
277
- }
278
- clearNamespaces() {
279
- for (const [name, engine] of this.namespaces) {
280
- this.namespaces.delete(name);
281
- engine.listeners.onUnmount();
282
- }
176
+ for (const { engine, reset } of this.namespaces.values()) reset?.({ engine });
283
177
  }
284
178
  };
179
+
180
+ //#endregion
181
+ //#region src/lib/stf.tsx
182
+ const Context = createContext(null);
183
+ function StfProvider({ value, children }) {
184
+ return /* @__PURE__ */ jsx(Context, {
185
+ value,
186
+ children
187
+ });
188
+ }
189
+ function useStf(options) {
190
+ const optionsRef = useRef(options);
191
+ optionsRef.current = options;
192
+ return useMemo(() => ({ dataEngine: new DataEngine(optionsRef.current.defaultValues) }), []);
193
+ }
194
+ function useDataEngine(instance) {
195
+ if (instance instanceof DataEngine) return instance;
196
+ if (instance) return instance.dataEngine;
197
+ return use(Context).dataEngine;
198
+ }
199
+ function useArray(field, options = {}) {
200
+ const engine = useDataEngine();
201
+ const { defaultValue } = options;
202
+ const [items] = useFieldValue(field, {
203
+ defaultValue,
204
+ compute(value) {
205
+ const items = [];
206
+ if (!Array.isArray(value)) return items;
207
+ for (let i = 0; i < value.length; i++) items.push({
208
+ field: [...field, i],
209
+ index: i
210
+ });
211
+ return items;
212
+ },
213
+ isChanged(prev, next) {
214
+ return prev.length !== next.length;
215
+ }
216
+ });
217
+ return {
218
+ items,
219
+ insertItem(itemValue) {
220
+ const value = engine.get(field);
221
+ const idx = Array.isArray(value) ? value.length : 0;
222
+ engine.init([...field, idx], itemValue);
223
+ },
224
+ removeItem(index) {
225
+ engine.delete([...field, index]);
226
+ }
227
+ };
228
+ }
229
+ function useObject(field, options) {
230
+ const { properties: definedProps, patternProperties: definedPatternProps = {}, defaultValue, fallback, lazy } = options;
231
+ const engine = useDataEngine();
232
+ const [objectKeys] = useFieldValue(field, {
233
+ defaultValue,
234
+ compute(currentValue) {
235
+ return isPlainObject(currentValue) ? Object.keys(currentValue) : [];
236
+ },
237
+ isChanged(prev, next) {
238
+ return !deepEqual(prev, next);
239
+ }
240
+ });
241
+ return {
242
+ properties: useMemo(() => {
243
+ const properties = [];
244
+ const unknownKeys = new Set(objectKeys);
245
+ for (const [key, prop] of Object.entries(definedProps)) {
246
+ if (lazy && !unknownKeys.has(key)) continue;
247
+ unknownKeys.delete(key);
248
+ properties.push({
249
+ kind: "fixed",
250
+ field: [...field, key],
251
+ key,
252
+ info: prop
253
+ });
254
+ }
255
+ for (const [pattern, prop] of Object.entries(definedPatternProps)) {
256
+ const regex = RegExp(pattern);
257
+ for (const key of unknownKeys) {
258
+ if (!key.match(regex)) continue;
259
+ unknownKeys.delete(key);
260
+ properties.push({
261
+ kind: "pattern",
262
+ info: prop,
263
+ key,
264
+ pattern,
265
+ field: [...field, key]
266
+ });
267
+ }
268
+ }
269
+ if (fallback) for (const key of unknownKeys) properties.push({
270
+ kind: "fallback",
271
+ field: [...field, key],
272
+ key,
273
+ info: fallback
274
+ });
275
+ return properties;
276
+ }, [
277
+ definedPatternProps,
278
+ definedProps,
279
+ fallback,
280
+ field,
281
+ lazy,
282
+ objectKeys
283
+ ]),
284
+ _objectKeys: objectKeys,
285
+ onAppend(name, value) {
286
+ name = name.trim();
287
+ if (name.length === 0) return;
288
+ engine.init([...field, name], value);
289
+ },
290
+ onDelete(name) {
291
+ return engine.delete([...field, name]);
292
+ }
293
+ };
294
+ }
285
295
  function useFieldValue(key, options = {}) {
286
296
  const { stf, compute = (v) => v, defaultValue, isChanged = (a, b) => a !== b } = options;
287
297
  const engine = useDataEngine(stf);
288
- const [value, setValue] = useState(() => compute(engine.init(key, defaultValue)));
298
+ const [value, setValue] = useState(() => compute(defaultValue === void 0 ? engine.get(key) : engine.init(key, defaultValue)));
289
299
  const prevEngineRef = useRef(engine);
290
300
  if (prevEngineRef.current !== engine) {
291
- setValue(compute(engine.init(key, defaultValue)));
301
+ setValue(compute(defaultValue === void 0 ? engine.get(key) : engine.init(key, defaultValue)));
292
302
  prevEngineRef.current = engine;
293
303
  }
304
+ function onUpdate() {
305
+ const computed = compute(engine.get(key));
306
+ setValue((prev) => isChanged(prev, computed) ? computed : prev);
307
+ }
294
308
  useListener({
295
309
  field: key,
296
310
  stf,
297
- onInit() {
298
- const computed = compute(engine.get(key));
299
- setValue((prev) => isChanged(prev, computed) ? computed : prev);
300
- },
301
- onUpdate() {
302
- const computed = compute(engine.get(key));
303
- setValue((prev) => isChanged(prev, computed) ? computed : prev);
304
- },
311
+ onInit: onUpdate,
312
+ onUpdate,
305
313
  onDelete() {
306
314
  const computed = compute(void 0);
307
315
  setValue((prev) => isChanged(prev, computed) ? computed : prev);
@@ -316,9 +324,6 @@ function useListener(listener) {
316
324
  useEffect(() => {
317
325
  const internal = {
318
326
  field: listener.field,
319
- onUnmount(...args) {
320
- return listenerRef.current.onUnmount?.(...args);
321
- },
322
327
  onDelete(...args) {
323
328
  return listenerRef.current.onDelete?.(...args);
324
329
  },
@@ -337,16 +342,10 @@ function useListener(listener) {
337
342
  }
338
343
  function useNamespace(options) {
339
344
  const { namespace, stf, initial } = options;
340
- const engine = useDataEngine(stf);
341
- const [value, setValue] = useState(() => engine.namespace(namespace, initial));
342
- useListener({
343
- stf: value,
344
- onUnmount() {
345
- setValue(engine.namespace(namespace, initial));
346
- }
347
- });
348
- return value;
345
+ return useDataEngine(stf).namespace(namespace, initial, { reset({ engine }) {
346
+ engine.update([], getDefaultValue(initial));
347
+ } });
349
348
  }
350
349
 
351
350
  //#endregion
352
- export { DataEngine, StfProvider, useArray, useDataEngine, useFieldValue, useListener, useNamespace, useObject, useStf };
351
+ export { DataEngine, StfProvider, getDefaultValue, useArray, useDataEngine, useFieldValue, useListener, useNamespace, useObject, useStf };
@@ -7,7 +7,7 @@ declare function objectGet(obj: unknown, key: (string | number)[]): unknown | un
7
7
  *
8
8
  * @returns updated value, throw error if parent object doesn't exist
9
9
  */
10
- declare function objectSet(obj: unknown, key: FieldKey, value: unknown): unknown;
10
+ declare function objectSet(obj: unknown, field: FieldKey, value: unknown): unknown;
11
11
  /**
12
12
  * doesn't handle recursive objects
13
13
  */
package/dist/lib/utils.js CHANGED
@@ -1,10 +1,9 @@
1
1
  //#region src/lib/utils.ts
2
2
  function objectGet(obj, key) {
3
3
  let cur = obj;
4
- for (const prop of key) {
5
- if (!isPlainObject(cur) || !(prop in cur)) return;
6
- cur = cur[prop];
7
- }
4
+ for (const prop of key) if (isPlainObject(cur) && prop in cur) cur = cur[prop];
5
+ else if (typeof prop === "number" && Array.isArray(cur)) cur = cur[prop];
6
+ else return;
8
7
  return cur;
9
8
  }
10
9
  /**
@@ -12,11 +11,13 @@ function objectGet(obj, key) {
12
11
  *
13
12
  * @returns updated value, throw error if parent object doesn't exist
14
13
  */
15
- function objectSet(obj, key, value) {
16
- if (key.length === 0) return value;
17
- const parent = objectGet(obj, key.slice(0, -1));
18
- if (!isPlainObject(parent)) throw new Error("missing parent object");
19
- parent[key[key.length - 1]] = value;
14
+ function objectSet(obj, field, value) {
15
+ if (field.length === 0) return value;
16
+ const parent = objectGet(obj, field.slice(0, -1));
17
+ const key = field[field.length - 1];
18
+ if (isPlainObject(parent)) parent[key] = value;
19
+ else if (typeof key === "number" && Array.isArray(parent)) parent[key] = value;
20
+ else throw new Error("missing parent object");
20
21
  return obj;
21
22
  }
22
23
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fumari/stf",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Schema to Form.",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -19,9 +19,9 @@
19
19
  "access": "public"
20
20
  },
21
21
  "devDependencies": {
22
- "@types/node": "25.2.3",
22
+ "@types/node": "25.3.3",
23
23
  "@types/react": "^19.2.14",
24
- "tsdown": "^0.20.3",
24
+ "tsdown": "0.20.3",
25
25
  "eslint-config-custom": "0.0.0",
26
26
  "tsconfig": "0.0.0"
27
27
  },