effect-atom-svelte 0.2.0 → 0.4.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/README.md +5 -2
- package/dist/Form.d.ts +605 -0
- package/dist/Form.js +67 -0
- package/dist/Hooks.svelte.d.ts +70 -10
- package/dist/Hooks.svelte.js +489 -163
- package/dist/HydrationBoundary.svelte +134 -17
- package/dist/HydrationBoundary.svelte.d.ts +13 -4
- package/dist/Inspector.d.ts +7 -3
- package/dist/Inspector.js +113 -29
- package/dist/RegistryContext.d.ts +2 -0
- package/dist/RegistryContext.js +13 -5
- package/dist/ScopedAtom.d.ts +5 -4
- package/dist/ScopedAtom.js +20 -11
- package/dist/SvelteKit.d.ts +71 -12
- package/dist/SvelteKit.js +133 -54
- package/dist/index.d.ts +10 -1
- package/dist/index.js +10 -1
- package/dist/internal/awaitWrite.d.ts +11 -0
- package/dist/internal/awaitWrite.js +27 -0
- package/dist/internal/boundaries.d.ts +9 -0
- package/dist/internal/boundaries.js +10 -0
- package/dist/internal/disposal.d.ts +13 -0
- package/dist/internal/disposal.js +43 -0
- package/dist/internal/form/array.d.ts +21 -0
- package/dist/internal/form/array.js +57 -0
- package/dist/internal/form/constraints.d.ts +13 -0
- package/dist/internal/form/constraints.js +166 -0
- package/dist/internal/form/engine.d.ts +56 -0
- package/dist/internal/form/engine.js +842 -0
- package/dist/internal/form/engineOf.d.ts +26 -0
- package/dist/internal/form/engineOf.js +43 -0
- package/dist/internal/form/field.d.ts +30 -0
- package/dist/internal/form/field.js +68 -0
- package/dist/internal/form/group.d.ts +13 -0
- package/dist/internal/form/group.js +20 -0
- package/dist/internal/form/instance.d.ts +41 -0
- package/dist/internal/form/instance.js +135 -0
- package/dist/internal/form/issues.d.ts +23 -0
- package/dist/internal/form/issues.js +144 -0
- package/dist/internal/form/paths.d.ts +47 -0
- package/dist/internal/form/paths.js +175 -0
- package/dist/internal/form/types.d.ts +80 -0
- package/dist/internal/form/types.js +1 -0
- package/dist/internal/form/validationError.d.ts +13 -0
- package/dist/internal/form/validationError.js +6 -0
- package/dist/internal/hydration.d.ts +11 -2
- package/dist/internal/hydration.js +26 -4
- package/dist/internal/nodeInternals.d.ts +6 -0
- package/dist/internal/nodeInternals.js +64 -7
- package/dist/internal/renderEnd.d.ts +13 -0
- package/dist/internal/renderEnd.js +34 -0
- package/dist/internal/teardown.svelte.d.ts +17 -0
- package/dist/internal/teardown.svelte.js +44 -0
- package/dist/internal/troubleshooting.d.ts +2 -0
- package/dist/internal/troubleshooting.js +3 -0
- package/package.json +12 -10
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { SchemaAST } from "effect";
|
|
2
|
+
const isJsonArray = (json) => Array.isArray(json);
|
|
3
|
+
const objectPayload = (payload) => typeof payload === "object" && payload !== null && !isJsonArray(payload)
|
|
4
|
+
? payload
|
|
5
|
+
: undefined;
|
|
6
|
+
const finite = (payload, key) => {
|
|
7
|
+
const value = payload?.[key];
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
};
|
|
12
|
+
// An HTML pattern is compiled with the v flag and must match the whole value, while a Schema
|
|
13
|
+
// pattern matches anywhere in it: only one anchored at both ends, with no flags but u or v, means
|
|
14
|
+
// the same to both.
|
|
15
|
+
const sameAsHtmlPattern = (source, flags) => {
|
|
16
|
+
if (flags !== undefined &&
|
|
17
|
+
!(typeof flags === "string" && ["", "u", "v"].includes(flags))) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (!(source.startsWith("^") && source.endsWith("$"))) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
// `^a|b$` is anchored only at its ends: the browser wraps it as `^(?:^a|b$)$`.
|
|
24
|
+
return !source.includes("|") && !source.endsWith("\\$");
|
|
25
|
+
};
|
|
26
|
+
// Effect's built-in checks say what they are in a `representation` annotation (`Schema.ts`, for
|
|
27
|
+
// example `isMinLength`). Only the ones that map exactly onto an HTML attribute are read; exclusive
|
|
28
|
+
// bounds and custom filters are left out.
|
|
29
|
+
const readers = {
|
|
30
|
+
"effect/schema/isBetween": (payload) => ({
|
|
31
|
+
max: payload?.exclusiveMaximum === true
|
|
32
|
+
? undefined
|
|
33
|
+
: finite(payload, "maximum"),
|
|
34
|
+
min: payload?.exclusiveMinimum === true
|
|
35
|
+
? undefined
|
|
36
|
+
: finite(payload, "minimum"),
|
|
37
|
+
}),
|
|
38
|
+
"effect/schema/isBetweenLength": (payload) => ({
|
|
39
|
+
maxlength: finite(payload, "maximum"),
|
|
40
|
+
minlength: finite(payload, "minimum"),
|
|
41
|
+
}),
|
|
42
|
+
"effect/schema/isGreaterThanOrEqualTo": (payload) => ({
|
|
43
|
+
min: finite(payload, "minimum"),
|
|
44
|
+
}),
|
|
45
|
+
"effect/schema/isLessThanOrEqualTo": (payload) => ({
|
|
46
|
+
max: finite(payload, "maximum"),
|
|
47
|
+
}),
|
|
48
|
+
"effect/schema/isMaxLength": (payload) => ({
|
|
49
|
+
maxlength: finite(payload, "maxLength"),
|
|
50
|
+
}),
|
|
51
|
+
"effect/schema/isMinLength": (payload) => ({
|
|
52
|
+
minlength: finite(payload, "minLength"),
|
|
53
|
+
}),
|
|
54
|
+
"effect/schema/isPattern": (payload) => {
|
|
55
|
+
const source = payload?.source;
|
|
56
|
+
return typeof source === "string" &&
|
|
57
|
+
sameAsHtmlPattern(source, payload?.flags)
|
|
58
|
+
? { pattern: source }
|
|
59
|
+
: {};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
/** Copies `from`'s defined constraints into `into`, in `from`'s order. */
|
|
63
|
+
const merge = (into, from) => {
|
|
64
|
+
Object.assign(into, Object.fromEntries(Object.entries(from).filter(([, value]) => value !== undefined)));
|
|
65
|
+
};
|
|
66
|
+
const readCheck = (check, into) => {
|
|
67
|
+
if (check._tag === "FilterGroup") {
|
|
68
|
+
for (const inner of check.checks) {
|
|
69
|
+
readCheck(inner, into);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const representation = check.annotations?.representation;
|
|
74
|
+
if (representation === undefined) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const reader = readers[representation.id];
|
|
78
|
+
if (reader !== undefined) {
|
|
79
|
+
merge(into, reader(objectPayload(representation.payload)));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
// The node's checks, then the checks along its encoding chain (NumberFromString keeps a range check
|
|
83
|
+
// on the decoded Number and a NonEmpty check on the encoded String).
|
|
84
|
+
const readNode = (ast, into) => {
|
|
85
|
+
for (const check of ast.checks ?? []) {
|
|
86
|
+
readCheck(check, into);
|
|
87
|
+
}
|
|
88
|
+
for (const link of ast.encoding ?? []) {
|
|
89
|
+
readNode(link.to, into);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// Steps into declarations (a Schema.Class wraps its Struct) and suspensions, to a node with fields.
|
|
93
|
+
const unwrap = (ast) => {
|
|
94
|
+
if (SchemaAST.isSuspend(ast)) {
|
|
95
|
+
return unwrap(ast.thunk());
|
|
96
|
+
}
|
|
97
|
+
if (SchemaAST.isDeclaration(ast)) {
|
|
98
|
+
const [inner] = ast.typeParameters;
|
|
99
|
+
if (inner !== undefined) {
|
|
100
|
+
return unwrap(inner);
|
|
101
|
+
}
|
|
102
|
+
const last = ast.encoding?.at(-1)?.to;
|
|
103
|
+
return last === undefined ? ast : unwrap(last);
|
|
104
|
+
}
|
|
105
|
+
return ast;
|
|
106
|
+
};
|
|
107
|
+
// The field at one step below `ast`, with whether its key is optional. In a union, the first member
|
|
108
|
+
// that has the step.
|
|
109
|
+
const step = (ast, segment) => {
|
|
110
|
+
const node = unwrap(ast);
|
|
111
|
+
if (SchemaAST.isUnion(node)) {
|
|
112
|
+
for (const member of node.types) {
|
|
113
|
+
const found = step(member, segment);
|
|
114
|
+
if (found !== undefined) {
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (SchemaAST.isObjects(node) && typeof segment === "string") {
|
|
121
|
+
const property = node.propertySignatures.find((candidate) => candidate.name === segment);
|
|
122
|
+
return property === undefined
|
|
123
|
+
? undefined
|
|
124
|
+
: { ast: property.type, optional: SchemaAST.isOptional(property.type) };
|
|
125
|
+
}
|
|
126
|
+
if (SchemaAST.isArrays(node) && typeof segment === "number") {
|
|
127
|
+
const element = node.elements[segment] ?? node.rest[0];
|
|
128
|
+
return element === undefined
|
|
129
|
+
? undefined
|
|
130
|
+
: { ast: element, optional: SchemaAST.isOptional(element) };
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
};
|
|
134
|
+
// `optional(X)` is an optional key holding `X | undefined`; the constraints are X's.
|
|
135
|
+
const withoutUndefined = (ast) => {
|
|
136
|
+
if (!SchemaAST.isUnion(ast)) {
|
|
137
|
+
return ast;
|
|
138
|
+
}
|
|
139
|
+
const types = ast.types.filter((member) => member._tag !== "Undefined");
|
|
140
|
+
return types.length === 1 && types[0] !== undefined ? types[0] : ast;
|
|
141
|
+
};
|
|
142
|
+
/** The HTML constraints for the field at `path` in `schema`'s AST. */
|
|
143
|
+
export const constraintsAt = (root, path) => {
|
|
144
|
+
let found = {
|
|
145
|
+
ast: root,
|
|
146
|
+
optional: false,
|
|
147
|
+
};
|
|
148
|
+
for (const segment of path) {
|
|
149
|
+
found &&= step(found.ast, segment);
|
|
150
|
+
}
|
|
151
|
+
if (found === undefined) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
const { ast, optional } = found;
|
|
155
|
+
const into = {};
|
|
156
|
+
readNode(withoutUndefined(ast), into);
|
|
157
|
+
// `required` means "not empty" to a browser, so it is set only where the Schema rejects an empty
|
|
158
|
+
// string: a required key alone allows "", and a checkbox's boolean is always present.
|
|
159
|
+
const { minlength } = into;
|
|
160
|
+
const out = {};
|
|
161
|
+
if (!optional && typeof minlength === "number" && minlength >= 1) {
|
|
162
|
+
out.required = true;
|
|
163
|
+
}
|
|
164
|
+
merge(out, into);
|
|
165
|
+
return out;
|
|
166
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Exit } from "effect";
|
|
2
|
+
import { Atom } from "effect/reactivity";
|
|
3
|
+
import type { AtomRegistry } from "effect/reactivity";
|
|
4
|
+
import { ArrayImpl } from "./array.ts";
|
|
5
|
+
import type { Constraints } from "./constraints.ts";
|
|
6
|
+
import { FieldImpl } from "./field.ts";
|
|
7
|
+
import { GroupImpl } from "./group.ts";
|
|
8
|
+
import { InstanceImpl } from "./instance.ts";
|
|
9
|
+
import type { IndexPath, KeyPath } from "./paths.ts";
|
|
10
|
+
import type { Definition, EngineOptions, ErrorsInput, State } from "./types.ts";
|
|
11
|
+
export declare class Engine {
|
|
12
|
+
#private;
|
|
13
|
+
readonly definition: Definition;
|
|
14
|
+
readonly options: EngineOptions;
|
|
15
|
+
readonly registry: AtomRegistry.AtomRegistry;
|
|
16
|
+
readonly stateAtom: Atom.Writable<State>;
|
|
17
|
+
readonly valuesAtom: Atom.Writable<unknown>;
|
|
18
|
+
readonly root: InstanceImpl;
|
|
19
|
+
constructor(definition: Definition, options: EngineOptions);
|
|
20
|
+
/** Reads state and values through these instead, as `useForm` does to make them reactive. */
|
|
21
|
+
setReaders(state: () => State, values: () => unknown): void;
|
|
22
|
+
get state(): State;
|
|
23
|
+
get values(): unknown;
|
|
24
|
+
/** Writes a field's value, as typing into it does. */
|
|
25
|
+
setField(path: KeyPath, value: unknown): void;
|
|
26
|
+
/** Marks a field touched, as leaving it does, and checks it if `validateOn` says so. */
|
|
27
|
+
leave(path: KeyPath): void;
|
|
28
|
+
setTouched(path: KeyPath, touched: boolean): void;
|
|
29
|
+
setValues(values: unknown): void;
|
|
30
|
+
/** Changes an array and its item keys together, so state stays with each item. */
|
|
31
|
+
updateArray(path: KeyPath, f: (items: unknown[], keys: number[]) => void): void;
|
|
32
|
+
newKey(): number;
|
|
33
|
+
/** The current initial values change: a clean form takes them, a dirty one keeps its edits. */
|
|
34
|
+
setInitial(initial: unknown): void;
|
|
35
|
+
reset(values?: unknown): void;
|
|
36
|
+
revertToLastSubmit(): void;
|
|
37
|
+
setErrors(errors: ErrorsInput, source: "submit" | "manual"): void;
|
|
38
|
+
/**
|
|
39
|
+
* Checks the given paths (all by default), shows their errors, and says whether they passed: no
|
|
40
|
+
* Schema error and no external error at or under any of them.
|
|
41
|
+
*/
|
|
42
|
+
validate(paths?: readonly (string | IndexPath)[]): Promise<boolean>;
|
|
43
|
+
submit(args?: unknown): Promise<Exit.Exit<unknown, unknown>>;
|
|
44
|
+
/** The error to show on a path: from outside the Schema first, then the Schema's once revealed. */
|
|
45
|
+
errorAt(path: KeyPath): string | undefined;
|
|
46
|
+
indexPath(path: KeyPath): IndexPath | undefined;
|
|
47
|
+
/** Resolves `relative`, an index path below `base`, to a key path. */
|
|
48
|
+
resolve(base: KeyPath, relative: string | IndexPath): KeyPath;
|
|
49
|
+
elementId(path: KeyPath): string;
|
|
50
|
+
constraints(path: IndexPath): Constraints;
|
|
51
|
+
field(path: KeyPath): FieldImpl;
|
|
52
|
+
group(path: KeyPath): GroupImpl;
|
|
53
|
+
array(path: KeyPath): ArrayImpl;
|
|
54
|
+
/** Releases the instance's atoms; a submit in flight keeps them until it settles. */
|
|
55
|
+
dispose(): void;
|
|
56
|
+
}
|