@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/dist/cjs/Field.js.map +1 -1
- package/dist/cjs/FormAtoms.js +2 -0
- package/dist/cjs/FormAtoms.js.map +1 -1
- package/dist/cjs/FormBuilder.js.map +1 -1
- package/dist/cjs/Mode.js.map +1 -1
- package/dist/cjs/Path.js.map +1 -1
- package/dist/cjs/Validation.js.map +1 -1
- package/dist/cjs/internal/dirty.js.map +1 -1
- package/dist/cjs/internal/weak-registry.js.map +1 -1
- package/dist/dts/Field.d.ts.map +1 -1
- package/dist/dts/FormAtoms.d.ts +1 -0
- package/dist/dts/FormAtoms.d.ts.map +1 -1
- package/dist/dts/FormBuilder.d.ts.map +1 -1
- package/dist/dts/Mode.d.ts.map +1 -1
- package/dist/dts/Path.d.ts.map +1 -1
- package/dist/dts/Validation.d.ts.map +1 -1
- package/dist/dts/internal/dirty.d.ts.map +1 -1
- package/dist/dts/internal/weak-registry.d.ts.map +1 -1
- package/dist/esm/FormAtoms.js +2 -0
- package/dist/esm/FormAtoms.js.map +1 -1
- package/package.json +1 -1
- package/src/Field.ts +40 -40
- package/src/FormAtoms.ts +5 -0
- package/src/FormBuilder.ts +70 -70
- package/src/Mode.ts +14 -14
- package/src/Path.ts +37 -37
- package/src/Validation.ts +54 -54
- package/src/internal/dirty.ts +40 -40
- package/src/internal/weak-registry.ts +17 -17
package/src/Validation.ts
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import * as Option from "effect/Option"
|
|
2
|
-
import * as ParseResult from "effect/ParseResult"
|
|
3
|
-
import type * as AST from "effect/SchemaAST"
|
|
4
|
-
import { schemaPathToFieldPath } from "./Path.js"
|
|
1
|
+
import * as Option from "effect/Option";
|
|
2
|
+
import * as ParseResult from "effect/ParseResult";
|
|
3
|
+
import type * as AST from "effect/SchemaAST";
|
|
4
|
+
import { schemaPathToFieldPath } from "./Path.js";
|
|
5
5
|
|
|
6
|
-
export type ErrorSource = "field" | "refinement"
|
|
6
|
+
export type ErrorSource = "field" | "refinement";
|
|
7
7
|
|
|
8
8
|
export interface ErrorEntry {
|
|
9
|
-
readonly message: string
|
|
10
|
-
readonly source: ErrorSource
|
|
9
|
+
readonly message: string;
|
|
10
|
+
readonly source: ErrorSource;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const getBaseAST = (ast: AST.AST): AST.AST => {
|
|
14
14
|
switch (ast._tag) {
|
|
15
15
|
case "Refinement":
|
|
16
16
|
case "Transformation":
|
|
17
|
-
return getBaseAST(ast.from)
|
|
17
|
+
return getBaseAST(ast.from);
|
|
18
18
|
default:
|
|
19
|
-
return ast
|
|
19
|
+
return ast;
|
|
20
20
|
}
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
const isCompositeType = (ast: AST.AST): boolean => {
|
|
24
|
-
const base = getBaseAST(ast)
|
|
24
|
+
const base = getBaseAST(ast);
|
|
25
25
|
switch (base._tag) {
|
|
26
26
|
case "TypeLiteral": // Schema.Struct
|
|
27
27
|
case "TupleType": // Schema.Tuple
|
|
28
28
|
case "Declaration": // Schema.Class, Schema.TaggedClass
|
|
29
29
|
case "Union": // Schema.Union
|
|
30
30
|
case "Suspend": // Recursive schemas
|
|
31
|
-
return true
|
|
31
|
+
return true;
|
|
32
32
|
default:
|
|
33
|
-
return false
|
|
33
|
+
return false;
|
|
34
34
|
}
|
|
35
|
-
}
|
|
35
|
+
};
|
|
36
36
|
|
|
37
37
|
export const extractFirstError = (error: ParseResult.ParseError): Option.Option<string> => {
|
|
38
|
-
const issues = ParseResult.ArrayFormatter.formatErrorSync(error)
|
|
38
|
+
const issues = ParseResult.ArrayFormatter.formatErrorSync(error);
|
|
39
39
|
if (issues.length === 0) {
|
|
40
|
-
return Option.none()
|
|
40
|
+
return Option.none();
|
|
41
41
|
}
|
|
42
|
-
return Option.some(issues[0].message)
|
|
43
|
-
}
|
|
42
|
+
return Option.some(issues[0].message);
|
|
43
|
+
};
|
|
44
44
|
|
|
45
45
|
export const routeErrors = (error: ParseResult.ParseError): Map<string, string> => {
|
|
46
|
-
const result = new Map<string, string>()
|
|
47
|
-
const issues = ParseResult.ArrayFormatter.formatErrorSync(error)
|
|
46
|
+
const result = new Map<string, string>();
|
|
47
|
+
const issues = ParseResult.ArrayFormatter.formatErrorSync(error);
|
|
48
48
|
|
|
49
49
|
for (const issue of issues) {
|
|
50
|
-
const fieldPath = schemaPathToFieldPath(issue.path)
|
|
50
|
+
const fieldPath = schemaPathToFieldPath(issue.path);
|
|
51
51
|
if (fieldPath && !result.has(fieldPath)) {
|
|
52
|
-
result.set(fieldPath, issue.message)
|
|
52
|
+
result.set(fieldPath, issue.message);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
return result
|
|
57
|
-
}
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
58
|
|
|
59
59
|
const determineErrorSources = (error: ParseResult.ParseError): Map<string, ErrorSource> => {
|
|
60
|
-
const sources = new Map<string, ErrorSource>()
|
|
60
|
+
const sources = new Map<string, ErrorSource>();
|
|
61
61
|
|
|
62
62
|
const walk = (issue: ParseResult.ParseIssue, path: ReadonlyArray<PropertyKey>, source: ErrorSource): void => {
|
|
63
63
|
switch (issue._tag) {
|
|
64
64
|
case "Refinement":
|
|
65
65
|
if (issue.kind === "Predicate" && isCompositeType(issue.ast.from) && path.length === 0) {
|
|
66
|
-
walk(issue.issue, path, "refinement")
|
|
66
|
+
walk(issue.issue, path, "refinement");
|
|
67
67
|
} else {
|
|
68
|
-
walk(issue.issue, path, source)
|
|
68
|
+
walk(issue.issue, path, source);
|
|
69
69
|
}
|
|
70
|
-
break
|
|
70
|
+
break;
|
|
71
71
|
case "Pointer": {
|
|
72
|
-
const pointerPath = Array.isArray(issue.path) ? issue.path : [issue.path]
|
|
73
|
-
walk(issue.issue, [...path, ...pointerPath], source)
|
|
74
|
-
break
|
|
72
|
+
const pointerPath = Array.isArray(issue.path) ? issue.path : [issue.path];
|
|
73
|
+
walk(issue.issue, [...path, ...pointerPath], source);
|
|
74
|
+
break;
|
|
75
75
|
}
|
|
76
76
|
case "Composite": {
|
|
77
|
-
const issues = Array.isArray(issue.issues) ? issue.issues : [issue.issues]
|
|
77
|
+
const issues = Array.isArray(issue.issues) ? issue.issues : [issue.issues];
|
|
78
78
|
for (const sub of issues) {
|
|
79
|
-
walk(sub, path, source)
|
|
79
|
+
walk(sub, path, source);
|
|
80
80
|
}
|
|
81
|
-
break
|
|
81
|
+
break;
|
|
82
82
|
}
|
|
83
83
|
case "Type":
|
|
84
84
|
case "Missing":
|
|
85
85
|
case "Unexpected":
|
|
86
86
|
case "Forbidden": {
|
|
87
|
-
const fieldPath = schemaPathToFieldPath(path)
|
|
88
|
-
const key = fieldPath ?? ""
|
|
87
|
+
const fieldPath = schemaPathToFieldPath(path);
|
|
88
|
+
const key = fieldPath ?? "";
|
|
89
89
|
if (!sources.has(key)) {
|
|
90
|
-
sources.set(key, source)
|
|
90
|
+
sources.set(key, source);
|
|
91
91
|
}
|
|
92
|
-
break
|
|
92
|
+
break;
|
|
93
93
|
}
|
|
94
94
|
case "Transformation":
|
|
95
95
|
if (
|
|
@@ -98,31 +98,31 @@ const determineErrorSources = (error: ParseResult.ParseError): Map<string, Error
|
|
|
98
98
|
isCompositeType(issue.ast.from) &&
|
|
99
99
|
path.length === 0
|
|
100
100
|
) {
|
|
101
|
-
walk(issue.issue, path, "refinement")
|
|
101
|
+
walk(issue.issue, path, "refinement");
|
|
102
102
|
} else {
|
|
103
|
-
walk(issue.issue, path, source)
|
|
103
|
+
walk(issue.issue, path, source);
|
|
104
104
|
}
|
|
105
|
-
break
|
|
105
|
+
break;
|
|
106
106
|
}
|
|
107
|
-
}
|
|
107
|
+
};
|
|
108
108
|
|
|
109
|
-
walk(error.issue, [], "field")
|
|
110
|
-
return sources
|
|
111
|
-
}
|
|
109
|
+
walk(error.issue, [], "field");
|
|
110
|
+
return sources;
|
|
111
|
+
};
|
|
112
112
|
|
|
113
113
|
export const routeErrorsWithSource = (error: ParseResult.ParseError): Map<string, ErrorEntry> => {
|
|
114
|
-
const result = new Map<string, ErrorEntry>()
|
|
115
|
-
const formattedIssues = ParseResult.ArrayFormatter.formatErrorSync(error)
|
|
116
|
-
const sources = determineErrorSources(error)
|
|
114
|
+
const result = new Map<string, ErrorEntry>();
|
|
115
|
+
const formattedIssues = ParseResult.ArrayFormatter.formatErrorSync(error);
|
|
116
|
+
const sources = determineErrorSources(error);
|
|
117
117
|
|
|
118
118
|
for (const issue of formattedIssues) {
|
|
119
|
-
const fieldPath = schemaPathToFieldPath(issue.path)
|
|
120
|
-
const key = fieldPath ?? ""
|
|
119
|
+
const fieldPath = schemaPathToFieldPath(issue.path);
|
|
120
|
+
const key = fieldPath ?? "";
|
|
121
121
|
if (!result.has(key)) {
|
|
122
|
-
const source = sources.get(key) ?? "field"
|
|
123
|
-
result.set(key, { message: issue.message, source })
|
|
122
|
+
const source = sources.get(key) ?? "field";
|
|
123
|
+
result.set(key, { message: issue.message, source });
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
return result
|
|
128
|
-
}
|
|
127
|
+
return result;
|
|
128
|
+
};
|
package/src/internal/dirty.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as Equal from "effect/Equal"
|
|
2
|
-
import * as Utils from "effect/Utils"
|
|
3
|
-
import { getNestedValue, isPathUnderRoot } from "../Path.js"
|
|
1
|
+
import * as Equal from "effect/Equal";
|
|
2
|
+
import * as Utils from "effect/Utils";
|
|
3
|
+
import { getNestedValue, isPathUnderRoot } from "../Path.js";
|
|
4
4
|
|
|
5
5
|
export const recalculateDirtyFieldsForArray = (
|
|
6
6
|
dirtyFields: ReadonlySet<string>,
|
|
@@ -8,38 +8,38 @@ export const recalculateDirtyFieldsForArray = (
|
|
|
8
8
|
arrayPath: string,
|
|
9
9
|
newItems: ReadonlyArray<unknown>,
|
|
10
10
|
): ReadonlySet<string> => {
|
|
11
|
-
const initialItems = (getNestedValue(initialValues, arrayPath) ?? []) as ReadonlyArray<unknown
|
|
11
|
+
const initialItems = (getNestedValue(initialValues, arrayPath) ?? []) as ReadonlyArray<unknown>;
|
|
12
12
|
|
|
13
13
|
if (newItems === initialItems) {
|
|
14
|
-
return dirtyFields
|
|
14
|
+
return dirtyFields;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const nextDirty = new Set(
|
|
18
18
|
Array.from(dirtyFields).filter((path) => !isPathUnderRoot(path, arrayPath)),
|
|
19
|
-
)
|
|
19
|
+
);
|
|
20
20
|
|
|
21
|
-
const loopLength = Math.max(newItems.length, initialItems.length)
|
|
21
|
+
const loopLength = Math.max(newItems.length, initialItems.length);
|
|
22
22
|
for (let i = 0; i < loopLength; i++) {
|
|
23
|
-
const itemPath = `${arrayPath}[${i}]
|
|
24
|
-
const newItem = newItems[i]
|
|
25
|
-
const initialItem = initialItems[i]
|
|
23
|
+
const itemPath = `${arrayPath}[${i}]`;
|
|
24
|
+
const newItem = newItems[i];
|
|
25
|
+
const initialItem = initialItems[i];
|
|
26
26
|
|
|
27
|
-
if (newItem === initialItem) continue
|
|
27
|
+
if (newItem === initialItem) continue;
|
|
28
28
|
|
|
29
|
-
const isEqual = Utils.structuralRegion(() => Equal.equals(newItem, initialItem))
|
|
29
|
+
const isEqual = Utils.structuralRegion(() => Equal.equals(newItem, initialItem));
|
|
30
30
|
if (!isEqual) {
|
|
31
|
-
nextDirty.add(itemPath)
|
|
31
|
+
nextDirty.add(itemPath);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
if (newItems.length !== initialItems.length) {
|
|
36
|
-
nextDirty.add(arrayPath)
|
|
36
|
+
nextDirty.add(arrayPath);
|
|
37
37
|
} else {
|
|
38
|
-
nextDirty.delete(arrayPath)
|
|
38
|
+
nextDirty.delete(arrayPath);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
return nextDirty
|
|
42
|
-
}
|
|
41
|
+
return nextDirty;
|
|
42
|
+
};
|
|
43
43
|
|
|
44
44
|
export const recalculateDirtySubtree = (
|
|
45
45
|
currentDirty: ReadonlySet<string>,
|
|
@@ -47,61 +47,61 @@ export const recalculateDirtySubtree = (
|
|
|
47
47
|
allValues: unknown,
|
|
48
48
|
rootPath: string = "",
|
|
49
49
|
): ReadonlySet<string> => {
|
|
50
|
-
const targetValue = rootPath ? getNestedValue(allValues, rootPath) : allValues
|
|
51
|
-
const targetInitial = rootPath ? getNestedValue(allInitial, rootPath) : allInitial
|
|
50
|
+
const targetValue = rootPath ? getNestedValue(allValues, rootPath) : allValues;
|
|
51
|
+
const targetInitial = rootPath ? getNestedValue(allInitial, rootPath) : allInitial;
|
|
52
52
|
|
|
53
53
|
if (targetValue === targetInitial) {
|
|
54
54
|
if (rootPath === "") {
|
|
55
|
-
return new Set()
|
|
55
|
+
return new Set();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
let changed = false
|
|
59
|
-
const nextDirty = new Set(currentDirty)
|
|
58
|
+
let changed = false;
|
|
59
|
+
const nextDirty = new Set(currentDirty);
|
|
60
60
|
for (const path of currentDirty) {
|
|
61
61
|
if (isPathUnderRoot(path, rootPath)) {
|
|
62
|
-
nextDirty.delete(path)
|
|
63
|
-
changed = true
|
|
62
|
+
nextDirty.delete(path);
|
|
63
|
+
changed = true;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
return changed ? nextDirty : currentDirty
|
|
66
|
+
return changed ? nextDirty : currentDirty;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const nextDirty = new Set(currentDirty)
|
|
69
|
+
const nextDirty = new Set(currentDirty);
|
|
70
70
|
|
|
71
71
|
if (rootPath === "") {
|
|
72
|
-
nextDirty.clear()
|
|
72
|
+
nextDirty.clear();
|
|
73
73
|
} else {
|
|
74
74
|
for (const path of nextDirty) {
|
|
75
75
|
if (isPathUnderRoot(path, rootPath)) {
|
|
76
|
-
nextDirty.delete(path)
|
|
76
|
+
nextDirty.delete(path);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const recurse = (current: unknown, initial: unknown, path: string): void => {
|
|
82
|
-
if (current === initial) return
|
|
82
|
+
if (current === initial) return;
|
|
83
83
|
|
|
84
84
|
if (Array.isArray(current)) {
|
|
85
|
-
const initialArr = (initial ?? []) as ReadonlyArray<unknown
|
|
85
|
+
const initialArr = (initial ?? []) as ReadonlyArray<unknown>;
|
|
86
86
|
for (let i = 0; i < Math.max(current.length, initialArr.length); i++) {
|
|
87
|
-
recurse(current[i], initialArr[i], path ? `${path}[${i}]` : `[${i}]`)
|
|
87
|
+
recurse(current[i], initialArr[i], path ? `${path}[${i}]` : `[${i}]`);
|
|
88
88
|
}
|
|
89
89
|
} else if (current !== null && typeof current === "object") {
|
|
90
|
-
const initialObj = (initial ?? {}) as Record<string, unknown
|
|
90
|
+
const initialObj = (initial ?? {}) as Record<string, unknown>;
|
|
91
91
|
for (const key in current as object) {
|
|
92
|
-
recurse((current as Record<string, unknown>)[key], initialObj[key], path ? `${path}.${key}` : key)
|
|
92
|
+
recurse((current as Record<string, unknown>)[key], initialObj[key], path ? `${path}.${key}` : key);
|
|
93
93
|
}
|
|
94
94
|
for (const key in initialObj) {
|
|
95
95
|
if (!(key in (current as object))) {
|
|
96
|
-
recurse(undefined, initialObj[key], path ? `${path}.${key}` : key)
|
|
96
|
+
recurse(undefined, initialObj[key], path ? `${path}.${key}` : key);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
} else {
|
|
100
|
-
const isEqual = Utils.structuralRegion(() => Equal.equals(current, initial))
|
|
101
|
-
if (!isEqual && path) nextDirty.add(path)
|
|
100
|
+
const isEqual = Utils.structuralRegion(() => Equal.equals(current, initial));
|
|
101
|
+
if (!isEqual && path) nextDirty.add(path);
|
|
102
102
|
}
|
|
103
|
-
}
|
|
103
|
+
};
|
|
104
104
|
|
|
105
|
-
recurse(targetValue, targetInitial, rootPath)
|
|
106
|
-
return nextDirty
|
|
107
|
-
}
|
|
105
|
+
recurse(targetValue, targetInitial, rootPath);
|
|
106
|
+
return nextDirty;
|
|
107
|
+
};
|
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
export interface WeakRegistry<V extends object> {
|
|
2
|
-
readonly get: (key: string) => V | undefined
|
|
3
|
-
readonly set: (key: string, value: V) => void
|
|
4
|
-
readonly delete: (key: string) => boolean
|
|
5
|
-
readonly clear: () => void
|
|
6
|
-
readonly values: () => IterableIterator<V
|
|
2
|
+
readonly get: (key: string) => V | undefined;
|
|
3
|
+
readonly set: (key: string, value: V) => void;
|
|
4
|
+
readonly delete: (key: string) => boolean;
|
|
5
|
+
readonly clear: () => void;
|
|
6
|
+
readonly values: () => IterableIterator<V>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const createWeakRegistry = <V extends object>(): WeakRegistry<V> => {
|
|
10
10
|
if (typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined") {
|
|
11
|
-
const map = new Map<string, V>()
|
|
11
|
+
const map = new Map<string, V>();
|
|
12
12
|
return {
|
|
13
13
|
get: (key) => map.get(key),
|
|
14
14
|
set: (key, value) => {
|
|
15
|
-
map.set(key, value)
|
|
15
|
+
map.set(key, value);
|
|
16
16
|
},
|
|
17
17
|
delete: (key) => map.delete(key),
|
|
18
18
|
clear: () => map.clear(),
|
|
19
19
|
values: () => map.values(),
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const map = new Map<string, WeakRef<V>>()
|
|
23
|
+
const map = new Map<string, WeakRef<V>>();
|
|
24
24
|
const registry = new FinalizationRegistry<string>((key) => {
|
|
25
|
-
map.delete(key)
|
|
26
|
-
})
|
|
25
|
+
map.delete(key);
|
|
26
|
+
});
|
|
27
27
|
|
|
28
28
|
return {
|
|
29
29
|
get: (key) => map.get(key)?.deref(),
|
|
30
30
|
set: (key, value) => {
|
|
31
|
-
map.set(key, new WeakRef(value))
|
|
32
|
-
registry.register(value, key)
|
|
31
|
+
map.set(key, new WeakRef(value));
|
|
32
|
+
registry.register(value, key);
|
|
33
33
|
},
|
|
34
34
|
delete: (key) => map.delete(key),
|
|
35
35
|
clear: () => map.clear(),
|
|
36
36
|
*values() {
|
|
37
37
|
for (const ref of map.values()) {
|
|
38
|
-
const value = ref.deref()
|
|
39
|
-
if (value !== undefined) yield value
|
|
38
|
+
const value = ref.deref();
|
|
39
|
+
if (value !== undefined) yield value;
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
}
|
|
43
|
-
}
|
|
42
|
+
};
|
|
43
|
+
};
|