chore: migrate from ts-matches to zod across all TypeScript packages

This commit is contained in:
Aiden McClelland
2026-02-20 16:24:35 -07:00
parent c7a4f0f9cb
commit 31352a72c3
40 changed files with 963 additions and 1891 deletions

View File

@@ -2,7 +2,7 @@ import { ValueSpec } from '../inputSpecTypes'
import { Value } from './value'
import { _ } from '../../../util'
import { Effects } from '../../../Effects'
import { Parser, object } from 'ts-matches'
import { z } from 'zod'
import { DeepPartial } from '../../../types'
import { InputSpecTools, createInputSpecTools } from './inputSpecTools'
@@ -96,7 +96,7 @@ export class InputSpec<
private readonly spec: {
[K in keyof Type]: Value<Type[K]>
},
public readonly validator: Parser<unknown, StaticValidatedAs>,
public readonly validator: z.ZodType<StaticValidatedAs>,
) {}
public _TYPE: Type = null as any as Type
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
@@ -104,13 +104,13 @@ export class InputSpec<
spec: {
[K in keyof Type]: ValueSpec
}
validator: Parser<unknown, Type>
validator: z.ZodType<Type>
}> {
const answer = {} as {
[K in keyof Type]: ValueSpec
}
const validator = {} as {
[K in keyof Type]: Parser<unknown, any>
[K in keyof Type]: z.ZodType<any>
}
for (const k in this.spec) {
const built = await this.spec[k].build(options as any)
@@ -119,7 +119,7 @@ export class InputSpec<
}
return {
spec: answer,
validator: object(validator) as any,
validator: z.object(validator) as any,
}
}
@@ -135,7 +135,7 @@ export class InputSpec<
const value =
build instanceof Function ? build(createInputSpecTools<Type>()) : build
const newSpec = { ...this.spec, [key]: value } as any
const newValidator = object(
const newValidator = z.object(
Object.fromEntries(
Object.entries(newSpec).map(([k, v]) => [
k,
@@ -163,7 +163,7 @@ export class InputSpec<
const addedValues =
build instanceof Function ? build(createInputSpecTools<Type>()) : build
const newSpec = { ...this.spec, ...addedValues } as any
const newValidator = object(
const newValidator = z.object(
Object.fromEntries(
Object.entries(newSpec).map(([k, v]) => [
k,
@@ -175,7 +175,7 @@ export class InputSpec<
}
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec) {
const validator = object(
const validator = z.object(
Object.fromEntries(
Object.entries(spec).map(([k, v]) => [k, v.validator]),
),

View File

@@ -9,7 +9,7 @@ import {
ValueSpecText,
} from '../inputSpecTypes'
import { DefaultString } from '../inputSpecTypes'
import { Parser } from 'ts-matches'
import { z } from 'zod'
import { ListValueSpecText } from '../inputSpecTypes'
export interface InputSpecTools<OuterType> {
@@ -224,7 +224,7 @@ export interface BoundValue<OuterType> {
},
OuterType
>,
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>,
): Value<
UnionRes<VariantValues>,
UnionResStaticValidatedAs<StaticVariantValues>,
@@ -232,7 +232,7 @@ export interface BoundValue<OuterType> {
>
dynamicHidden<T>(
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
getParser: LazyBuild<z.ZodType<T>, OuterType>,
): Value<T, T, OuterType>
}

View File

@@ -7,7 +7,7 @@ import {
ValueSpecList,
ValueSpecListOf,
} from '../inputSpecTypes'
import { Parser, arrayOf, string } from 'ts-matches'
import { z } from 'zod'
export class List<
Type extends StaticValidatedAs,
@@ -18,11 +18,11 @@ export class List<
public build: LazyBuild<
{
spec: ValueSpecList
validator: Parser<unknown, Type>
validator: z.ZodType<Type>
},
OuterType
>,
public readonly validator: Parser<unknown, StaticValidatedAs>,
public readonly validator: z.ZodType<StaticValidatedAs>,
) {}
readonly _TYPE: Type = null as any
@@ -69,7 +69,7 @@ export class List<
generate?: null | RandomString
},
) {
const validator = arrayOf(string)
const validator = z.array(z.string())
return new List<string[]>(() => {
const spec = {
type: 'text' as const,
@@ -120,7 +120,7 @@ export class List<
OuterType
>,
) {
const validator = arrayOf(string)
const validator = z.array(z.string())
return new List<string[], string[], OuterType>(async (options) => {
const { spec: aSpec, ...a } = await getA(options)
const spec = {
@@ -193,8 +193,8 @@ export class List<
disabled: false,
...value,
},
validator: arrayOf(built.validator),
validator: z.array(built.validator),
}
}, arrayOf(aSpec.spec.validator))
}, z.array(aSpec.spec.validator))
}
}

View File

@@ -12,37 +12,27 @@ import {
} from '../inputSpecTypes'
import { DefaultString } from '../inputSpecTypes'
import { _, once } from '../../../util'
import {
Parser,
any,
anyOf,
arrayOf,
boolean,
literal,
literals,
number,
object,
string,
} from 'ts-matches'
import { z } from 'zod'
import { DeepPartial } from '../../../types'
export const fileInfoParser = object({
path: string,
commitment: object({ hash: string, size: number }),
export const fileInfoParser = z.object({
path: z.string(),
commitment: z.object({ hash: z.string(), size: z.number() }),
})
export type FileInfo = typeof fileInfoParser._TYPE
export type FileInfo = z.infer<typeof fileInfoParser>
export type AsRequired<T, Required extends boolean> = Required extends true
? T
: T | null
const testForAsRequiredParser = once(
() => object({ required: literal(true) }).test,
() => (v: unknown) =>
z.object({ required: z.literal(true) }).safeParse(v).success,
)
function asRequiredParser<Type, Input extends { required: boolean }>(
parser: Parser<unknown, Type>,
parser: z.ZodType<Type>,
input: Input,
): Parser<unknown, AsRequired<Type, Input['required']>> {
): z.ZodType<AsRequired<Type, Input['required']>> {
if (testForAsRequiredParser()(input)) return parser as any
return parser.nullable() as any
}
@@ -56,11 +46,11 @@ export class Value<
public build: LazyBuild<
{
spec: ValueSpec
validator: Parser<unknown, Type>
validator: z.ZodType<Type>
},
OuterType
>,
public readonly validator: Parser<unknown, StaticValidatedAs>,
public readonly validator: z.ZodType<StaticValidatedAs>,
) {}
public _TYPE: Type = null as any as Type
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
@@ -93,7 +83,7 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = boolean
const validator = z.boolean()
return new Value<boolean>(
async () => ({
spec: {
@@ -121,7 +111,7 @@ export class Value<
OuterType
>,
) {
const validator = boolean
const validator = z.boolean()
return new Value<boolean, boolean, OuterType>(
async (options) => ({
spec: {
@@ -212,7 +202,7 @@ export class Value<
*/
generate?: RandomString | null
}) {
const validator = asRequiredParser(string, a)
const validator = asRequiredParser(z.string(), a)
return new Value<AsRequired<string, Required>>(
async () => ({
spec: {
@@ -274,10 +264,10 @@ export class Value<
generate: a.generate ?? null,
...a,
},
validator: asRequiredParser(string, a),
validator: asRequiredParser(z.string(), a),
}
},
string.nullable(),
z.string().nullable(),
)
}
/**
@@ -336,7 +326,7 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = asRequiredParser(string, a)
const validator = asRequiredParser(z.string(), a)
return new Value<AsRequired<string, Required>>(async () => {
const built: ValueSpecTextarea = {
description: null,
@@ -392,10 +382,10 @@ export class Value<
immutable: false,
...a,
},
validator: asRequiredParser(string, a),
validator: asRequiredParser(z.string(), a),
}
},
string.nullable(),
z.string().nullable(),
)
}
/**
@@ -456,7 +446,7 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = asRequiredParser(number, a)
const validator = asRequiredParser(z.number(), a)
return new Value<AsRequired<number, Required>>(
() => ({
spec: {
@@ -513,10 +503,10 @@ export class Value<
immutable: false,
...a,
},
validator: asRequiredParser(number, a),
validator: asRequiredParser(z.number(), a),
}
},
number.nullable(),
z.number().nullable(),
)
}
/**
@@ -555,7 +545,7 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = asRequiredParser(string, a)
const validator = asRequiredParser(z.string(), a)
return new Value<AsRequired<string, Required>>(
() => ({
spec: {
@@ -597,10 +587,10 @@ export class Value<
immutable: false,
...a,
},
validator: asRequiredParser(string, a),
validator: asRequiredParser(z.string(), a),
}
},
string.nullable(),
z.string().nullable(),
)
}
/**
@@ -649,7 +639,7 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = asRequiredParser(string, a)
const validator = asRequiredParser(z.string(), a)
return new Value<AsRequired<string, Required>>(
() => ({
spec: {
@@ -700,10 +690,10 @@ export class Value<
immutable: false,
...a,
},
validator: asRequiredParser(string, a),
validator: asRequiredParser(z.string(), a),
}
},
string.nullable(),
z.string().nullable(),
)
}
/**
@@ -757,8 +747,12 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = anyOf(
...Object.keys(a.values).map((x: keyof Values & string) => literal(x)),
const validator = z.union(
Object.keys(a.values).map((x: keyof Values & string) => z.literal(x)) as [
z.ZodLiteral<string>,
z.ZodLiteral<string>,
...z.ZodLiteral<string>[],
],
)
return new Value<keyof Values & string>(
() => ({
@@ -803,14 +797,18 @@ export class Value<
immutable: false,
...a,
},
validator: anyOf(
...Object.keys(a.values).map((x: keyof Values & string) =>
literal(x),
),
validator: z.union(
Object.keys(a.values).map((x: keyof Values & string) =>
z.literal(x),
) as [
z.ZodLiteral<string>,
z.ZodLiteral<string>,
...z.ZodLiteral<string>[],
],
),
}
},
string,
z.string(),
)
}
/**
@@ -865,8 +863,14 @@ export class Value<
*/
immutable?: boolean
}) {
const validator = arrayOf(
literals(...(Object.keys(a.values) as any as [keyof Values & string])),
const validator = z.array(
z.union(
Object.keys(a.values).map((x) => z.literal(x)) as [
z.ZodLiteral<string>,
z.ZodLiteral<string>,
...z.ZodLiteral<string>[],
],
),
)
return new Value<(keyof Values & string)[]>(
() => ({
@@ -920,13 +924,17 @@ export class Value<
immutable: false,
...a,
},
validator: arrayOf(
literals(
...(Object.keys(a.values) as any as [keyof Values & string]),
validator: z.array(
z.union(
Object.keys(a.values).map((x) => z.literal(x)) as [
z.ZodLiteral<string>,
z.ZodLiteral<string>,
...z.ZodLiteral<string>[],
],
),
),
}
}, arrayOf(string))
}, z.array(z.string()))
}
/**
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
@@ -1077,7 +1085,7 @@ export class Value<
}) {
return new Value<
typeof a.variants._TYPE,
typeof a.variants.validator._TYPE
typeof a.variants.validator._output
>(async (options) => {
const built = await a.variants.build(options as any)
return {
@@ -1136,7 +1144,7 @@ export class Value<
},
OuterType
>,
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>,
): Value<
UnionRes<VariantValues>,
UnionResStaticValidatedAs<StaticVariantValues>,
@@ -1162,11 +1170,11 @@ export class Value<
},
OuterType
>,
validator: Parser<unknown, unknown> = any,
validator: z.ZodType<unknown> = z.any(),
) {
return new Value<
UnionRes<VariantValues>,
typeof validator._TYPE,
z.infer<typeof validator>,
OuterType
>(async (options) => {
const newValues = await getA(options)
@@ -1259,9 +1267,9 @@ export class Value<
* ```
*/
static hidden<T>(): Value<T>
static hidden<T>(parser: Parser<unknown, T>): Value<T>
static hidden<T>(parser: Parser<unknown, T> = any) {
return new Value<T, typeof parser._TYPE>(async () => {
static hidden<T>(parser: z.ZodType<T>): Value<T>
static hidden<T>(parser: z.ZodType<T> = z.any()) {
return new Value<T, z.infer<typeof parser>>(async () => {
return {
spec: {
type: 'hidden' as const,
@@ -1279,7 +1287,7 @@ export class Value<
* ```
*/
static dynamicHidden<T, OuterType = unknown>(
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
getParser: LazyBuild<z.ZodType<T>, OuterType>,
) {
return new Value<T, T, OuterType>(async (options) => {
const validator = await getParser(options)
@@ -1289,7 +1297,7 @@ export class Value<
} as ValueSpecHidden,
validator,
}
}, any)
}, z.any())
}
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType> {
@@ -1297,8 +1305,8 @@ export class Value<
const built = await this.build(options)
return {
spec: built.spec,
validator: built.validator.map(fn),
validator: built.validator.transform(fn),
}
}, this.validator.map(fn))
}, this.validator.transform(fn))
}
}

View File

@@ -6,7 +6,7 @@ import {
ExtractInputSpecType,
ExtractInputSpecStaticValidatedAs,
} from './inputSpec'
import { Parser, any, anyOf, literal, object } from 'ts-matches'
import { z } from 'zod'
export type UnionRes<
VariantValues extends {
@@ -109,12 +109,11 @@ export class Variants<
public build: LazyBuild<
{
spec: ValueSpecUnion['variants']
validator: Parser<unknown, UnionRes<VariantValues>>
validator: z.ZodType<UnionRes<VariantValues>>
},
OuterType
>,
public readonly validator: Parser<
unknown,
public readonly validator: z.ZodType<
UnionResStaticValidatedAs<VariantValues>
>,
) {}
@@ -128,8 +127,7 @@ export class Variants<
},
>(a: VariantValues) {
const staticValidators = {} as {
[K in keyof VariantValues]: Parser<
unknown,
[K in keyof VariantValues]: z.ZodType<
ExtractInputSpecStaticValidatedAs<VariantValues[K]['spec']>
>
}
@@ -137,16 +135,20 @@ export class Variants<
const value = a[key]
staticValidators[key] = value.spec.validator
}
const other = object(
Object.fromEntries(
Object.entries(staticValidators).map(([k, v]) => [k, any.optional()]),
),
).optional()
const other = z
.object(
Object.fromEntries(
Object.entries(staticValidators).map(([k, v]) => [
k,
z.any().optional(),
]),
),
)
.optional()
return new Variants<VariantValues>(
async (options) => {
const validators = {} as {
[K in keyof VariantValues]: Parser<
unknown,
[K in keyof VariantValues]: z.ZodType<
ExtractInputSpecType<VariantValues[K]['spec']>
>
}
@@ -165,32 +167,37 @@ export class Variants<
}
validators[key] = built.validator
}
const other = object(
Object.fromEntries(
Object.entries(validators).map(([k, v]) => [k, any.optional()]),
),
).optional()
const other = z
.object(
Object.fromEntries(
Object.entries(validators).map(([k, v]) => [
k,
z.any().optional(),
]),
),
)
.optional()
return {
spec: variants,
validator: anyOf(
...Object.entries(validators).map(([k, v]) =>
object({
selection: literal(k),
validator: z.union(
Object.entries(validators).map(([k, v]) =>
z.object({
selection: z.literal(k),
value: v,
other,
}),
),
) as [z.ZodObject<any>, z.ZodObject<any>, ...z.ZodObject<any>[]],
) as any,
}
},
anyOf(
...Object.entries(staticValidators).map(([k, v]) =>
object({
selection: literal(k),
z.union(
Object.entries(staticValidators).map(([k, v]) =>
z.object({
selection: z.literal(k),
value: v,
other,
}),
),
) as [z.ZodObject<any>, z.ZodObject<any>, ...z.ZodObject<any>[]],
) as any,
)
}

View File

@@ -3,7 +3,7 @@ import { ExtractInputSpecType } from './input/builder/inputSpec'
import * as T from '../types'
import { once } from '../util'
import { InitScript } from '../inits'
import { Parser } from 'ts-matches'
import { z } from 'zod'
type MaybeInputSpec<Type> = {} extends Type ? null : InputSpec<Type>
export type Run<A extends Record<string, any>> = (options: {
@@ -52,7 +52,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
readonly _INPUT: Type = null as any as Type
private prevInputSpec: Record<
string,
{ spec: T.inputSpecTypes.InputSpec; validator: Parser<unknown, Type> }
{ spec: T.inputSpecTypes.InputSpec; validator: z.ZodType<Type> }
> = {}
private constructor(
readonly id: Id,
@@ -137,7 +137,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
`getActionInput has not been called for EventID ${options.effects.eventId}`,
)
}
options.input = prevInputSpec.validator.unsafeCast(options.input)
options.input = prevInputSpec.validator.parse(options.input)
spec = prevInputSpec.spec
}
return (

View File

@@ -8,6 +8,6 @@ export * as types from './types'
export * as T from './types'
export * as yaml from 'yaml'
export * as inits from './inits'
export * as matches from 'ts-matches'
export { z } from 'zod'
export * as utils from './util'

View File

@@ -1,4 +1,4 @@
import { object, string } from 'ts-matches'
import { z } from 'zod'
import { Effects } from '../Effects'
import { Origin } from './Origin'
import { AddSslOptions, BindParams } from '../osBindings'
@@ -69,9 +69,8 @@ export type BindOptionsByProtocol =
| BindOptionsByKnownProtocol
| (BindOptions & { protocol: null })
const hasStringProtocol = object({
protocol: string,
}).test
const hasStringProtocol = (v: unknown): v is { protocol: string } =>
z.object({ protocol: z.string() }).safeParse(v).success
export class MultiHost {
constructor(

View File

@@ -1,7 +1,7 @@
import { object } from 'ts-matches'
export function deepEqual(...args: unknown[]) {
const objects = args.filter(object.test)
const objects = args.filter(
(x): x is object => typeof x === 'object' && x !== null,
)
if (objects.length === 0) {
for (const x of args) if (x !== args[0]) return false
return true

View File

@@ -1,4 +1,3 @@
import { boolean } from 'ts-matches'
import { ExtendedVersion } from '../exver'
export type Vertex<VMetadata = null, EMetadata = null> = {

View File

@@ -1,8 +1,6 @@
import { arrayOf, string } from 'ts-matches'
export const splitCommand = (
command: string | [string, ...string[]],
): string[] => {
if (arrayOf(string).test(command)) return command
if (Array.isArray(command)) return command
return ['sh', '-c', command]
}

View File

@@ -13,8 +13,8 @@
"deep-equality-data-structures": "^1.5.0",
"isomorphic-fetch": "^3.0.0",
"mime": "^4.0.7",
"ts-matches": "^6.3.2",
"yaml": "^2.7.1"
"yaml": "^2.7.1",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/jest": "^29.4.0",
@@ -4626,12 +4626,6 @@
"node": ">=10"
}
},
"node_modules/ts-matches": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.3.2.tgz",
"integrity": "sha512-UhSgJymF8cLd4y0vV29qlKVCkQpUtekAaujXbQVc729FezS8HwqzepqvtjzQ3HboatIqN/Idor85O2RMwT7lIQ==",
"license": "MIT"
},
"node_modules/ts-morph": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
@@ -5006,6 +5000,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -24,11 +24,11 @@
"@iarna/toml": "^3.0.0",
"@noble/curves": "^1.8.2",
"@noble/hashes": "^1.7.2",
"deep-equality-data-structures": "^1.5.0",
"isomorphic-fetch": "^3.0.0",
"mime": "^4.0.7",
"ts-matches": "^6.3.2",
"yaml": "^2.7.1",
"deep-equality-data-structures": "^1.5.0"
"zod": "^4.3.6"
},
"prettier": {
"trailingComma": "all",