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.
Files changed (56) hide show
  1. package/README.md +5 -2
  2. package/dist/Form.d.ts +605 -0
  3. package/dist/Form.js +67 -0
  4. package/dist/Hooks.svelte.d.ts +70 -10
  5. package/dist/Hooks.svelte.js +489 -163
  6. package/dist/HydrationBoundary.svelte +134 -17
  7. package/dist/HydrationBoundary.svelte.d.ts +13 -4
  8. package/dist/Inspector.d.ts +7 -3
  9. package/dist/Inspector.js +113 -29
  10. package/dist/RegistryContext.d.ts +2 -0
  11. package/dist/RegistryContext.js +13 -5
  12. package/dist/ScopedAtom.d.ts +5 -4
  13. package/dist/ScopedAtom.js +20 -11
  14. package/dist/SvelteKit.d.ts +71 -12
  15. package/dist/SvelteKit.js +133 -54
  16. package/dist/index.d.ts +10 -1
  17. package/dist/index.js +10 -1
  18. package/dist/internal/awaitWrite.d.ts +11 -0
  19. package/dist/internal/awaitWrite.js +27 -0
  20. package/dist/internal/boundaries.d.ts +9 -0
  21. package/dist/internal/boundaries.js +10 -0
  22. package/dist/internal/disposal.d.ts +13 -0
  23. package/dist/internal/disposal.js +43 -0
  24. package/dist/internal/form/array.d.ts +21 -0
  25. package/dist/internal/form/array.js +57 -0
  26. package/dist/internal/form/constraints.d.ts +13 -0
  27. package/dist/internal/form/constraints.js +166 -0
  28. package/dist/internal/form/engine.d.ts +56 -0
  29. package/dist/internal/form/engine.js +842 -0
  30. package/dist/internal/form/engineOf.d.ts +26 -0
  31. package/dist/internal/form/engineOf.js +43 -0
  32. package/dist/internal/form/field.d.ts +30 -0
  33. package/dist/internal/form/field.js +68 -0
  34. package/dist/internal/form/group.d.ts +13 -0
  35. package/dist/internal/form/group.js +20 -0
  36. package/dist/internal/form/instance.d.ts +41 -0
  37. package/dist/internal/form/instance.js +135 -0
  38. package/dist/internal/form/issues.d.ts +23 -0
  39. package/dist/internal/form/issues.js +144 -0
  40. package/dist/internal/form/paths.d.ts +47 -0
  41. package/dist/internal/form/paths.js +175 -0
  42. package/dist/internal/form/types.d.ts +80 -0
  43. package/dist/internal/form/types.js +1 -0
  44. package/dist/internal/form/validationError.d.ts +13 -0
  45. package/dist/internal/form/validationError.js +6 -0
  46. package/dist/internal/hydration.d.ts +11 -2
  47. package/dist/internal/hydration.js +26 -4
  48. package/dist/internal/nodeInternals.d.ts +6 -0
  49. package/dist/internal/nodeInternals.js +64 -7
  50. package/dist/internal/renderEnd.d.ts +13 -0
  51. package/dist/internal/renderEnd.js +34 -0
  52. package/dist/internal/teardown.svelte.d.ts +17 -0
  53. package/dist/internal/teardown.svelte.js +44 -0
  54. package/dist/internal/troubleshooting.d.ts +2 -0
  55. package/dist/internal/troubleshooting.js +3 -0
  56. 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
+ }