@goodie-forms/core 1.1.5-alpha → 1.2.0-alpha
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/LICENSE +426 -426
- package/dist/field/FieldPath.d.ts +32 -0
- package/dist/field/FieldPath.d.ts.map +1 -0
- package/dist/field/FieldPathBuilder.d.ts +25 -0
- package/dist/field/FieldPathBuilder.d.ts.map +1 -0
- package/dist/field/Reconcile.d.ts +9 -0
- package/dist/field/Reconcile.d.ts.map +1 -0
- package/dist/form/FormController.d.ts +35 -39
- package/dist/form/FormController.d.ts.map +1 -1
- package/dist/form/FormField.d.ts +12 -10
- package/dist/form/FormField.d.ts.map +1 -1
- package/dist/form/NonullFormField.d.ts +3 -4
- package/dist/form/NonullFormField.d.ts.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +615 -881
- package/dist/index.js.map +1 -1
- package/dist/validation/CustomValidation.d.ts +10 -0
- package/dist/validation/CustomValidation.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/field/FieldPath.ts +292 -0
- package/src/field/FieldPathBuilder.ts +132 -0
- package/src/field/Reconcile.ts +99 -0
- package/src/form/FormController.ts +342 -310
- package/src/form/FormField.ts +200 -202
- package/src/form/NonullFormField.ts +15 -21
- package/src/index.ts +12 -5
- package/src/types/DeepPartial.ts +7 -7
- package/src/types/Mixin.ts +2 -2
- package/src/utils/ensureImmerability.ts +30 -30
- package/src/utils/getId.ts +5 -5
- package/src/utils/removeBy.ts +11 -11
- package/src/{form → validation}/CustomValidation.ts +52 -52
- package/tsconfig.json +8 -7
- package/vite.config.ts +18 -18
- package/dist/form/CustomValidation.d.ts +0 -10
- package/dist/form/CustomValidation.d.ts.map +0 -1
- package/dist/form/Field.d.ts +0 -31
- package/dist/form/Field.d.ts.map +0 -1
- package/src/form/Field.ts +0 -334
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export type CustomValidationIssue<
|
|
6
|
-
path:
|
|
7
|
-
message: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type CustomValidationStrategy<
|
|
11
|
-
data: DeepPartial<
|
|
12
|
-
) =>
|
|
13
|
-
| void
|
|
14
|
-
| CustomValidationIssue<
|
|
15
|
-
| Promise<CustomValidationIssue<
|
|
16
|
-
|
|
17
|
-
export function customValidation<
|
|
18
|
-
strategy: CustomValidationStrategy<
|
|
19
|
-
)
|
|
20
|
-
return {
|
|
21
|
-
"~standard": {
|
|
22
|
-
version: 1 as const,
|
|
23
|
-
|
|
24
|
-
vendor: "goodie-forms/custom" as const,
|
|
25
|
-
|
|
26
|
-
validate: async (input: unknown) => {
|
|
27
|
-
try {
|
|
28
|
-
const customIssues = await strategy(input as DeepPartial<
|
|
29
|
-
|
|
30
|
-
if (customIssues == null) {
|
|
31
|
-
return { value: input as
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
issues: customIssues.map((i) => ({
|
|
36
|
-
path:
|
|
37
|
-
message: i.message,
|
|
38
|
-
})),
|
|
39
|
-
};
|
|
40
|
-
} catch (err) {
|
|
41
|
-
return {
|
|
42
|
-
issues: [
|
|
43
|
-
{
|
|
44
|
-
message: err instanceof Error ? err.message : "Unknown error",
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
}
|
|
1
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { DeepPartial } from "../types/DeepPartial";
|
|
3
|
+
import { FieldPath } from "../field/FieldPath";
|
|
4
|
+
|
|
5
|
+
export type CustomValidationIssue<TOutput extends object> = {
|
|
6
|
+
path: FieldPath.StringPaths<TOutput>;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CustomValidationStrategy<TOutput extends object> = (
|
|
11
|
+
data: DeepPartial<TOutput>,
|
|
12
|
+
) =>
|
|
13
|
+
| void
|
|
14
|
+
| CustomValidationIssue<TOutput>[]
|
|
15
|
+
| Promise<CustomValidationIssue<TOutput>[] | void>;
|
|
16
|
+
|
|
17
|
+
export function customValidation<TOutput extends object>(
|
|
18
|
+
strategy: CustomValidationStrategy<TOutput>,
|
|
19
|
+
) {
|
|
20
|
+
return {
|
|
21
|
+
"~standard": {
|
|
22
|
+
version: 1 as const,
|
|
23
|
+
|
|
24
|
+
vendor: "goodie-forms/custom" as const,
|
|
25
|
+
|
|
26
|
+
validate: async (input: unknown) => {
|
|
27
|
+
try {
|
|
28
|
+
const customIssues = await strategy(input as DeepPartial<TOutput>);
|
|
29
|
+
|
|
30
|
+
if (customIssues == null) {
|
|
31
|
+
return { value: input as TOutput };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
issues: customIssues.map((i) => ({
|
|
36
|
+
path: FieldPath.fromStringPath(i.path as string),
|
|
37
|
+
message: i.message,
|
|
38
|
+
})),
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return {
|
|
42
|
+
issues: [
|
|
43
|
+
{
|
|
44
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
} as StandardSchemaV1<TOutput, TOutput>;
|
|
52
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "dist"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"rootDir": "."
|
|
6
|
+
},
|
|
7
|
+
"include": ["src"]
|
|
8
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { defineConfig } from "vite";
|
|
2
|
-
|
|
3
|
-
import dts from "vite-plugin-dts";
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
plugins: [dts({ entryRoot: "src" })],
|
|
7
|
-
build: {
|
|
8
|
-
lib: {
|
|
9
|
-
entry: "src/index.ts",
|
|
10
|
-
formats: ["es"],
|
|
11
|
-
fileName: "index",
|
|
12
|
-
},
|
|
13
|
-
rollupOptions: {
|
|
14
|
-
external: [],
|
|
15
|
-
},
|
|
16
|
-
sourcemap: true,
|
|
17
|
-
},
|
|
18
|
-
});
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
|
|
3
|
+
import dts from "vite-plugin-dts";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [dts({ entryRoot: "src" })],
|
|
7
|
+
build: {
|
|
8
|
+
lib: {
|
|
9
|
+
entry: "src/index.ts",
|
|
10
|
+
formats: ["es"],
|
|
11
|
+
fileName: "index",
|
|
12
|
+
},
|
|
13
|
+
rollupOptions: {
|
|
14
|
+
external: [],
|
|
15
|
+
},
|
|
16
|
+
sourcemap: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
import { Field } from '../form/Field';
|
|
3
|
-
import { DeepPartial } from '../types/DeepPartial';
|
|
4
|
-
export type CustomValidationIssue<TShape extends object> = {
|
|
5
|
-
path: Field.Paths<TShape>;
|
|
6
|
-
message: string;
|
|
7
|
-
};
|
|
8
|
-
export type CustomValidationStrategy<TShape extends object> = (data: DeepPartial<TShape>) => void | CustomValidationIssue<TShape>[] | Promise<CustomValidationIssue<TShape>[] | void>;
|
|
9
|
-
export declare function customValidation<TShape extends object>(strategy: CustomValidationStrategy<TShape>): StandardSchemaV1<TShape, TShape>;
|
|
10
|
-
//# sourceMappingURL=CustomValidation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CustomValidation.d.ts","sourceRoot":"","sources":["../../src/form/CustomValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,MAAM,qBAAqB,CAAC,MAAM,SAAS,MAAM,IAAI;IACzD,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,wBAAwB,CAAC,MAAM,SAAS,MAAM,IAAI,CAC5D,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,KAEvB,IAAI,GACJ,qBAAqB,CAAC,MAAM,CAAC,EAAE,GAC/B,OAAO,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;AAEpD,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,EACpD,QAAQ,EAAE,wBAAwB,CAAC,MAAM,CAAC,GACzC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAiClC"}
|
package/dist/form/Field.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
export declare namespace Field {
|
|
3
|
-
type Unfoldable<T> = T & {
|
|
4
|
-
_____foldMark?: never;
|
|
5
|
-
} & {};
|
|
6
|
-
export type Paths<TShape extends object> = PathsImpl<CanonicalPaths<TShape>>;
|
|
7
|
-
type PathsImpl<T> = T extends string ? T extends `${string}[*]${string}` ? ReplaceAll<T, "[*]", "[0]"> | Unfoldable<ReplaceAll<T, "[*]", `[${number}]`>> : T : never;
|
|
8
|
-
type CanonicalPaths<TShape extends object> = {
|
|
9
|
-
[K in keyof TShape & string]: NonNullable<TShape[K]> extends (...args: any[]) => any ? never : NonNullable<TShape[K]> extends (infer U)[] ? U extends object ? K | `${K}[*]` | `${K}[*].${CanonicalPaths<NonNullable<U>>}` : K | `${K}[*]` : NonNullable<TShape[K]> extends object ? K | `${K}.${CanonicalPaths<NonNullable<TShape[K]>>}` : K;
|
|
10
|
-
}[keyof TShape & string];
|
|
11
|
-
type ReplaceAll<TString extends string, TMatch extends string, TReplace extends string | number> = TString extends `${infer A}${TMatch}${infer B}` ? `${A}${TReplace}${ReplaceAll<B, TMatch, TReplace>}` : TString;
|
|
12
|
-
export type GetValue<TShape, TPath extends string> = GetValueImpl<TShape, NormalizePath<TPath>>;
|
|
13
|
-
type GetValueImpl<TShape, TPath extends string> = TPath extends `${infer Head}.${infer Tail}` ? GetValueImpl<ResolveFragment<NonNullable<TShape>, Head>, Tail> : ResolveFragment<NonNullable<TShape>, TPath>;
|
|
14
|
-
type NormalizePath<TPath extends string> = TPath extends `${infer A}[${infer B}]${infer Rest}` ? NormalizePath<`${A}.${B}${Rest}`> : TPath extends `.${infer R}` ? NormalizePath<R> : TPath;
|
|
15
|
-
type ResolveFragment<TShape, TFragment extends string> = TFragment extends `${number}` ? TShape extends readonly (infer U)[] ? U : never : TFragment extends keyof TShape ? TShape[TFragment] : never;
|
|
16
|
-
export function deepEqual(a: any, b: any, customComparator?: (a: any, b: any) => boolean | undefined): boolean;
|
|
17
|
-
export function diff<T>(prev: readonly T[], next: readonly T[], equals: (a: T, b: T) => boolean, filter?: (a: T) => boolean): {
|
|
18
|
-
added: T[];
|
|
19
|
-
removed: T[];
|
|
20
|
-
unchanged: T[];
|
|
21
|
-
};
|
|
22
|
-
export function parsePathFragments(path: string): (string | number)[];
|
|
23
|
-
export function parsePath(fragments: readonly (PropertyKey | StandardSchemaV1.PathSegment)[]): string;
|
|
24
|
-
export function isDescendant(parentPath: string, childPath: string): boolean;
|
|
25
|
-
export function getValue<TShape extends object, TPath extends Field.Paths<TShape>>(data: TShape, path: TPath): Field.GetValue<TShape, TPath> | undefined;
|
|
26
|
-
export function setValue<TShape extends object, TPath extends Field.Paths<TShape>>(data: TShape, key: TPath, value: Field.GetValue<TShape, TPath>): void;
|
|
27
|
-
export function modifyValue<TShape extends object, TPath extends Field.Paths<TShape>>(data: TShape, path: TPath, modifier: (currentValue: Field.GetValue<TShape, TPath>) => Field.GetValue<TShape, TPath> | void): void;
|
|
28
|
-
export function deleteValue<TShape extends object, TPath extends Field.Paths<TShape>>(data: TShape, path: TPath): void;
|
|
29
|
-
export {};
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=Field.d.ts.map
|
package/dist/form/Field.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/form/Field.ts"],"names":[],"mappings":"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"}
|
package/src/form/Field.ts
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
|
|
3
|
-
export namespace Field {
|
|
4
|
-
type Unfoldable<T> = T & { _____foldMark?: never } & {};
|
|
5
|
-
|
|
6
|
-
export type Paths<TShape extends object> = PathsImpl<CanonicalPaths<TShape>>;
|
|
7
|
-
|
|
8
|
-
type PathsImpl<T> = T extends string
|
|
9
|
-
? T extends `${string}[*]${string}`
|
|
10
|
-
?
|
|
11
|
-
| ReplaceAll<T, "[*]", "[0]">
|
|
12
|
-
| Unfoldable<ReplaceAll<T, "[*]", `[${number}]`>>
|
|
13
|
-
: T
|
|
14
|
-
: never;
|
|
15
|
-
|
|
16
|
-
type CanonicalPaths<TShape extends object> = {
|
|
17
|
-
[K in keyof TShape & string]: NonNullable<TShape[K]> extends (
|
|
18
|
-
...args: any[]
|
|
19
|
-
) => any
|
|
20
|
-
? never
|
|
21
|
-
: NonNullable<TShape[K]> extends (infer U)[]
|
|
22
|
-
? U extends object
|
|
23
|
-
? K | `${K}[*]` | `${K}[*].${CanonicalPaths<NonNullable<U>>}`
|
|
24
|
-
: K | `${K}[*]`
|
|
25
|
-
: NonNullable<TShape[K]> extends object
|
|
26
|
-
? K | `${K}.${CanonicalPaths<NonNullable<TShape[K]>>}`
|
|
27
|
-
: K;
|
|
28
|
-
}[keyof TShape & string];
|
|
29
|
-
|
|
30
|
-
type ReplaceAll<
|
|
31
|
-
TString extends string,
|
|
32
|
-
TMatch extends string,
|
|
33
|
-
TReplace extends string | number
|
|
34
|
-
> = TString extends `${infer A}${TMatch}${infer B}`
|
|
35
|
-
? `${A}${TReplace}${ReplaceAll<B, TMatch, TReplace>}`
|
|
36
|
-
: TString;
|
|
37
|
-
|
|
38
|
-
export type GetValue<TShape, TPath extends string> = GetValueImpl<
|
|
39
|
-
TShape,
|
|
40
|
-
NormalizePath<TPath>
|
|
41
|
-
>;
|
|
42
|
-
|
|
43
|
-
type GetValueImpl<
|
|
44
|
-
TShape,
|
|
45
|
-
TPath extends string
|
|
46
|
-
> = TPath extends `${infer Head}.${infer Tail}`
|
|
47
|
-
? GetValueImpl<ResolveFragment<NonNullable<TShape>, Head>, Tail>
|
|
48
|
-
: ResolveFragment<NonNullable<TShape>, TPath>;
|
|
49
|
-
|
|
50
|
-
type NormalizePath<TPath extends string> =
|
|
51
|
-
TPath extends `${infer A}[${infer B}]${infer Rest}`
|
|
52
|
-
? NormalizePath<`${A}.${B}${Rest}`>
|
|
53
|
-
: TPath extends `.${infer R}`
|
|
54
|
-
? NormalizePath<R>
|
|
55
|
-
: TPath;
|
|
56
|
-
|
|
57
|
-
type ResolveFragment<
|
|
58
|
-
TShape,
|
|
59
|
-
TFragment extends string
|
|
60
|
-
> = TFragment extends `${number}`
|
|
61
|
-
? TShape extends readonly (infer U)[]
|
|
62
|
-
? U
|
|
63
|
-
: never
|
|
64
|
-
: TFragment extends keyof TShape
|
|
65
|
-
? TShape[TFragment]
|
|
66
|
-
: never;
|
|
67
|
-
|
|
68
|
-
export function deepEqual(
|
|
69
|
-
a: any,
|
|
70
|
-
b: any,
|
|
71
|
-
customComparator?: (a: any, b: any) => boolean | undefined
|
|
72
|
-
) {
|
|
73
|
-
if (a === b) return true;
|
|
74
|
-
|
|
75
|
-
if (a === null || b === null) return false;
|
|
76
|
-
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
77
|
-
|
|
78
|
-
// Arrays
|
|
79
|
-
if (Array.isArray(a)) {
|
|
80
|
-
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
81
|
-
for (let i = 0; i < a.length; i++) {
|
|
82
|
-
if (!deepEqual(a[i], b[i])) return false;
|
|
83
|
-
}
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Allow custom comparison
|
|
88
|
-
if (customComparator != null) {
|
|
89
|
-
const result = customComparator(a, b);
|
|
90
|
-
if (result !== undefined) return result;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Dates
|
|
94
|
-
if (a instanceof Date && b instanceof Date) {
|
|
95
|
-
return a.getTime() === b.getTime();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// RegExp
|
|
99
|
-
if (a instanceof RegExp && b instanceof RegExp) {
|
|
100
|
-
return a.source === b.source && a.flags === b.flags;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Map
|
|
104
|
-
if (a instanceof Map && b instanceof Map) {
|
|
105
|
-
if (a.size !== b.size) return false;
|
|
106
|
-
for (const [key, val] of a) {
|
|
107
|
-
if (!b.has(key) || !deepEqual(val, b.get(key))) return false;
|
|
108
|
-
}
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Set
|
|
113
|
-
if (a instanceof Set && b instanceof Set) {
|
|
114
|
-
if (a.size !== b.size) return false;
|
|
115
|
-
for (const val of a) {
|
|
116
|
-
if (!b.has(val)) return false;
|
|
117
|
-
}
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Plain / class objects
|
|
122
|
-
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const keysA = Object.keys(a);
|
|
127
|
-
const keysB = Object.keys(b);
|
|
128
|
-
|
|
129
|
-
if (keysA.length !== keysB.length) return false;
|
|
130
|
-
|
|
131
|
-
for (const key of keysA) {
|
|
132
|
-
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
133
|
-
if (!deepEqual(a[key], b[key])) return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function diff<T>(
|
|
140
|
-
prev: readonly T[],
|
|
141
|
-
next: readonly T[],
|
|
142
|
-
equals: (a: T, b: T) => boolean,
|
|
143
|
-
filter?: (a: T) => boolean
|
|
144
|
-
) {
|
|
145
|
-
const added: T[] = [];
|
|
146
|
-
const removed: T[] = [];
|
|
147
|
-
const unchanged: T[] = [];
|
|
148
|
-
|
|
149
|
-
for (const n of next) {
|
|
150
|
-
if (prev.some((p) => equals(p, n))) {
|
|
151
|
-
if (filter?.(n) ?? true) unchanged.push(n);
|
|
152
|
-
} else {
|
|
153
|
-
if (filter?.(n) ?? true) added.push(n);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
for (const p of prev) {
|
|
158
|
-
if (!next.some((n) => equals(p, n))) {
|
|
159
|
-
if (filter?.(p) ?? true) removed.push(p);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { added, removed, unchanged };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function parsePathFragments(path: string) {
|
|
167
|
-
const result: Array<string | number> = [];
|
|
168
|
-
|
|
169
|
-
let i = 0;
|
|
170
|
-
|
|
171
|
-
while (i < path.length) {
|
|
172
|
-
const char = path[i];
|
|
173
|
-
|
|
174
|
-
// dot separator
|
|
175
|
-
if (char === ".") {
|
|
176
|
-
i++;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// bracket index: [123]
|
|
181
|
-
if (char === "[") {
|
|
182
|
-
i++; // skip '['
|
|
183
|
-
let num = "";
|
|
184
|
-
|
|
185
|
-
while (i < path.length && path[i] !== "]") {
|
|
186
|
-
num += path[i];
|
|
187
|
-
i++;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
i++; // skip ']'
|
|
191
|
-
|
|
192
|
-
if (!num || !/^\d+$/.test(num)) {
|
|
193
|
-
throw new Error(`Invalid array index in path: ${path}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
result.push(Number(num));
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// identifier
|
|
201
|
-
let key = "";
|
|
202
|
-
while (i < path.length && /[^\.\[]/.test(path[i])) {
|
|
203
|
-
key += path[i];
|
|
204
|
-
i++;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (key) {
|
|
208
|
-
result.push(key);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function parsePath(
|
|
216
|
-
fragments: readonly (PropertyKey | StandardSchemaV1.PathSegment)[]
|
|
217
|
-
) {
|
|
218
|
-
let result = "";
|
|
219
|
-
|
|
220
|
-
for (const fragment of fragments) {
|
|
221
|
-
const pathFragment =
|
|
222
|
-
typeof fragment === "object" && "key" in fragment
|
|
223
|
-
? fragment.key
|
|
224
|
-
: fragment;
|
|
225
|
-
|
|
226
|
-
if (typeof pathFragment === "number") {
|
|
227
|
-
result += `[${pathFragment}]`;
|
|
228
|
-
} else {
|
|
229
|
-
if (result.length > 0) {
|
|
230
|
-
result += ".";
|
|
231
|
-
}
|
|
232
|
-
result += pathFragment.toString();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return result;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function isDescendant(parentPath: string, childPath: string) {
|
|
240
|
-
const parentFrags = parsePathFragments(parentPath);
|
|
241
|
-
const childFrags = parsePathFragments(childPath);
|
|
242
|
-
|
|
243
|
-
if (parentFrags.length >= childFrags.length) return false;
|
|
244
|
-
|
|
245
|
-
for (let i = 0; i < parentFrags.length; i++) {
|
|
246
|
-
if (parentFrags[i] !== childFrags[i]) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export function getValue<
|
|
255
|
-
TShape extends object,
|
|
256
|
-
TPath extends Field.Paths<TShape>
|
|
257
|
-
>(data: TShape, path: TPath): Field.GetValue<TShape, TPath> | undefined {
|
|
258
|
-
if (data == null) return undefined;
|
|
259
|
-
|
|
260
|
-
const pathFragments = parsePathFragments(path);
|
|
261
|
-
|
|
262
|
-
let current: any = data;
|
|
263
|
-
|
|
264
|
-
for (const pathFragment of pathFragments) {
|
|
265
|
-
if (current == null) return undefined;
|
|
266
|
-
current = current[pathFragment];
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return current;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function setValue<
|
|
273
|
-
TShape extends object,
|
|
274
|
-
TPath extends Field.Paths<TShape>
|
|
275
|
-
>(data: TShape, key: TPath, value: Field.GetValue<TShape, TPath>) {
|
|
276
|
-
return modifyValue(data, key, () => value);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function modifyValue<
|
|
280
|
-
TShape extends object,
|
|
281
|
-
TPath extends Field.Paths<TShape>
|
|
282
|
-
>(
|
|
283
|
-
data: TShape,
|
|
284
|
-
path: TPath,
|
|
285
|
-
modifier: (
|
|
286
|
-
currentValue: Field.GetValue<TShape, TPath>
|
|
287
|
-
) => Field.GetValue<TShape, TPath> | void
|
|
288
|
-
) {
|
|
289
|
-
const pathFragments = parsePathFragments(path);
|
|
290
|
-
|
|
291
|
-
let current: any = data;
|
|
292
|
-
|
|
293
|
-
for (let i = 0; i < pathFragments.length - 1; i++) {
|
|
294
|
-
const pathFragment = pathFragments[i];
|
|
295
|
-
const nextFragment = pathFragments[i + 1];
|
|
296
|
-
|
|
297
|
-
if (current[pathFragment] == null) {
|
|
298
|
-
current[pathFragment] = typeof nextFragment === "number" ? [] : {};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
current = current[pathFragment];
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const lastFragment = pathFragments[pathFragments.length - 1];
|
|
305
|
-
|
|
306
|
-
const oldValue = current[lastFragment];
|
|
307
|
-
const newValue = modifier(oldValue);
|
|
308
|
-
|
|
309
|
-
if (newValue !== undefined) {
|
|
310
|
-
current[lastFragment] = newValue;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
export function deleteValue<
|
|
315
|
-
TShape extends object,
|
|
316
|
-
TPath extends Field.Paths<TShape>
|
|
317
|
-
>(data: TShape, path: TPath) {
|
|
318
|
-
const pathFragments = parsePathFragments(path);
|
|
319
|
-
|
|
320
|
-
let current: any = data;
|
|
321
|
-
|
|
322
|
-
for (let i = 0; i < pathFragments.length - 1; i++) {
|
|
323
|
-
const pathFragment = pathFragments[i];
|
|
324
|
-
|
|
325
|
-
if (current[pathFragment] == null) return;
|
|
326
|
-
|
|
327
|
-
current = current[pathFragment];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const lastFragment = pathFragments[pathFragments.length - 1];
|
|
331
|
-
|
|
332
|
-
delete current[lastFragment];
|
|
333
|
-
}
|
|
334
|
-
}
|