@lucas-barake/effect-form 0.15.0 → 0.16.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/src/Field.ts CHANGED
@@ -1,29 +1,29 @@
1
- import * as Schema from "effect/Schema"
1
+ import * as Schema from "effect/Schema";
2
2
 
3
- export const TypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Field")
3
+ export const TypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Field");
4
4
 
5
- export type TypeId = typeof TypeId
5
+ export type TypeId = typeof TypeId;
6
6
 
7
7
  export interface FieldDef<K extends string, S extends Schema.Schema.Any> {
8
- readonly _tag: "field"
9
- readonly key: K
10
- readonly schema: S
8
+ readonly _tag: "field";
9
+ readonly key: K;
10
+ readonly schema: S;
11
11
  }
12
12
 
13
13
  export interface ArrayFieldDef<K extends string, S extends Schema.Schema.Any> {
14
- readonly _tag: "array"
15
- readonly key: K
16
- readonly itemSchema: S
14
+ readonly _tag: "array";
15
+ readonly key: K;
16
+ readonly itemSchema: S;
17
17
  }
18
18
 
19
- export type AnyFieldDef = FieldDef<string, Schema.Schema.Any> | ArrayFieldDef<string, Schema.Schema.Any>
19
+ export type AnyFieldDef = FieldDef<string, Schema.Schema.Any> | ArrayFieldDef<string, Schema.Schema.Any>;
20
20
 
21
- export type FieldsRecord = Record<string, AnyFieldDef>
21
+ export type FieldsRecord = Record<string, AnyFieldDef>;
22
22
 
23
23
  export const isArrayFieldDef = (def: AnyFieldDef): def is ArrayFieldDef<string, Schema.Schema.Any> =>
24
- def._tag === "array"
24
+ def._tag === "array";
25
25
 
26
- export const isFieldDef = (def: AnyFieldDef): def is FieldDef<string, Schema.Schema.Any> => def._tag === "field"
26
+ export const isFieldDef = (def: AnyFieldDef): def is FieldDef<string, Schema.Schema.Any> => def._tag === "field";
27
27
 
28
28
  export const makeField = <K extends string, S extends Schema.Schema.Any>(
29
29
  key: K,
@@ -32,7 +32,7 @@ export const makeField = <K extends string, S extends Schema.Schema.Any>(
32
32
  _tag: "field",
33
33
  key,
34
34
  schema,
35
- })
35
+ });
36
36
 
37
37
  export const makeArrayField = <K extends string, S extends Schema.Schema.Any>(
38
38
  key: K,
@@ -41,64 +41,64 @@ export const makeArrayField = <K extends string, S extends Schema.Schema.Any>(
41
41
  _tag: "array",
42
42
  key,
43
43
  itemSchema,
44
- })
44
+ });
45
45
 
46
46
  export type EncodedFromFields<T extends FieldsRecord> = {
47
47
  readonly [K in keyof T]: T[K] extends FieldDef<any, infer S> ? Schema.Schema.Encoded<S>
48
48
  : T[K] extends ArrayFieldDef<any, infer S> ? ReadonlyArray<Schema.Schema.Encoded<S>>
49
- : never
50
- }
49
+ : never;
50
+ };
51
51
 
52
52
  export type DecodedFromFields<T extends FieldsRecord> = {
53
53
  readonly [K in keyof T]: T[K] extends FieldDef<any, infer S> ? Schema.Schema.Type<S>
54
54
  : T[K] extends ArrayFieldDef<any, infer S> ? ReadonlyArray<Schema.Schema.Type<S>>
55
- : never
56
- }
55
+ : never;
56
+ };
57
57
 
58
58
  export const getDefaultFromSchema = (schema: Schema.Schema.Any): unknown => {
59
- const ast = schema.ast
59
+ const ast = schema.ast;
60
60
  switch (ast._tag) {
61
61
  case "StringKeyword":
62
62
  case "TemplateLiteral":
63
- return ""
63
+ return "";
64
64
  case "NumberKeyword":
65
- return 0
65
+ return 0;
66
66
  case "BooleanKeyword":
67
- return false
67
+ return false;
68
68
  case "TypeLiteral": {
69
- const result: Record<string, unknown> = {}
69
+ const result: Record<string, unknown> = {};
70
70
  for (const prop of ast.propertySignatures) {
71
- result[prop.name as string] = getDefaultFromSchema(Schema.make(prop.type))
71
+ result[prop.name as string] = getDefaultFromSchema(Schema.make(prop.type));
72
72
  }
73
- return result
73
+ return result;
74
74
  }
75
75
  case "Transformation":
76
- return getDefaultFromSchema(Schema.make(ast.from))
76
+ return getDefaultFromSchema(Schema.make(ast.from));
77
77
  case "Refinement":
78
- return getDefaultFromSchema(Schema.make(ast.from))
78
+ return getDefaultFromSchema(Schema.make(ast.from));
79
79
  case "Suspend":
80
- return getDefaultFromSchema(Schema.make(ast.f()))
80
+ return getDefaultFromSchema(Schema.make(ast.f()));
81
81
  default:
82
- return ""
82
+ return "";
83
83
  }
84
- }
84
+ };
85
85
 
86
86
  export const getDefaultEncodedValues = (fields: FieldsRecord): Record<string, unknown> => {
87
- const result: Record<string, unknown> = {}
87
+ const result: Record<string, unknown> = {};
88
88
  for (const [key, def] of Object.entries(fields)) {
89
89
  if (isArrayFieldDef(def)) {
90
- result[key] = []
90
+ result[key] = [];
91
91
  } else {
92
- result[key] = ""
92
+ result[key] = "";
93
93
  }
94
94
  }
95
- return result
96
- }
95
+ return result;
96
+ };
97
97
 
98
98
  export const createTouchedRecord = (fields: FieldsRecord, value: boolean): Record<string, boolean> => {
99
- const result: Record<string, boolean> = {}
99
+ const result: Record<string, boolean> = {};
100
100
  for (const key of Object.keys(fields)) {
101
- result[key] = value
101
+ result[key] = value;
102
102
  }
103
- return result
104
- }
103
+ return result;
104
+ };
package/src/FormAtoms.ts CHANGED
@@ -81,6 +81,7 @@ export interface FormAtoms<TFields extends Field.FieldsRecord, R, A = void, E =
81
81
  readonly setValue: <S>(field: FormBuilder.FieldRef<S>) => Atom.Writable<void, S | ((prev: S) => S)>;
82
82
 
83
83
  readonly getFieldAtom: <S>(field: FormBuilder.FieldRef<S>) => Atom.Atom<Option.Option<S>>;
84
+ readonly getFieldIsDirty: (field: FormBuilder.FieldRef<any>) => Atom.Atom<boolean>;
84
85
 
85
86
  /**
86
87
  * Root anchor atom for the form's dependency graph.
@@ -550,6 +551,9 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
550
551
  return safeAtom;
551
552
  };
552
553
 
554
+ const getFieldIsDirty = (field: FormBuilder.FieldRef<any>): Atom.Atom<boolean> =>
555
+ getOrCreateFieldAtoms(field.key).isDirtyAtom;
556
+
553
557
  const mountAtom = Atom.readable((get) => {
554
558
  get(stateAtom);
555
559
  get(errorsAtom);
@@ -583,6 +587,7 @@ export const make = <TFields extends Field.FieldsRecord, R, A, E, SubmitArgs = v
583
587
  setValuesAtom,
584
588
  setValue,
585
589
  getFieldAtom,
590
+ getFieldIsDirty,
586
591
  mountAtom,
587
592
  keepAliveActiveAtom,
588
593
  } as FormAtoms<TFields, R, A, E, SubmitArgs>;
@@ -1,8 +1,8 @@
1
- import type * as Registry from "@effect-atom/atom/Registry"
2
- import type * as Effect from "effect/Effect"
3
- import type * as Option from "effect/Option"
4
- import * as Predicate from "effect/Predicate"
5
- import * as Schema from "effect/Schema"
1
+ import type * as Registry from "@effect-atom/atom/Registry";
2
+ import type * as Effect from "effect/Effect";
3
+ import type * as Option from "effect/Option";
4
+ import * as Predicate from "effect/Predicate";
5
+ import * as Schema from "effect/Schema";
6
6
 
7
7
  import type {
8
8
  AnyFieldDef,
@@ -11,91 +11,91 @@ import type {
11
11
  EncodedFromFields,
12
12
  FieldDef,
13
13
  FieldsRecord,
14
- } from "./Field.js"
15
- import { isArrayFieldDef, isFieldDef, makeField } from "./Field.js"
14
+ } from "./Field.js";
15
+ import { isArrayFieldDef, isFieldDef, makeField } from "./Field.js";
16
16
 
17
17
  export interface SubmittedValues<TFields extends FieldsRecord> {
18
- readonly encoded: EncodedFromFields<TFields>
19
- readonly decoded: DecodedFromFields<TFields>
18
+ readonly encoded: EncodedFromFields<TFields>;
19
+ readonly decoded: DecodedFromFields<TFields>;
20
20
  }
21
21
 
22
- export const FieldTypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Field")
22
+ export const FieldTypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Field");
23
23
 
24
- export type FieldTypeId = typeof FieldTypeId
24
+ export type FieldTypeId = typeof FieldTypeId;
25
25
 
26
26
  export interface FieldRef<S> {
27
- readonly [FieldTypeId]: FieldTypeId
28
- readonly _S: S
29
- readonly key: string
27
+ readonly [FieldTypeId]: FieldTypeId;
28
+ readonly _S: S;
29
+ readonly key: string;
30
30
  }
31
31
 
32
32
  export const makeFieldRef = <S>(key: string): FieldRef<S> => ({
33
33
  [FieldTypeId]: FieldTypeId,
34
34
  _S: undefined as any,
35
35
  key,
36
- })
36
+ });
37
37
 
38
- export const TypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Form")
38
+ export const TypeId: unique symbol = Symbol.for("@lucas-barake/effect-form/Form");
39
39
 
40
- export type TypeId = typeof TypeId
40
+ export type TypeId = typeof TypeId;
41
41
 
42
42
  export interface FormState<TFields extends FieldsRecord> {
43
- readonly values: EncodedFromFields<TFields>
44
- readonly initialValues: EncodedFromFields<TFields>
45
- readonly lastSubmittedValues: Option.Option<SubmittedValues<TFields>>
46
- readonly touched: { readonly [K in keyof TFields]: boolean }
47
- readonly submitCount: number
48
- readonly dirtyFields: ReadonlySet<string>
43
+ readonly values: EncodedFromFields<TFields>;
44
+ readonly initialValues: EncodedFromFields<TFields>;
45
+ readonly lastSubmittedValues: Option.Option<SubmittedValues<TFields>>;
46
+ readonly touched: { readonly [K in keyof TFields]: boolean; };
47
+ readonly submitCount: number;
48
+ readonly dirtyFields: ReadonlySet<string>;
49
49
  }
50
50
 
51
51
  interface SyncRefinement {
52
- readonly _tag: "sync"
53
- readonly fn: (values: unknown) => Schema.FilterOutput
52
+ readonly _tag: "sync";
53
+ readonly fn: (values: unknown) => Schema.FilterOutput;
54
54
  }
55
55
 
56
56
  interface AsyncRefinement {
57
- readonly _tag: "async"
58
- readonly fn: (values: unknown) => Effect.Effect<Schema.FilterOutput, never, unknown>
57
+ readonly _tag: "async";
58
+ readonly fn: (values: unknown) => Effect.Effect<Schema.FilterOutput, never, unknown>;
59
59
  }
60
60
 
61
- type Refinement = SyncRefinement | AsyncRefinement
61
+ type Refinement = SyncRefinement | AsyncRefinement;
62
62
 
63
63
  export interface FormBuilder<TFields extends FieldsRecord, R> {
64
- readonly [TypeId]: TypeId
65
- readonly fields: TFields
66
- readonly refinements: ReadonlyArray<Refinement>
67
- readonly _R?: R
64
+ readonly [TypeId]: TypeId;
65
+ readonly fields: TFields;
66
+ readonly refinements: ReadonlyArray<Refinement>;
67
+ readonly _R?: R;
68
68
 
69
69
  addField<K extends string, S extends Schema.Schema.Any>(
70
70
  this: FormBuilder<TFields, R>,
71
71
  field: FieldDef<K, S>,
72
- ): FormBuilder<TFields & { readonly [key in K]: FieldDef<K, S> }, R | Schema.Schema.Context<S>>
72
+ ): FormBuilder<TFields & { readonly [key in K]: FieldDef<K, S>; }, R | Schema.Schema.Context<S>>;
73
73
 
74
74
  addField<K extends string, S extends Schema.Schema.Any>(
75
75
  this: FormBuilder<TFields, R>,
76
76
  field: ArrayFieldDef<K, S>,
77
- ): FormBuilder<TFields & { readonly [key in K]: ArrayFieldDef<K, S> }, R | Schema.Schema.Context<S>>
77
+ ): FormBuilder<TFields & { readonly [key in K]: ArrayFieldDef<K, S>; }, R | Schema.Schema.Context<S>>;
78
78
 
79
79
  addField<K extends string, S extends Schema.Schema.Any>(
80
80
  this: FormBuilder<TFields, R>,
81
81
  key: K,
82
82
  schema: S,
83
- ): FormBuilder<TFields & { readonly [key in K]: FieldDef<K, S> }, R | Schema.Schema.Context<S>>
83
+ ): FormBuilder<TFields & { readonly [key in K]: FieldDef<K, S>; }, R | Schema.Schema.Context<S>>;
84
84
 
85
85
  merge<TFields2 extends FieldsRecord, R2>(
86
86
  this: FormBuilder<TFields, R>,
87
87
  other: FormBuilder<TFields2, R2>,
88
- ): FormBuilder<TFields & TFields2, R | R2>
88
+ ): FormBuilder<TFields & TFields2, R | R2>;
89
89
 
90
90
  refine(
91
91
  this: FormBuilder<TFields, R>,
92
92
  predicate: (values: DecodedFromFields<TFields>) => Schema.FilterOutput,
93
- ): FormBuilder<TFields, R>
93
+ ): FormBuilder<TFields, R>;
94
94
 
95
95
  refineEffect<RD>(
96
96
  this: FormBuilder<TFields, R>,
97
97
  predicate: (values: DecodedFromFields<TFields>) => Effect.Effect<Schema.FilterOutput, never, RD>,
98
- ): FormBuilder<TFields, R | Exclude<RD, Registry.AtomRegistry>>
98
+ ): FormBuilder<TFields, R | Exclude<RD, Registry.AtomRegistry>>;
99
99
  }
100
100
 
101
101
  const FormBuilderProto = {
@@ -107,76 +107,76 @@ const FormBuilderProto = {
107
107
  ): FormBuilder<any, any> {
108
108
  const field = typeof keyOrField === "string"
109
109
  ? makeField(keyOrField, schema!)
110
- : keyOrField
111
- const newSelf = Object.create(FormBuilderProto)
112
- newSelf.fields = { ...this.fields, [field.key]: field }
113
- newSelf.refinements = this.refinements
114
- return newSelf
110
+ : keyOrField;
111
+ const newSelf = Object.create(FormBuilderProto);
112
+ newSelf.fields = { ...this.fields, [field.key]: field };
113
+ newSelf.refinements = this.refinements;
114
+ return newSelf;
115
115
  },
116
116
  merge<TFields extends FieldsRecord, R, TFields2 extends FieldsRecord, R2>(
117
117
  this: FormBuilder<TFields, R>,
118
118
  other: FormBuilder<TFields2, R2>,
119
119
  ): FormBuilder<TFields & TFields2, R | R2> {
120
- const newSelf = Object.create(FormBuilderProto)
121
- newSelf.fields = { ...this.fields, ...other.fields }
122
- newSelf.refinements = [...this.refinements, ...other.refinements]
123
- return newSelf
120
+ const newSelf = Object.create(FormBuilderProto);
121
+ newSelf.fields = { ...this.fields, ...other.fields };
122
+ newSelf.refinements = [...this.refinements, ...other.refinements];
123
+ return newSelf;
124
124
  },
125
125
  refine<TFields extends FieldsRecord, R>(
126
126
  this: FormBuilder<TFields, R>,
127
127
  predicate: (values: DecodedFromFields<TFields>) => Schema.FilterOutput,
128
128
  ): FormBuilder<TFields, R> {
129
- const newSelf = Object.create(FormBuilderProto)
130
- newSelf.fields = this.fields
129
+ const newSelf = Object.create(FormBuilderProto);
130
+ newSelf.fields = this.fields;
131
131
  newSelf.refinements = [
132
132
  ...this.refinements,
133
133
  { _tag: "sync" as const, fn: (values: unknown) => predicate(values as DecodedFromFields<TFields>) },
134
- ]
135
- return newSelf
134
+ ];
135
+ return newSelf;
136
136
  },
137
137
  refineEffect<TFields extends FieldsRecord, R, RD>(
138
138
  this: FormBuilder<TFields, R>,
139
139
  predicate: (values: DecodedFromFields<TFields>) => Effect.Effect<Schema.FilterOutput, never, RD>,
140
140
  ): FormBuilder<TFields, R | Exclude<RD, Registry.AtomRegistry>> {
141
- const newSelf = Object.create(FormBuilderProto)
142
- newSelf.fields = this.fields
141
+ const newSelf = Object.create(FormBuilderProto);
142
+ newSelf.fields = this.fields;
143
143
  newSelf.refinements = [
144
144
  ...this.refinements,
145
145
  { _tag: "async" as const, fn: (values: unknown) => predicate(values as DecodedFromFields<TFields>) },
146
- ]
147
- return newSelf
146
+ ];
147
+ return newSelf;
148
148
  },
149
- }
149
+ };
150
150
 
151
- export const isFormBuilder = (u: unknown): u is FormBuilder<any, any> => Predicate.hasProperty(u, TypeId)
151
+ export const isFormBuilder = (u: unknown): u is FormBuilder<any, any> => Predicate.hasProperty(u, TypeId);
152
152
 
153
153
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
154
154
  export const empty: FormBuilder<{}, never> = (() => {
155
- const self = Object.create(FormBuilderProto)
156
- self.fields = {}
157
- self.refinements = []
158
- return self
159
- })()
155
+ const self = Object.create(FormBuilderProto);
156
+ self.fields = {};
157
+ self.refinements = [];
158
+ return self;
159
+ })();
160
160
 
161
161
  export const buildSchema = <TFields extends FieldsRecord, R>(
162
162
  self: FormBuilder<TFields, R>,
163
163
  ): Schema.Schema<DecodedFromFields<TFields>, EncodedFromFields<TFields>, R> => {
164
- const schemaFields: Record<string, Schema.Schema.Any> = {}
164
+ const schemaFields: Record<string, Schema.Schema.Any> = {};
165
165
  for (const [key, def] of Object.entries(self.fields)) {
166
166
  if (isArrayFieldDef(def)) {
167
- schemaFields[key] = Schema.Array(def.itemSchema)
167
+ schemaFields[key] = Schema.Array(def.itemSchema);
168
168
  } else if (isFieldDef(def)) {
169
- schemaFields[key] = def.schema
169
+ schemaFields[key] = def.schema;
170
170
  }
171
171
  }
172
172
 
173
- let schema: Schema.Schema<any, any, any> = Schema.Struct(schemaFields)
173
+ let schema: Schema.Schema<any, any, any> = Schema.Struct(schemaFields);
174
174
 
175
175
  for (const refinement of self.refinements) {
176
176
  if (refinement._tag === "sync") {
177
- schema = schema.pipe(Schema.filter(refinement.fn))
177
+ schema = schema.pipe(Schema.filter(refinement.fn));
178
178
  } else {
179
- schema = schema.pipe(Schema.filterEffect(refinement.fn))
179
+ schema = schema.pipe(Schema.filterEffect(refinement.fn));
180
180
  }
181
181
  }
182
182
 
@@ -184,5 +184,5 @@ export const buildSchema = <TFields extends FieldsRecord, R>(
184
184
  DecodedFromFields<TFields>,
185
185
  EncodedFromFields<TFields>,
186
186
  R
187
- >
188
- }
187
+ >;
188
+ };
package/src/Mode.ts CHANGED
@@ -1,33 +1,33 @@
1
- import * as Duration from "effect/Duration"
1
+ import * as Duration from "effect/Duration";
2
2
 
3
3
  export type FormMode =
4
4
  | "onSubmit"
5
5
  | "onBlur"
6
6
  | "onChange"
7
- | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit?: false } }
8
- | { readonly onBlur: { readonly autoSubmit: true } }
9
- | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit: true } }
7
+ | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit?: false; }; }
8
+ | { readonly onBlur: { readonly autoSubmit: true; }; }
9
+ | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit: true; }; };
10
10
 
11
11
  export type FormModeWithoutAutoSubmit =
12
12
  | "onSubmit"
13
13
  | "onBlur"
14
14
  | "onChange"
15
- | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit?: false } }
15
+ | { readonly onChange: { readonly debounce: Duration.DurationInput; readonly autoSubmit?: false; }; };
16
16
 
17
17
  export interface ParsedMode {
18
- readonly validation: "onSubmit" | "onBlur" | "onChange"
19
- readonly debounce: number | null
20
- readonly autoSubmit: boolean
18
+ readonly validation: "onSubmit" | "onBlur" | "onChange";
19
+ readonly debounce: number | null;
20
+ readonly autoSubmit: boolean;
21
21
  }
22
22
 
23
23
  export const parse = (mode: FormMode = "onSubmit"): ParsedMode => {
24
24
  if (typeof mode === "string") {
25
- return { validation: mode, debounce: null, autoSubmit: false }
25
+ return { validation: mode, debounce: null, autoSubmit: false };
26
26
  }
27
27
  if ("onBlur" in mode) {
28
- return { validation: "onBlur", debounce: null, autoSubmit: true }
28
+ return { validation: "onBlur", debounce: null, autoSubmit: true };
29
29
  }
30
- const debounceMs = Duration.toMillis(mode.onChange.debounce)
31
- const autoSubmit = mode.onChange.autoSubmit === true
32
- return { validation: "onChange", debounce: debounceMs, autoSubmit }
33
- }
30
+ const debounceMs = Duration.toMillis(mode.onChange.debounce);
31
+ const autoSubmit = mode.onChange.autoSubmit === true;
32
+ return { validation: "onChange", debounce: debounceMs, autoSubmit };
33
+ };
package/src/Path.ts CHANGED
@@ -1,68 +1,68 @@
1
- const BRACKET_NOTATION_REGEX = /\[(\d+)\]/g
1
+ const BRACKET_NOTATION_REGEX = /\[(\d+)\]/g;
2
2
 
3
3
  export const schemaPathToFieldPath = (path: ReadonlyArray<PropertyKey>): string => {
4
- if (path.length === 0) return ""
4
+ if (path.length === 0) return "";
5
5
 
6
- let result = String(path[0])
6
+ let result = String(path[0]);
7
7
  for (let i = 1; i < path.length; i++) {
8
- const segment = path[i]
8
+ const segment = path[i];
9
9
  if (typeof segment === "number") {
10
- result += `[${segment}]`
10
+ result += `[${segment}]`;
11
11
  } else {
12
- result += `.${String(segment)}`
12
+ result += `.${String(segment)}`;
13
13
  }
14
14
  }
15
- return result
16
- }
15
+ return result;
16
+ };
17
17
 
18
18
  export const isPathUnderRoot = (path: string, rootPath: string): boolean =>
19
- path === rootPath || path.startsWith(rootPath + ".") || path.startsWith(rootPath + "[")
19
+ path === rootPath || path.startsWith(rootPath + ".") || path.startsWith(rootPath + "[");
20
20
 
21
21
  export const isPathOrParentDirty = (dirtyFields: ReadonlySet<string>, path: string): boolean => {
22
- if (dirtyFields.has(path)) return true
22
+ if (dirtyFields.has(path)) return true;
23
23
 
24
- let parent = path
24
+ let parent = path;
25
25
  while (true) {
26
- const lastDot = parent.lastIndexOf(".")
27
- const lastBracket = parent.lastIndexOf("[")
28
- const splitIndex = Math.max(lastDot, lastBracket)
26
+ const lastDot = parent.lastIndexOf(".");
27
+ const lastBracket = parent.lastIndexOf("[");
28
+ const splitIndex = Math.max(lastDot, lastBracket);
29
29
 
30
- if (splitIndex === -1) break
30
+ if (splitIndex === -1) break;
31
31
 
32
- parent = parent.substring(0, splitIndex)
33
- if (dirtyFields.has(parent)) return true
32
+ parent = parent.substring(0, splitIndex);
33
+ if (dirtyFields.has(parent)) return true;
34
34
  }
35
35
 
36
- return false
37
- }
36
+ return false;
37
+ };
38
38
 
39
39
  export const getNestedValue = (obj: unknown, path: string): unknown => {
40
- if (path === "") return obj
41
- const parts = path.replace(BRACKET_NOTATION_REGEX, ".$1").split(".")
42
- let current: unknown = obj
40
+ if (path === "") return obj;
41
+ const parts = path.replace(BRACKET_NOTATION_REGEX, ".$1").split(".");
42
+ let current: unknown = obj;
43
43
  for (const part of parts) {
44
- if (current == null || typeof current !== "object") return undefined
45
- current = (current as Record<string, unknown>)[part]
44
+ if (current == null || typeof current !== "object") return undefined;
45
+ current = (current as Record<string, unknown>)[part];
46
46
  }
47
- return current
48
- }
47
+ return current;
48
+ };
49
49
 
50
50
  export const setNestedValue = <T>(obj: T, path: string, value: unknown): T => {
51
- if (path === "") return value as T
52
- const parts = path.replace(BRACKET_NOTATION_REGEX, ".$1").split(".")
53
- const result = { ...obj } as Record<string, unknown>
51
+ if (path === "") return value as T;
52
+ const parts = path.replace(BRACKET_NOTATION_REGEX, ".$1").split(".");
53
+ const result = { ...obj } as Record<string, unknown>;
54
54
 
55
- let current = result
55
+ let current = result;
56
56
  for (let i = 0; i < parts.length - 1; i++) {
57
- const part = parts[i]
57
+ const part = parts[i];
58
58
  if (Array.isArray(current[part])) {
59
- current[part] = [...(current[part] as Array<unknown>)]
59
+ current[part] = [...(current[part] as Array<unknown>)];
60
60
  } else {
61
- current[part] = { ...(current[part] as Record<string, unknown>) }
61
+ current[part] = { ...(current[part] as Record<string, unknown>) };
62
62
  }
63
- current = current[part] as Record<string, unknown>
63
+ current = current[part] as Record<string, unknown>;
64
64
  }
65
65
 
66
- current[parts[parts.length - 1]] = value
67
- return result as T
68
- }
66
+ current[parts[parts.length - 1]] = value;
67
+ return result as T;
68
+ };