@ata-project/valibot 0.1.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 (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/index.d.ts +71 -0
  4. package/index.js +317 -0
  5. package/package.json +38 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mert Can Altin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # @ata-project/valibot
2
+
3
+ Run a valibot schema on the [ata engine](https://github.com/ata-core/ata-validator).
4
+ The schema stays valibot, the answers always match valibot's own, and the
5
+ verdicts arrive in nanoseconds.
6
+
7
+ ```
8
+ npm install @ata-project/valibot
9
+ ```
10
+
11
+ valibot and ata-validator are installed alongside; valibot is a peer.
12
+
13
+ ```js
14
+ import * as v from 'valibot'
15
+ import { compile } from '@ata-project/valibot'
16
+
17
+ const user = v.object({
18
+ id: v.pipe(v.number(), v.integer(), v.minValue(1)),
19
+ name: v.pipe(v.string(), v.minLength(1)),
20
+ })
21
+
22
+ const check = compile(user)
23
+ check.isValid(data) // ata answers
24
+ check.safeParse(data) // valibot-shaped result, valibot-produced value
25
+ check.parse(data) // throws a real ValiError
26
+ check.isValidBytes(bytes) // verdict from a Buffer or JSON string
27
+ ```
28
+
29
+ ## How it stays correct
30
+
31
+ valibot ships an official JSON Schema conversion (`@valibot/to-json-schema`).
32
+ The bridge asks it for the input side with unrepresentable features ignored,
33
+ which can only make the emitted schema looser than valibot, never stricter.
34
+ Then the schema is classified by walking valibot's own tree before anything
35
+ runs:
36
+
37
+ | Mode | When | Who answers |
38
+ |---|---|---|
39
+ | `ata` | the conversion is exact | ata alone |
40
+ | `hybrid` | provably looser (transforms, checks, formats, native types) | ata's rejections are final, valibot confirms acceptances |
41
+ | `valibot` | valibot accepts what the conversion rejects (fallback, records, async) or a node is unknown | valibot |
42
+
43
+ The classification is conservative: an unrecognised node lands in `valibot`
44
+ mode, so a new valibot feature can make the bridge slower, never wrong.
45
+ `compiled.engine` tells you which mode you got, `compiled.reasons` says why.
46
+
47
+ Two valibot semantics survive nowhere in JSON Schema and are handled
48
+ explicitly rather than papered over. valibot's `number` accepts Infinity,
49
+ which JSON has no word for: schemas that constrain numbers get a scan of the
50
+ rejected value, and a rejection that might be Infinity-caused is handed to
51
+ valibot for the final word. And `v.record` runs on arrays too, so records stay
52
+ valibot's entirely.
53
+
54
+ The package is differential-tested against valibot on 10,249 generated values
55
+ across all three modes, including NaN and Infinity corners, and the whole
56
+ suite runs a second time with code generation blocked.
57
+
58
+ ## What it costs, measured
59
+
60
+ One representative API-boundary object schema (nine fields, nested arrays of
61
+ objects, picklist, nullable), interleaved medians of 7 rounds on an M-series
62
+ Mac, Node 25, valibot 1.4.2, ata-validator 1.13.2:
63
+
64
+ | | valibot `safeParse` | this package |
65
+ |---|---|---|
66
+ | accept, verdict only | 1,051 ns | **20 ns** |
67
+ | reject, verdict only | 1,117 ns | **83 ns** |
68
+ | reject, `safeParse` | 1,117 ns | **84 ns** |
69
+ | accept, `safeParse` | 1,051 ns | 1,062 ns |
70
+
71
+ The last row is by design, not a gap: an accepted value's output is valibot's
72
+ to make. Plain `v.object` strips unknown keys, defaults fill, transforms
73
+ rewrite, so `safeParse` hands every accepted value to valibot and returns
74
+ exactly what valibot returns. What this package owns is the verdict and the
75
+ rejection, and a rejected `safeParse` builds its issues only when somebody
76
+ reads them, by running valibot once at that moment.
77
+
78
+ The reject rows carry the Infinity scan described above; on schemas with no
79
+ number constraints they drop to the bare engine verdict.
80
+
81
+ With code generation blocked, the way a strict CSP or a locked-down edge
82
+ runtime blocks it: valibot stays at its usual speed, and the bridge falls back
83
+ to ata's interpreted engine at 624 ns for accepts and 187 ns for rejects,
84
+ still ahead on both.
85
+
86
+ ## Raw bytes
87
+
88
+ `isValidBytes` answers from a `Buffer`, `Uint8Array` or JSON string. On an
89
+ `engine: 'ata'` schema with no number constraints and the native engine
90
+ present, the verdict comes straight off the bytes with no `JSON.parse`; other
91
+ schemas parse first so number semantics stay exact. Bytes that are not valid
92
+ JSON return `false` rather than throwing.
93
+
94
+ ## Limitations, plainly
95
+
96
+ - Accepted values in `hybrid` mode and every value in `valibot` mode run
97
+ valibot, so those paths are valibot-speed. The win is the rejection and the
98
+ pure-schema case.
99
+ - `validate()` reports ata's errors for the schema-representable part, which
100
+ are JSON Schema errors, not valibot issues. Use `safeParse` when you need
101
+ valibot's issue shape.
102
+ - The classifier reads valibot's public schema tree and the peer range is
103
+ pinned to valibot 1; the differential suite is the tripwire for internals
104
+ moving.
105
+ - Async schemas are not supported; `safeParse` is synchronous, as in valibot.
106
+
107
+ ## Standard Schema
108
+
109
+ The compiled object implements Standard Schema V1, so anything that accepts a
110
+ standard schema runs the fast path without knowing either library.
111
+
112
+ ## License
113
+
114
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ import type { BaseIssue, GenericSchema, InferInput, InferOutput, ValiError } from 'valibot'
2
+
3
+ /** How a schema was classified, and why. */
4
+ export interface Analysis {
5
+ /**
6
+ * 'ata': the JSON Schema conversion is exact and ata answers alone.
7
+ * 'hybrid': the conversion is provably looser (transforms, checks, formats,
8
+ * native types), so ata's rejections are final and valibot confirms the
9
+ * acceptances.
10
+ * 'valibot': a feature makes valibot accept what the converted schema
11
+ * rejects (fallback, records over arrays, async) or a node is unknown;
12
+ * valibot answers.
13
+ */
14
+ mode: 'ata' | 'hybrid' | 'valibot'
15
+ /** The nodes that forced the mode, first eight. */
16
+ reasons: string[]
17
+ /** Whether parsing can return a value that differs from the input. */
18
+ producesValue: boolean
19
+ }
20
+
21
+ export type SafeParseResult<S extends GenericSchema> =
22
+ | { typed: true; success: true; output: InferOutput<S>; issues?: undefined }
23
+ | { typed: boolean; success: false; output: unknown; issues: [BaseIssue<unknown>, ...BaseIssue<unknown>[]] }
24
+
25
+ export interface CompiledValibot<S extends GenericSchema> {
26
+ /** The verdict, at ata speed where the classification allows it. */
27
+ isValid(data: unknown): boolean
28
+ /**
29
+ * Verdict on raw bytes or a JSON string. An engine:'ata' schema with no
30
+ * number constraints is decided without JSON.parse when the native engine
31
+ * is present; other schemas and pure-JS installs parse first. Bytes that
32
+ * are not JSON return false.
33
+ */
34
+ isValidBytes(input: Uint8Array | string): boolean
35
+ /** valibot-shaped result. Accepted values run valibot, so `output` is
36
+ * exactly what valibot returns (unknown keys stripped, defaults filled,
37
+ * transforms applied). Rejections are decided by ata; the issues are built
38
+ * on first read. */
39
+ safeParse(data: unknown): SafeParseResult<S>
40
+ /** Like safeParse but throwing the ValiError. */
41
+ parse(data: unknown): InferOutput<S>
42
+ /** ata's error report for the schema-representable part; valibot's issues
43
+ * where only valibot knows why. */
44
+ validate(data: unknown): { valid: true; data: unknown } | { valid: false; errors: unknown[] }
45
+ /** The emitted JSON Schema, or null in valibot mode. */
46
+ schema: object | null
47
+ /** The original valibot schema. */
48
+ valibotSchema: S
49
+ /** Which mode the classification chose. */
50
+ engine: 'ata' | 'hybrid' | 'valibot'
51
+ /** Why, first eight reasons. */
52
+ reasons: string[]
53
+ /** Standard Schema V1. */
54
+ '~standard': {
55
+ version: 1
56
+ vendor: 'ata-valibot'
57
+ validate(value: unknown):
58
+ | { value: InferOutput<S> }
59
+ | { issues: Array<{ message: string; path?: Array<PropertyKey> }> }
60
+ }
61
+ }
62
+
63
+ export interface CompileOptions {
64
+ /** Options forwarded to the ata Validator (formats stay unasserted). */
65
+ validator?: object
66
+ }
67
+
68
+ export declare function compile<S extends GenericSchema>(schema: S, opts?: CompileOptions): CompiledValibot<S>
69
+ export declare function analyze(schema: unknown): Analysis
70
+ export declare function toJSONSchema(schema: GenericSchema): object
71
+ export type { InferInput, InferOutput, ValiError }
package/index.js ADDED
@@ -0,0 +1,317 @@
1
+ 'use strict'
2
+
3
+ // Run a valibot schema on the ata engine.
4
+ //
5
+ // const check = compile(schema)
6
+ // check.isValid(data) // ata answers
7
+ // check.safeParse(data) // valibot-shaped result, valibot value
8
+ //
9
+ // valibot ships an official JSON Schema conversion (@valibot/to-json-schema),
10
+ // and ata is a JSON Schema engine. The conversion is asked for the input side
11
+ // (`typeMode: 'input'`) with unrepresentable features ignored, which can only
12
+ // make the emitted schema looser than valibot, never stricter. That gives the
13
+ // same contract as the zod bridge: a schema classified `ata` is exact and the
14
+ // engine answers alone; a schema with residue (transforms, checks, formats,
15
+ // native types) runs in `hybrid` mode, where ata's rejections are final and
16
+ // valibot confirms the acceptances; anything that widens acceptance beyond
17
+ // the emitted schema (fallback) or that the classifier has never seen hands
18
+ // the whole schema to valibot. Unknown nodes land in `valibot` mode, so a new
19
+ // valibot feature can make the bridge slower, never wrong.
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // classification
23
+
24
+ // Schema node types whose emitted schema equals valibot's acceptance, given
25
+ // their children do.
26
+ const EXACT = new Set([
27
+ 'string', 'number', 'boolean', 'null', 'literal', 'picklist', 'enum',
28
+ 'object', 'strict_object', 'loose_object', 'array', 'tuple', 'union',
29
+ 'variant', 'intersect', 'optional', 'nullable', 'nullish',
30
+ 'exact_optional', 'any', 'unknown', 'lazy',
31
+ ])
32
+
33
+ // Schema node types JSON Schema cannot express: the emitted input schema is
34
+ // `{}` for them, strictly looser, so ata's rejections stay sound and valibot
35
+ // owns the acceptance.
36
+ const RESIDUE = new Set([
37
+ 'date', 'bigint', 'blob', 'file', 'map', 'set', 'symbol', 'undefined',
38
+ 'void', 'nan', 'promise', 'function', 'instance', 'custom', 'never',
39
+ ])
40
+
41
+ // Pipe validations the converter emits as structural JSON Schema keywords
42
+ // with the same semantics ata checks. Formats (email, url, uuid and friends)
43
+ // are deliberately NOT here: ata's format checks are not guaranteed to match
44
+ // valibot's character for character, so the engine is compiled with format
45
+ // assertion off and every format action stays residue for valibot to confirm.
46
+ const EXACT_ACTIONS = new Set([
47
+ 'min_length', 'max_length', 'length', 'min_value', 'max_value',
48
+ 'multiple_of', 'integer', 'regex', 'min_entries', 'max_entries',
49
+ ])
50
+
51
+ const CHILD_LISTS = ['options', 'items', 'pipe']
52
+ const CHILD_NODES = ['item', 'wrapped', 'key', 'value', 'rest']
53
+
54
+ function analyze (schema) {
55
+ const seen = new Set()
56
+ const reasons = []
57
+ let mode = 'ata'
58
+ let producesValue = false
59
+
60
+ const escalate = (to, why) => {
61
+ if (to === 'valibot') mode = 'valibot'
62
+ else if (to === 'hybrid' && mode === 'ata') mode = 'hybrid'
63
+ if (reasons.length < 8) reasons.push(why)
64
+ }
65
+
66
+ const walk = (node) => {
67
+ if (!node || typeof node !== 'object' || seen.has(node)) return
68
+ seen.add(node)
69
+ if (node.kind !== 'schema') { escalate('valibot', 'node without a schema kind'); return }
70
+ const type = node.type
71
+
72
+ if (node.async) escalate('valibot', 'async schema at ' + type)
73
+ // v.fallback() swallows failures and returns a value, so valibot accepts
74
+ // input the emitted schema turns away. A fast rejection would be a wrong
75
+ // rejection.
76
+ if (node.fallback !== undefined) { escalate('valibot', 'fallback at ' + type); producesValue = true }
77
+ if (node.default !== undefined) producesValue = true
78
+
79
+ // valibot's record runs on anything object-typed, arrays included, while
80
+ // the emitted schema's `object` excludes them; a fast rejection of an
81
+ // array would be a wrong rejection, so records stay valibot's.
82
+ if (type === 'record') escalate('valibot', 'record accepts arrays')
83
+ else if (RESIDUE.has(type)) escalate('hybrid', type)
84
+ else if (!EXACT.has(type)) escalate('valibot', 'unrecognised node ' + type)
85
+
86
+ if (Array.isArray(node.pipe)) {
87
+ // pipe[0] is the base schema and the only stage the input-side
88
+ // conversion emits beyond whitelisted validations. Everything after it
89
+ // can only tighten what valibot accepts, so residue stays sound.
90
+ for (let i = 1; i < node.pipe.length; i++) {
91
+ const item = node.pipe[i]
92
+ if (!item || typeof item !== 'object') continue
93
+ if (item.kind === 'metadata') continue
94
+ if (item.kind === 'validation') {
95
+ if (!EXACT_ACTIONS.has(item.type)) escalate('hybrid', item.type + ' at ' + type)
96
+ } else if (item.kind === 'transformation') {
97
+ escalate('hybrid', item.type + ' at ' + type)
98
+ producesValue = true
99
+ } else if (item.kind === 'schema') {
100
+ escalate('hybrid', 'piped schema at ' + type)
101
+ producesValue = true
102
+ } else {
103
+ escalate('valibot', 'unrecognised pipe item ' + String(item.kind))
104
+ }
105
+ }
106
+ }
107
+
108
+ if (node.entries) for (const k of Object.keys(node.entries)) walk(node.entries[k])
109
+ for (const k of CHILD_NODES) if (node[k] && typeof node[k] === 'object') walk(node[k])
110
+ for (const k of CHILD_LISTS) {
111
+ if (k === 'pipe') continue
112
+ if (Array.isArray(node[k])) for (const child of node[k]) walk(child)
113
+ }
114
+ if (Array.isArray(node.pipe) && node.pipe[0] && node.pipe[0] !== node) walk(node.pipe[0])
115
+ if (typeof node.getter === 'function') {
116
+ try { walk(node.getter(undefined)) } catch { escalate('valibot', 'lazy getter threw') }
117
+ }
118
+ }
119
+
120
+ walk(schema)
121
+ return { mode, reasons, producesValue }
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // compile
126
+
127
+ function requireValibot () {
128
+ // Resolved lazily from the peer so this package never pins its own copy.
129
+ return require('valibot')
130
+ }
131
+
132
+ function toJSONSchema (schema) {
133
+ const { toJsonSchema } = require('@valibot/to-json-schema')
134
+ return toJsonSchema(schema, { typeMode: 'input', errorMode: 'ignore' })
135
+ }
136
+
137
+ // One semantic gap survives the conversion: valibot's `number` accepts
138
+ // Infinity and -Infinity (it only turns away NaN), while ata follows JSON,
139
+ // where no such values exist. Values holding them can only be built in
140
+ // JavaScript, never parsed from JSON, so the engine's verdict stands unless
141
+ // the schema constrains numbers somewhere AND the rejected value actually
142
+ // carries a non-finite number, in which case valibot gets the final word.
143
+ function schemaChecksNumbers (node, seen) {
144
+ if (!node || typeof node !== 'object') return false
145
+ if (seen.has(node)) return false
146
+ seen.add(node)
147
+ if (node.type === 'number' || (Array.isArray(node.type) && node.type.includes('number'))) return true
148
+ for (const k of Object.keys(node)) {
149
+ const child = node[k]
150
+ if (Array.isArray(child)) {
151
+ for (const it of child) if (schemaChecksNumbers(it, seen)) return true
152
+ } else if (schemaChecksNumbers(child, seen)) return true
153
+ }
154
+ return false
155
+ }
156
+
157
+ // Depth-capped so cyclic input cannot loop: past the cap the scan reports
158
+ // "might", and the delegate below decides inside a try/catch.
159
+ function hasNonFinite (value, depth) {
160
+ if (typeof value === 'number') return value === Infinity || value === -Infinity
161
+ if (value === null || typeof value !== 'object') return false
162
+ if (depth > 256) return true
163
+ if (Array.isArray(value)) {
164
+ for (let i = 0; i < value.length; i++) if (hasNonFinite(value[i], depth + 1)) return true
165
+ return false
166
+ }
167
+ for (const k in value) if (hasNonFinite(value[k], depth + 1)) return true
168
+ return false
169
+ }
170
+
171
+ function confirmNonFinite (v, schema, d) {
172
+ try {
173
+ return v.safeParse(schema, d).success
174
+ } catch {
175
+ // valibot could not decide (cyclic input past the scan cap); the engine's
176
+ // rejection stands.
177
+ return false
178
+ }
179
+ }
180
+
181
+ // A rejection whose issues are built on first read, by running valibot once
182
+ // at that moment. The getter lives on the prototype: an object-literal
183
+ // accessor per rejection costs about a hundred nanoseconds, which is the
184
+ // whole budget of the fast path. The classifier proves ata only rejects what
185
+ // valibot rejects; were that ever broken, the getter hands back what ata saw
186
+ // rather than nothing, and the differential suite is the place that fails.
187
+ class LazyRejection {
188
+ constructor (schema, engine, data) {
189
+ this.typed = false
190
+ this.success = false
191
+ this.output = data
192
+ this._schema = schema
193
+ this._engine = engine
194
+ this._issues = null
195
+ }
196
+ }
197
+ Object.defineProperty(LazyRejection.prototype, 'issues', {
198
+ enumerable: true,
199
+ configurable: true,
200
+ get () {
201
+ if (this._issues === null) {
202
+ const v = requireValibot()
203
+ const r = v.safeParse(this._schema, this.output)
204
+ this._issues = r.success
205
+ ? this._engine.validate(this.output).errors.map((e) => ({
206
+ kind: 'schema', type: 'ata', input: this.output, expected: null,
207
+ received: 'unknown', message: e.message, path: undefined,
208
+ }))
209
+ : r.issues
210
+ }
211
+ return this._issues
212
+ },
213
+ })
214
+
215
+ function compile (schema, opts) {
216
+ const options = opts || {}
217
+ const v = requireValibot()
218
+ const analysis = analyze(schema)
219
+ const jsonSchema = analysis.mode === 'valibot' ? null : toJSONSchema(schema)
220
+ const engine = jsonSchema
221
+ ? new (require('ata-validator').Validator)(jsonSchema, { assertFormat: false, ...options.validator })
222
+ : null
223
+ const rawFast = engine ? (d) => engine.isValidObject(d) : null
224
+ // A rejection is final unless the value carries Infinity, which valibot
225
+ // accepts where JSON has no word for it. The escape only exists for
226
+ // schemas that constrain numbers at all, and it only ever runs on the
227
+ // rejected path, so JSON-borne data never pays for it.
228
+ const numeric = engine ? schemaChecksNumbers(jsonSchema, new Set()) : false
229
+ const fast = !rawFast ? null : !numeric ? rawFast
230
+ : (d) => rawFast(d) || (hasNonFinite(d, 0) && confirmNonFinite(v, schema, d))
231
+
232
+ let isValid
233
+ if (analysis.mode === 'ata') {
234
+ isValid = fast
235
+ } else if (analysis.mode === 'hybrid') {
236
+ // ata turning a value down is final; ata letting it through hands it to
237
+ // valibot for the checks and native types the JSON Schema cannot carry.
238
+ isValid = (d) => fast(d) && v.safeParse(schema, d).success
239
+ } else {
240
+ isValid = (d) => v.safeParse(schema, d).success
241
+ }
242
+
243
+ // The parsed value is valibot's to make: plain v.object strips unknown
244
+ // keys, defaults fill, transforms rewrite, so an accepted value always runs
245
+ // valibot and comes back exactly as valibot would return it. What ata owns
246
+ // is the rejection: it is decided at ata speed, and the issues are built
247
+ // only if somebody reads them, by running valibot once at that moment.
248
+ const safeParse = (d) => {
249
+ if (fast && !fast(d)) return new LazyRejection(schema, engine, d)
250
+ return v.safeParse(schema, d)
251
+ }
252
+
253
+ // Raw bytes: an engine:'ata' schema is decided without JSON.parse, straight
254
+ // off the buffer by the native walker when it is present. The other modes
255
+ // need the materialized value for valibot, and so does a pure-JS install,
256
+ // so they parse and take the object path. Bytes that are not JSON are a
257
+ // rejection, not an exception.
258
+ let isValidBytes
259
+ if (analysis.mode === 'ata' && engine && !numeric && typeof engine.isValid === 'function') {
260
+ isValidBytes = (input) => engine.isValid(input)
261
+ } else {
262
+ const td = new TextDecoder()
263
+ isValidBytes = (input) => {
264
+ let value
265
+ try {
266
+ value = JSON.parse(typeof input === 'string' ? input : td.decode(input))
267
+ } catch {
268
+ return false
269
+ }
270
+ return isValid(value)
271
+ }
272
+ }
273
+
274
+ // ata's error report for the schema-representable part; valibot's issues
275
+ // where only valibot knows why.
276
+ const validate = (d) => {
277
+ if (engine) {
278
+ const r = engine.validate(d)
279
+ if (!r.valid) return { valid: false, errors: r.errors }
280
+ }
281
+ if (analysis.mode !== 'ata') {
282
+ const vr = v.safeParse(schema, d)
283
+ if (!vr.success) return { valid: false, errors: vr.issues }
284
+ }
285
+ return { valid: true, data: d }
286
+ }
287
+
288
+ const compiled = {
289
+ isValid,
290
+ isValidBytes,
291
+ safeParse,
292
+ parse: (d) => {
293
+ const r = safeParse(d)
294
+ if (r.success) return r.output
295
+ throw new v.ValiError(r.issues)
296
+ },
297
+ validate,
298
+ schema: jsonSchema,
299
+ valibotSchema: schema,
300
+ engine: analysis.mode,
301
+ reasons: analysis.reasons,
302
+ }
303
+
304
+ compiled['~standard'] = {
305
+ version: 1,
306
+ vendor: 'ata-valibot',
307
+ validate (value) {
308
+ const r = safeParse(value)
309
+ if (r.success) return { value: r.output }
310
+ return { issues: r.issues.map((i) => ({ message: i.message, path: i.path && i.path.map((p) => p.key) })) }
311
+ },
312
+ }
313
+
314
+ return compiled
315
+ }
316
+
317
+ module.exports = { compile, analyze, toJSONSchema }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@ata-project/valibot",
3
+ "version": "0.1.0",
4
+ "description": "Run valibot schemas on the ata engine: same answers, verdicts in nanoseconds",
5
+ "license": "MIT",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/ata-core/ata-valibot.git"
20
+ },
21
+ "scripts": {
22
+ "test": "node test.js && node --disallow-code-generation-from-strings test.js && npm run test:types",
23
+ "bench": "node bench.mjs",
24
+ "test:types": "tsc --noEmit -p tsconfig.json"
25
+ },
26
+ "dependencies": {
27
+ "@valibot/to-json-schema": "^1.3.0",
28
+ "ata-validator": "^1.13.2"
29
+ },
30
+ "peerDependencies": {
31
+ "valibot": "^1.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^26.5.0",
35
+ "typescript": "^5.6.0",
36
+ "valibot": "^1.0.0"
37
+ }
38
+ }