@warlock.js/seal 5.19.0 → 5.20.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/CHANGELOG.md +12 -0
- package/esm/config.d.mts +0 -1
- package/esm/config.d.mts.map +1 -1
- package/esm/factory/validators.d.mts.map +1 -1
- package/esm/helpers/get-field-value.d.mts +0 -1
- package/esm/helpers/get-field-value.d.mts.map +1 -1
- package/esm/helpers/index.d.mts +5 -0
- package/esm/helpers/validation-helpers.d.mts +0 -1
- package/esm/helpers/validation-helpers.d.mts.map +1 -1
- package/esm/mutators/array-mutators.d.mts +0 -1
- package/esm/mutators/array-mutators.d.mts.map +1 -1
- package/esm/mutators/boolean-mutators.d.mts +0 -1
- package/esm/mutators/boolean-mutators.d.mts.map +1 -1
- package/esm/mutators/date-mutators.d.mts +0 -1
- package/esm/mutators/date-mutators.d.mts.map +1 -1
- package/esm/mutators/index.d.mts +7 -0
- package/esm/mutators/number-mutators.d.mts +0 -1
- package/esm/mutators/number-mutators.d.mts.map +1 -1
- package/esm/mutators/object-mutators.d.mts +0 -1
- package/esm/mutators/object-mutators.d.mts.map +1 -1
- package/esm/mutators/scalar-mutators.d.mts +0 -1
- package/esm/mutators/scalar-mutators.d.mts.map +1 -1
- package/esm/mutators/string-mutators.d.mts +0 -1
- package/esm/mutators/string-mutators.d.mts.map +1 -1
- package/esm/rules/array/array-rules.d.mts +0 -1
- package/esm/rules/array/array-rules.d.mts.map +1 -1
- package/esm/rules/array/index.d.mts +1 -0
- package/esm/rules/color/color-rules.d.mts +0 -1
- package/esm/rules/color/color-rules.d.mts.map +1 -1
- package/esm/rules/color/index.d.mts +1 -0
- package/esm/rules/common/enum.d.mts +0 -1
- package/esm/rules/common/enum.d.mts.map +1 -1
- package/esm/rules/common/equals-field-rules.d.mts +0 -1
- package/esm/rules/common/equals-field-rules.d.mts.map +1 -1
- package/esm/rules/common/index.d.mts +6 -0
- package/esm/rules/common/instanceof.d.mts +0 -1
- package/esm/rules/common/instanceof.d.mts.map +1 -1
- package/esm/rules/common/literal.d.mts +0 -1
- package/esm/rules/common/literal.d.mts.map +1 -1
- package/esm/rules/common/type-rules.d.mts +0 -1
- package/esm/rules/common/type-rules.d.mts.map +1 -1
- package/esm/rules/common/unknown-key.d.mts +0 -1
- package/esm/rules/common/unknown-key.d.mts.map +1 -1
- package/esm/rules/conditional/forbidden-if-rules.d.mts +0 -1
- package/esm/rules/conditional/forbidden-if-rules.d.mts.map +1 -1
- package/esm/rules/conditional/index.d.mts +10 -0
- package/esm/rules/conditional/present-if-rules.d.mts +0 -1
- package/esm/rules/conditional/present-if-rules.d.mts.map +1 -1
- package/esm/rules/conditional/present-unless-rules.d.mts +0 -1
- package/esm/rules/conditional/present-unless-rules.d.mts.map +1 -1
- package/esm/rules/conditional/present-with-rules.d.mts +0 -1
- package/esm/rules/conditional/present-with-rules.d.mts.map +1 -1
- package/esm/rules/conditional/present-without-rules.d.mts +0 -1
- package/esm/rules/conditional/present-without-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-if-rules.d.mts +0 -1
- package/esm/rules/conditional/required-if-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-unless-rules.d.mts +0 -1
- package/esm/rules/conditional/required-unless-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-when-rule.d.mts +0 -1
- package/esm/rules/conditional/required-when-rule.d.mts.map +1 -1
- package/esm/rules/conditional/required-with-rules.d.mts +0 -1
- package/esm/rules/conditional/required-with-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-without-rules.d.mts +0 -1
- package/esm/rules/conditional/required-without-rules.d.mts.map +1 -1
- package/esm/rules/core/equal.d.mts +0 -1
- package/esm/rules/core/equal.d.mts.map +1 -1
- package/esm/rules/core/forbidden.d.mts +0 -1
- package/esm/rules/core/forbidden.d.mts.map +1 -1
- package/esm/rules/core/index.d.mts +5 -0
- package/esm/rules/core/required.d.mts +0 -1
- package/esm/rules/core/required.d.mts.map +1 -1
- package/esm/rules/core/union.d.mts.map +1 -1
- package/esm/rules/core/when.d.mts +0 -1
- package/esm/rules/core/when.d.mts.map +1 -1
- package/esm/rules/date/date-comparison-rules.d.mts +0 -1
- package/esm/rules/date/date-comparison-rules.d.mts.map +1 -1
- package/esm/rules/date/date-day-rules.d.mts +0 -1
- package/esm/rules/date/date-day-rules.d.mts.map +1 -1
- package/esm/rules/date/date-field-comparison-rules.d.mts +0 -1
- package/esm/rules/date/date-field-comparison-rules.d.mts.map +1 -1
- package/esm/rules/date/date-period-rules.d.mts +0 -1
- package/esm/rules/date/date-period-rules.d.mts.map +1 -1
- package/esm/rules/date/date-relative-rules.d.mts +0 -1
- package/esm/rules/date/date-relative-rules.d.mts.map +1 -1
- package/esm/rules/date/date-special-rules.d.mts +0 -1
- package/esm/rules/date/date-special-rules.d.mts.map +1 -1
- package/esm/rules/date/date.d.mts +0 -1
- package/esm/rules/date/date.d.mts.map +1 -1
- package/esm/rules/date/index.d.mts +7 -0
- package/esm/rules/file/dimensions.d.mts +0 -1
- package/esm/rules/file/dimensions.d.mts.map +1 -1
- package/esm/rules/file/file-size.d.mts +0 -1
- package/esm/rules/file/file-size.d.mts.map +1 -1
- package/esm/rules/file/index.d.mts +2 -0
- package/esm/rules/index.d.mts +45 -0
- package/esm/rules/length/index.d.mts +1 -0
- package/esm/rules/length/length-rules.d.mts +0 -1
- package/esm/rules/length/length-rules.d.mts.map +1 -1
- package/esm/rules/number/index.d.mts +1 -0
- package/esm/rules/number/number-rules.d.mts +0 -1
- package/esm/rules/number/number-rules.d.mts.map +1 -1
- package/esm/rules/string/alpha.d.mts +0 -1
- package/esm/rules/string/alpha.d.mts.map +1 -1
- package/esm/rules/string/credit-card.d.mts +0 -1
- package/esm/rules/string/credit-card.d.mts.map +1 -1
- package/esm/rules/string/email.d.mts +0 -1
- package/esm/rules/string/email.d.mts.map +1 -1
- package/esm/rules/string/id-formats.d.mts +0 -1
- package/esm/rules/string/id-formats.d.mts.map +1 -1
- package/esm/rules/string/index.d.mts +11 -0
- package/esm/rules/string/ip.d.mts +0 -1
- package/esm/rules/string/ip.d.mts.map +1 -1
- package/esm/rules/string/matches.d.mts +0 -1
- package/esm/rules/string/matches.d.mts.map +1 -1
- package/esm/rules/string/pattern.d.mts +0 -1
- package/esm/rules/string/pattern.d.mts.map +1 -1
- package/esm/rules/string/string-comparison.d.mts +0 -1
- package/esm/rules/string/string-comparison.d.mts.map +1 -1
- package/esm/rules/string/strong-password-rule.d.mts +0 -1
- package/esm/rules/string/strong-password-rule.d.mts.map +1 -1
- package/esm/rules/string/url.d.mts +0 -1
- package/esm/rules/string/url.d.mts.map +1 -1
- package/esm/rules/string/without-whitespace.d.mts +0 -1
- package/esm/rules/string/without-whitespace.d.mts.map +1 -1
- package/esm/standard-schema/index.d.mts +3 -0
- package/esm/standard-schema/map-result.d.mts +0 -1
- package/esm/standard-schema/map-result.d.mts.map +1 -1
- package/esm/types/index.d.mts +10 -0
- package/esm/validators/array-validator.d.mts.map +1 -1
- package/esm/validators/base-validator.d.mts +0 -1
- package/esm/validators/base-validator.d.mts.map +1 -1
- package/esm/validators/computed-validator.d.mts.map +1 -1
- package/esm/validators/date-validator.d.mts +0 -1
- package/esm/validators/date-validator.d.mts.map +1 -1
- package/esm/validators/discriminated-union-validator.d.mts.map +1 -1
- package/esm/validators/lazy-validator.d.mts.map +1 -1
- package/esm/validators/managed-validator.d.mts.map +1 -1
- package/esm/validators/methods/equality-conditional-methods.d.mts +0 -1
- package/esm/validators/methods/equality-conditional-methods.d.mts.map +1 -1
- package/esm/validators/methods/required-methods.d.mts +0 -1
- package/esm/validators/methods/required-methods.d.mts.map +1 -1
- package/esm/validators/object-validator.d.mts.map +1 -1
- package/esm/validators/record-validator.d.mts.map +1 -1
- package/esm/validators/tuple-validator.d.mts.map +1 -1
- package/llms-full.txt +1167 -1167
- package/llms.txt +7 -7
- package/package.json +3 -3
- package/skills/bridge-standard-schema/SKILL.md +138 -138
- package/skills/compose-seal-modifiers/SKILL.md +271 -271
- package/skills/define-structural-shape/SKILL.md +138 -138
- package/skills/extend-seal-with-plugins/SKILL.md +178 -178
- package/skills/generate-json-schema/SKILL.md +143 -143
- package/skills/handle-seal-errors/SKILL.md +148 -148
- package/skills/index.md +21 -0
- package/skills/overview/SKILL.md +2 -2
- package/skills/pick-seal-primitive/SKILL.md +149 -149
package/llms.txt
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [bridge-standard-schema](@warlock.js/seal/bridge-standard-schema/SKILL.md):
|
|
10
|
-
- [compose-seal-modifiers](@warlock.js/seal/compose-seal-modifiers/SKILL.md):
|
|
11
|
-
- [define-structural-shape](@warlock.js/seal/define-structural-shape/SKILL.md):
|
|
12
|
-
- [extend-seal-with-plugins](@warlock.js/seal/extend-seal-with-plugins/SKILL.md):
|
|
13
|
-
- [generate-json-schema](@warlock.js/seal/generate-json-schema/SKILL.md):
|
|
14
|
-
- [handle-seal-errors](@warlock.js/seal/handle-seal-errors/SKILL.md):
|
|
9
|
+
- [bridge-standard-schema](@warlock.js/seal/bridge-standard-schema/SKILL.md): (no description)
|
|
10
|
+
- [compose-seal-modifiers](@warlock.js/seal/compose-seal-modifiers/SKILL.md): (no description)
|
|
11
|
+
- [define-structural-shape](@warlock.js/seal/define-structural-shape/SKILL.md): (no description)
|
|
12
|
+
- [extend-seal-with-plugins](@warlock.js/seal/extend-seal-with-plugins/SKILL.md): (no description)
|
|
13
|
+
- [generate-json-schema](@warlock.js/seal/generate-json-schema/SKILL.md): (no description)
|
|
14
|
+
- [handle-seal-errors](@warlock.js/seal/handle-seal-errors/SKILL.md): (no description)
|
|
15
15
|
- [overview](@warlock.js/seal/overview/SKILL.md): @warlock.js/seal overview for orientation and choosing the focused skill for the task.
|
|
16
|
-
- [pick-seal-primitive](@warlock.js/seal/pick-seal-primitive/SKILL.md):
|
|
16
|
+
- [pick-seal-primitive](@warlock.js/seal/pick-seal-primitive/SKILL.md): (no description)
|
|
17
17
|
- [seal-basics](@warlock.js/seal/seal-basics/SKILL.md): Start here for @warlock.js/seal schemas, validation, parsing, and typed structural contracts.
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"environment": "universal"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@mongez/supportive-is": "^2.1.
|
|
9
|
-
"@mongez/reinforcements": "^4.0.
|
|
8
|
+
"@mongez/supportive-is": "^2.1.5",
|
|
9
|
+
"@mongez/reinforcements": "^4.0.2",
|
|
10
10
|
"dayjs": "^1.11.9"
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"author": "hassanzohdy",
|
|
24
24
|
"license": "MIT",
|
|
25
|
-
"version": "5.
|
|
25
|
+
"version": "5.20.0",
|
|
26
26
|
"main": "./cjs/index.cjs",
|
|
27
27
|
"module": "./esm/index.mjs",
|
|
28
28
|
"types": "./esm/index.d.mts",
|
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: bridge-standard-schema
|
|
3
|
-
description: "
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# The Standard Schema bridge
|
|
7
|
-
|
|
8
|
-
Seal validators implement [Standard Schema V1](https://standardschema.dev) — every validator exposes a `~standard` member with `validate()`, JSON Schema metadata, and inferred types. Any consumer that accepts `StandardSchemaV1<T>` (`@warlock.js/ai` supervisor `output`, ai tool `input`, LangGraph state, OpenAI structured outputs, TanStack Form, Conform) accepts a seal schema directly.
|
|
9
|
-
|
|
10
|
-
This skill is about the **typing** of that interop — the phantom intersection that makes `v.object({...})` satisfy `StandardSchemaV1<T>` without `as unknown as` casts.
|
|
11
|
-
|
|
12
|
-
## The shape of the bridge
|
|
13
|
-
|
|
14
|
-
The `v` factory returns aren't bare validator classes. Each factory widens its return with a phantom intersection:
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
// Inside seal's factory:
|
|
18
|
-
object: <T extends Schema>(schema: T) =>
|
|
19
|
-
new ObjectValidator<T>(schema) as ObjectValidator<T>
|
|
20
|
-
& StandardSchemaV1<Infer<ObjectValidator<T>>>;
|
|
21
|
-
|
|
22
|
-
string: () =>
|
|
23
|
-
new StringValidator() as StringValidator & StandardSchemaV1<string>;
|
|
24
|
-
|
|
25
|
-
literal: <T extends readonly [...] >(...values: T) =>
|
|
26
|
-
new LiteralValidator<T>(values) as LiteralValidator<T> & StandardSchemaV1<T[number]>;
|
|
27
|
-
|
|
28
|
-
// ...one intersection per factory.
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
The intersection is **only on the factory return type** — the `BaseValidator` and `ObjectValidator` *class* shapes are unchanged. That distinction is load-bearing: putting `Infer<this>` directly on `BaseValidator['~standard']` breaks `@warlock.js/cascade`'s `Model<TSchema>` because `ObjectValidator` becomes invariant when its members vary with `TSchema`. The factory-side intersection avoids that — bare classes in `Model.schema: ObjectValidator<TSchema>` slots are still structurally compatible with the typed factory return.
|
|
32
|
-
|
|
33
|
-
## What this means for app code
|
|
34
|
-
|
|
35
|
-
You write the schema once, you use it anywhere a `StandardSchemaV1<T>` is expected, you never cast:
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
import { v, validate, type Infer } from "@warlock.js/seal";
|
|
39
|
-
import { ai } from "@warlock.js/ai";
|
|
40
|
-
|
|
41
|
-
const userSchema = v.object({
|
|
42
|
-
email: v.string().email(),
|
|
43
|
-
age: v.int().optional(),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
type User = Infer<typeof userSchema>;
|
|
47
|
-
|
|
48
|
-
// ✅ ai-side slot — no cast
|
|
49
|
-
ai.tool({
|
|
50
|
-
name: "create_user",
|
|
51
|
-
description: "Create a new user",
|
|
52
|
-
input: userSchema,
|
|
53
|
-
execute: async input => createUser(input),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ✅ supervisor output — no cast
|
|
57
|
-
ai.supervisor({
|
|
58
|
-
name: "user-flow",
|
|
59
|
-
output: userSchema,
|
|
60
|
-
// ...
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// ✅ direct validation — call ~standard
|
|
64
|
-
const result = await userSchema["~standard"].validate(rawData);
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## `Infer<typeof schema>` vs hand-rolled types
|
|
68
|
-
|
|
69
|
-
Use `Infer<>`. Always:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
const schema = v.object({
|
|
73
|
-
email: v.string(),
|
|
74
|
-
blocks: v.array(v.literal("text", "image")).optional(),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
type Output = Infer<typeof schema>;
|
|
78
|
-
// { email: string; blocks?: ("text" | "image")[] }
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Hand-rolled parallel types drift the moment a field changes — and seal's tightened typing now catches that drift at compile time. The old loose typing hid the mismatch; the bridge surfaces it.
|
|
82
|
-
|
|
83
|
-
## When the bridge "fails"
|
|
84
|
-
|
|
85
|
-
Three failure modes you'll see in real projects:
|
|
86
|
-
|
|
87
|
-
### 1. You annotated the schema with the bare class type
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
// ❌ Discards the phantom intersection
|
|
91
|
-
const schema: ObjectValidator<{ email: StringValidator }> = v.object({...});
|
|
92
|
-
|
|
93
|
-
// ✅ Let inference run
|
|
94
|
-
const schema = v.object({...});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The annotation strips `& StandardSchemaV1<...>` from the type. The schema stops fitting `StandardSchemaV1<T>` slots. Fix: remove the annotation. If you need the value type, use `Infer<typeof schema>`.
|
|
98
|
-
|
|
99
|
-
### 2. The schema's inferred shape doesn't match the target type
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
ai.supervisor<MyOutput, ...>({
|
|
103
|
-
output: schema, // ❌ schema infers differently from MyOutput
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
The supervisor's explicit `<MyOutput>` generic is a constraint the schema must satisfy. If they diverge, TS rightly rejects. Two fixes:
|
|
108
|
-
|
|
109
|
-
- **Drop the explicit generic** — let the supervisor infer output type from the schema. The hand-rolled type was probably documentation-only anyway.
|
|
110
|
-
- **Align them** — if the hand-rolled type is the source of truth (e.g. a domain type from elsewhere), make the schema match.
|
|
111
|
-
|
|
112
|
-
### 3. The error says `Result<unknown>` even though `Infer<>` resolves correctly
|
|
113
|
-
|
|
114
|
-
This is a TypeScript reporting quirk, not a bridge bug. When a `StandardSchemaV1<T>` slot rejects a schema, TS picks the simplest mismatch chain to report — often it falls through `BaseValidator['~standard'].validate`'s wider `Result<unknown>` declaration before reaching the factory-side narrower one. The intersection *is* there structurally, but the error message mentions `unknown`.
|
|
115
|
-
|
|
116
|
-
If you see this, ignore the `unknown` and ask: does `Infer<typeof schema>` match the slot's expected `T`? Probe with:
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
type _Probe = Infer<typeof schema>;
|
|
120
|
-
const _force: { __nope: 1 } = null as unknown as _Probe;
|
|
121
|
-
// Hover the error to see the resolved shape, then compare to the slot's T.
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
That's the real mismatch.
|
|
125
|
-
|
|
126
|
-
## Why not `Omit<class, "~standard"> & StandardSchemaV1<T>`?
|
|
127
|
-
|
|
128
|
-
Tempting — it would force the factory's `~standard` to fully replace the class's wider one, fixing the misleading `Result<unknown>` error message. **Don't do it.** `Omit` on a class instance type triggers the same variance trap as putting `Infer<this>` on the class itself: cascade explodes with hundreds of `ObjectValidator<{specific}>` vs `ObjectValidator<TSchema>` errors. The phantom intersection is the only shape that satisfies both ends — narrower for typed slots, structurally identical to the class for cascade's invariant generic positions.
|
|
129
|
-
|
|
130
|
-
## Why not import `@standard-schema/spec` instead of forking the types?
|
|
131
|
-
|
|
132
|
-
Seal forks the types locally. Reasons:
|
|
133
|
-
|
|
134
|
-
- Seal extends the spec with `StandardJSONSchemaV1` (JSON Schema converter on `~standard.jsonSchema`). The package doesn't have this — half-importing is the worst of both worlds.
|
|
135
|
-
- V1 spec is locked. The fork is ~70 lines and updates rarely.
|
|
136
|
-
- Avoids version-coupling pain across `@warlock.js/*` packages.
|
|
137
|
-
|
|
138
|
-
If V2 lands, re-fork. Until then the local copy is the right call.
|
|
1
|
+
---
|
|
2
|
+
name: bridge-standard-schema
|
|
3
|
+
description: 'Diagnose `StandardSchemaV1` interop — phantom-intersection at the `v` factory return, why typed slots reject a schema, cascade `Model<TSchema>` variance. Triggers: `StandardSchemaV1`, `~standard`, `Infer`, `ObjectValidator`, `StringValidator`, `LiteralValidator`, `BaseValidator`, `Model<TSchema>`, `StandardJSONSchemaV1`, `Result<unknown>`; "StandardSchemaV1 slot rejecting my schema", "Result<unknown> error", "drop as unknown as cast", "cascade Model schema variance"; typical import `import { v, type Infer } from "@warlock.js/seal"`. Skip: foundations — `@warlock.js/seal/seal-basics/SKILL.md`; JSON Schema gen — `@warlock.js/seal/generate-json-schema/SKILL.md`; competing spec `@standard-schema/spec`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The Standard Schema bridge
|
|
7
|
+
|
|
8
|
+
Seal validators implement [Standard Schema V1](https://standardschema.dev) — every validator exposes a `~standard` member with `validate()`, JSON Schema metadata, and inferred types. Any consumer that accepts `StandardSchemaV1<T>` (`@warlock.js/ai` supervisor `output`, ai tool `input`, LangGraph state, OpenAI structured outputs, TanStack Form, Conform) accepts a seal schema directly.
|
|
9
|
+
|
|
10
|
+
This skill is about the **typing** of that interop — the phantom intersection that makes `v.object({...})` satisfy `StandardSchemaV1<T>` without `as unknown as` casts.
|
|
11
|
+
|
|
12
|
+
## The shape of the bridge
|
|
13
|
+
|
|
14
|
+
The `v` factory returns aren't bare validator classes. Each factory widens its return with a phantom intersection:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// Inside seal's factory:
|
|
18
|
+
object: <T extends Schema>(schema: T) =>
|
|
19
|
+
new ObjectValidator<T>(schema) as ObjectValidator<T>
|
|
20
|
+
& StandardSchemaV1<Infer<ObjectValidator<T>>>;
|
|
21
|
+
|
|
22
|
+
string: () =>
|
|
23
|
+
new StringValidator() as StringValidator & StandardSchemaV1<string>;
|
|
24
|
+
|
|
25
|
+
literal: <T extends readonly [...] >(...values: T) =>
|
|
26
|
+
new LiteralValidator<T>(values) as LiteralValidator<T> & StandardSchemaV1<T[number]>;
|
|
27
|
+
|
|
28
|
+
// ...one intersection per factory.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The intersection is **only on the factory return type** — the `BaseValidator` and `ObjectValidator` *class* shapes are unchanged. That distinction is load-bearing: putting `Infer<this>` directly on `BaseValidator['~standard']` breaks `@warlock.js/cascade`'s `Model<TSchema>` because `ObjectValidator` becomes invariant when its members vary with `TSchema`. The factory-side intersection avoids that — bare classes in `Model.schema: ObjectValidator<TSchema>` slots are still structurally compatible with the typed factory return.
|
|
32
|
+
|
|
33
|
+
## What this means for app code
|
|
34
|
+
|
|
35
|
+
You write the schema once, you use it anywhere a `StandardSchemaV1<T>` is expected, you never cast:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { v, validate, type Infer } from "@warlock.js/seal";
|
|
39
|
+
import { ai } from "@warlock.js/ai";
|
|
40
|
+
|
|
41
|
+
const userSchema = v.object({
|
|
42
|
+
email: v.string().email(),
|
|
43
|
+
age: v.int().optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
type User = Infer<typeof userSchema>;
|
|
47
|
+
|
|
48
|
+
// ✅ ai-side slot — no cast
|
|
49
|
+
ai.tool({
|
|
50
|
+
name: "create_user",
|
|
51
|
+
description: "Create a new user",
|
|
52
|
+
input: userSchema,
|
|
53
|
+
execute: async input => createUser(input),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ✅ supervisor output — no cast
|
|
57
|
+
ai.supervisor({
|
|
58
|
+
name: "user-flow",
|
|
59
|
+
output: userSchema,
|
|
60
|
+
// ...
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ✅ direct validation — call ~standard
|
|
64
|
+
const result = await userSchema["~standard"].validate(rawData);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## `Infer<typeof schema>` vs hand-rolled types
|
|
68
|
+
|
|
69
|
+
Use `Infer<>`. Always:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const schema = v.object({
|
|
73
|
+
email: v.string(),
|
|
74
|
+
blocks: v.array(v.literal("text", "image")).optional(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
type Output = Infer<typeof schema>;
|
|
78
|
+
// { email: string; blocks?: ("text" | "image")[] }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Hand-rolled parallel types drift the moment a field changes — and seal's tightened typing now catches that drift at compile time. The old loose typing hid the mismatch; the bridge surfaces it.
|
|
82
|
+
|
|
83
|
+
## When the bridge "fails"
|
|
84
|
+
|
|
85
|
+
Three failure modes you'll see in real projects:
|
|
86
|
+
|
|
87
|
+
### 1. You annotated the schema with the bare class type
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// ❌ Discards the phantom intersection
|
|
91
|
+
const schema: ObjectValidator<{ email: StringValidator }> = v.object({...});
|
|
92
|
+
|
|
93
|
+
// ✅ Let inference run
|
|
94
|
+
const schema = v.object({...});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The annotation strips `& StandardSchemaV1<...>` from the type. The schema stops fitting `StandardSchemaV1<T>` slots. Fix: remove the annotation. If you need the value type, use `Infer<typeof schema>`.
|
|
98
|
+
|
|
99
|
+
### 2. The schema's inferred shape doesn't match the target type
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
ai.supervisor<MyOutput, ...>({
|
|
103
|
+
output: schema, // ❌ schema infers differently from MyOutput
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The supervisor's explicit `<MyOutput>` generic is a constraint the schema must satisfy. If they diverge, TS rightly rejects. Two fixes:
|
|
108
|
+
|
|
109
|
+
- **Drop the explicit generic** — let the supervisor infer output type from the schema. The hand-rolled type was probably documentation-only anyway.
|
|
110
|
+
- **Align them** — if the hand-rolled type is the source of truth (e.g. a domain type from elsewhere), make the schema match.
|
|
111
|
+
|
|
112
|
+
### 3. The error says `Result<unknown>` even though `Infer<>` resolves correctly
|
|
113
|
+
|
|
114
|
+
This is a TypeScript reporting quirk, not a bridge bug. When a `StandardSchemaV1<T>` slot rejects a schema, TS picks the simplest mismatch chain to report — often it falls through `BaseValidator['~standard'].validate`'s wider `Result<unknown>` declaration before reaching the factory-side narrower one. The intersection *is* there structurally, but the error message mentions `unknown`.
|
|
115
|
+
|
|
116
|
+
If you see this, ignore the `unknown` and ask: does `Infer<typeof schema>` match the slot's expected `T`? Probe with:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
type _Probe = Infer<typeof schema>;
|
|
120
|
+
const _force: { __nope: 1 } = null as unknown as _Probe;
|
|
121
|
+
// Hover the error to see the resolved shape, then compare to the slot's T.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
That's the real mismatch.
|
|
125
|
+
|
|
126
|
+
## Why not `Omit<class, "~standard"> & StandardSchemaV1<T>`?
|
|
127
|
+
|
|
128
|
+
Tempting — it would force the factory's `~standard` to fully replace the class's wider one, fixing the misleading `Result<unknown>` error message. **Don't do it.** `Omit` on a class instance type triggers the same variance trap as putting `Infer<this>` on the class itself: cascade explodes with hundreds of `ObjectValidator<{specific}>` vs `ObjectValidator<TSchema>` errors. The phantom intersection is the only shape that satisfies both ends — narrower for typed slots, structurally identical to the class for cascade's invariant generic positions.
|
|
129
|
+
|
|
130
|
+
## Why not import `@standard-schema/spec` instead of forking the types?
|
|
131
|
+
|
|
132
|
+
Seal forks the types locally. Reasons:
|
|
133
|
+
|
|
134
|
+
- Seal extends the spec with `StandardJSONSchemaV1` (JSON Schema converter on `~standard.jsonSchema`). The package doesn't have this — half-importing is the worst of both worlds.
|
|
135
|
+
- V1 spec is locked. The fork is ~70 lines and updates rarely.
|
|
136
|
+
- Avoids version-coupling pain across `@warlock.js/*` packages.
|
|
137
|
+
|
|
138
|
+
If V2 lands, re-fork. Until then the local copy is the right call.
|