effect-atom-svelte 0.4.0 → 0.4.1

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/Form.d.ts CHANGED
@@ -92,7 +92,11 @@ export interface MakeOptions {
92
92
  readonly revalidateOn?: Timing | undefined;
93
93
  /** How long to wait after a change before validating. Defaults to none. */
94
94
  readonly debounce?: Duration.Input | undefined;
95
- /** A message for an issue that has no `message` annotation, or `undefined` for Effect's own. */
95
+ /**
96
+ * A message for an issue that has no `message` annotation, or `undefined` for Effect's own. A
97
+ * value outside a union, such as one that isn't in a `Schema.Literals`, comes as an
98
+ * `InvalidType` whose `ast` is the union.
99
+ */
96
100
  readonly leafHook?: ((issue: SchemaIssue.Leaf) => string | undefined) | undefined;
97
101
  /** A message for a failed check that has no `message` annotation, or `undefined` for Effect's. */
98
102
  readonly checkHook?: ((issue: SchemaIssue.Filter) => string | undefined) | undefined;
@@ -9,5 +9,8 @@ export interface Constraints {
9
9
  readonly max?: number | undefined;
10
10
  readonly pattern?: string | undefined;
11
11
  }
12
- /** The HTML constraints for the field at `path` in `schema`'s AST. */
13
- export declare const constraintsAt: (root: SchemaAST.AST, path: IndexPath) => Constraints;
12
+ /**
13
+ * The HTML constraints for the field at `path` in `schema`'s AST. `lengthAt` gives the length of the
14
+ * array at a path, read only for a tuple with elements after its rest.
15
+ */
16
+ export declare const constraintsAt: (root: SchemaAST.AST, path: IndexPath, lengthAt: (path: IndexPath) => number | undefined) => Constraints;
@@ -104,13 +104,31 @@ const unwrap = (ast) => {
104
104
  }
105
105
  return ast;
106
106
  };
107
+ // The element at `index`, picked as Effect's parser picks it (`Arrays.getParser` in `SchemaAST.ts`):
108
+ // a fixed element, else the rest element, except that the elements after the rest are counted back
109
+ // from the array's end. Those need its length; without one, there is no element.
110
+ const elementAt = (node, index, length) => {
111
+ if (index < node.elements.length) {
112
+ return node.elements[index];
113
+ }
114
+ const [rest, ...tail] = node.rest;
115
+ if (tail.length === 0) {
116
+ return rest;
117
+ }
118
+ const len = length();
119
+ if (len === undefined) {
120
+ return undefined;
121
+ }
122
+ const threshold = Math.max(node.elements.length, len - tail.length);
123
+ return index >= threshold ? tail[index - threshold] : rest;
124
+ };
107
125
  // The field at one step below `ast`, with whether its key is optional. In a union, the first member
108
126
  // that has the step.
109
- const step = (ast, segment) => {
127
+ const step = (ast, segment, length) => {
110
128
  const node = unwrap(ast);
111
129
  if (SchemaAST.isUnion(node)) {
112
130
  for (const member of node.types) {
113
- const found = step(member, segment);
131
+ const found = step(member, segment, length);
114
132
  if (found !== undefined) {
115
133
  return found;
116
134
  }
@@ -124,7 +142,7 @@ const step = (ast, segment) => {
124
142
  : { ast: property.type, optional: SchemaAST.isOptional(property.type) };
125
143
  }
126
144
  if (SchemaAST.isArrays(node) && typeof segment === "number") {
127
- const element = node.elements[segment] ?? node.rest[0];
145
+ const element = elementAt(node, segment, length);
128
146
  return element === undefined
129
147
  ? undefined
130
148
  : { ast: element, optional: SchemaAST.isOptional(element) };
@@ -139,19 +157,13 @@ const withoutUndefined = (ast) => {
139
157
  const types = ast.types.filter((member) => member._tag !== "Undefined");
140
158
  return types.length === 1 && types[0] !== undefined ? types[0] : ast;
141
159
  };
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;
160
+ // A field's constraints depend only on its node and whether its key is optional, so each is read once.
161
+ const cache = {
162
+ optional: new WeakMap(),
163
+ required: new WeakMap(),
164
+ };
165
+ const none = {};
166
+ const read = (ast, optional) => {
155
167
  const into = {};
156
168
  readNode(withoutUndefined(ast), into);
157
169
  // `required` means "not empty" to a browser, so it is set only where the Schema rejects an empty
@@ -164,3 +176,27 @@ export const constraintsAt = (root, path) => {
164
176
  merge(out, into);
165
177
  return out;
166
178
  };
179
+ /**
180
+ * The HTML constraints for the field at `path` in `schema`'s AST. `lengthAt` gives the length of the
181
+ * array at a path, read only for a tuple with elements after its rest.
182
+ */
183
+ export const constraintsAt = (root, path, lengthAt) => {
184
+ let found = {
185
+ ast: root,
186
+ optional: false,
187
+ };
188
+ for (const [depth, segment] of path.entries()) {
189
+ found &&= step(found.ast, segment, () => lengthAt(path.slice(0, depth)));
190
+ }
191
+ if (found === undefined) {
192
+ return none;
193
+ }
194
+ const { ast, optional } = found;
195
+ const byNode = optional ? cache.optional : cache.required;
196
+ let constraints = byNode.get(ast);
197
+ if (constraints === undefined) {
198
+ constraints = read(ast, optional);
199
+ byNode.set(ast, constraints);
200
+ }
201
+ return constraints;
202
+ };
@@ -89,7 +89,6 @@ export class Engine {
89
89
  #fields = new Map();
90
90
  #groups = new Map();
91
91
  #arrays = new Map();
92
- #constraints = new Map();
93
92
  constructor(definition, options) {
94
93
  this.definition = definition;
95
94
  this.options = options;
@@ -784,14 +783,11 @@ export class Engine {
784
783
  : `${this.options.id}-${idSuffix(path)}`;
785
784
  }
786
785
  constraints(path) {
787
- const schemaPath = path.map((segment) => typeof segment === "number" ? 0 : segment);
788
- const id = pathId(schemaPath);
789
- let found = this.#constraints.get(id);
790
- if (found === undefined) {
791
- found = constraintsAt(this.definition.schema.ast, schemaPath);
792
- this.#constraints.set(id, found);
793
- }
794
- return found;
786
+ // Reads the values only for a tuple with elements after its rest, whose fields move as it grows.
787
+ return constraintsAt(this.definition.schema.ast, path, (arrayPath) => {
788
+ const array = getIn(this.values, arrayPath);
789
+ return Array.isArray(array) ? array.length : undefined;
790
+ });
795
791
  }
796
792
  field(path) {
797
793
  const id = pathId(path);
@@ -66,6 +66,9 @@ const emitDefault = (issue, path, emit) => {
66
66
  emit([...path, ...(formatted.path ?? [])], formatted.message);
67
67
  }
68
68
  };
69
+ const asInvalidType = (issue) => new SchemaIssue.InvalidType(issue.ast, issue.input, {
70
+ reportInput: SchemaIssue.hasInput(issue),
71
+ });
69
72
  const walkFilter = (issue, path, hooks, emit, walk) => {
70
73
  const pointed = pointedPaths(issue.issue, path);
71
74
  const annotated = findMessage(issue.issue) ?? findMessage(issue);
@@ -119,8 +122,13 @@ export const toPathIssues = (issue, hooks) => {
119
122
  }
120
123
  case "Composite":
121
124
  case "AnyOf": {
122
- if (current.issues.length === 0) {
123
- emitDefault(current, path, emit);
125
+ // A value no member of the union applies to (one outside a Literals, say) fails with no
126
+ // inner issues. Effect's formatter reports it without calling a hook. Walked here as the
127
+ // InvalidType a single Literal gives, so `leafHook` names it, and the fallback is
128
+ // Effect's same `Expected "a" | "b"`.
129
+ if (current._tag === "AnyOf" && current.issues.length === 0) {
130
+ walk(asInvalidType(current), path);
131
+ return;
124
132
  }
125
133
  for (const inner of current.issues) {
126
134
  walk(inner, path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-atom-svelte",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Community-built Svelte 5 bindings for Effect Atom (not affiliated with the Effect team)",
5
5
  "keywords": [
6
6
  "effect",