This commit is contained in:
sinclair
2025-12-24 15:44:34 +09:00
commit 13d553220c
1047 changed files with 80931 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { Errors, ValueErrorIterator, ValueError } from '../../errors/index'
import { TypeBoxError } from '../../type/error/error'
import { TSchema } from '../../type/schema/index'
import { Static } from '../../type/static/index'
import { Check } from '../check/check'
// ------------------------------------------------------------------
// AssertError
// ------------------------------------------------------------------
export class AssertError extends TypeBoxError {
readonly #iterator: ValueErrorIterator
error: ValueError | undefined
constructor(iterator: ValueErrorIterator) {
const error = iterator.First()
super(error === undefined ? 'Invalid Value' : error.message)
this.#iterator = iterator
this.error = error
}
/** Returns an iterator for each error in this value. */
public Errors(): ValueErrorIterator {
return new ValueErrorIterator(this.#Iterator())
}
*#Iterator(): IterableIterator<ValueError> {
if (this.error) yield this.error
yield* this.#iterator
}
}
// ------------------------------------------------------------------
// AssertValue
// ------------------------------------------------------------------
function AssertValue(schema: TSchema, references: TSchema[], value: unknown): unknown {
if (Check(schema, references, value)) return
throw new AssertError(Errors(schema, references, value))
}
// ------------------------------------------------------------------
// Assert
// ------------------------------------------------------------------
/** Asserts a value matches the given type or throws an `AssertError` if invalid */
export function Assert<T extends TSchema>(schema: T, references: TSchema[], value: unknown): asserts value is Static<T>
/** Asserts a value matches the given type or throws an `AssertError` if invalid */
export function Assert<T extends TSchema>(schema: T, value: unknown): asserts value is Static<T>
/** Asserts a value matches the given type or throws an `AssertError` if invalid */
export function Assert(...args: any[]): unknown {
return args.length === 3 ? AssertValue(args[0], args[1], args[2]) : AssertValue(args[0], [], args[1])
}

29
src/value/assert/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './assert'

266
src/value/cast/cast.ts Normal file
View File

@@ -0,0 +1,266 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsObject, IsArray, IsString, IsNumber, IsNull } from '../guard/index'
import { TypeBoxError } from '../../type/error/index'
import { Kind } from '../../type/symbols/index'
import { Create } from '../create/index'
import { Check } from '../check/index'
import { Clone } from '../clone/index'
import { Deref, Pushref } from '../deref/index'
import type { TSchema } from '../../type/schema/index'
import type { Static } from '../../type/static/index'
import type { TArray } from '../../type/array/index'
import type { TConstructor } from '../../type/constructor/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
import type { TNever } from '../../type/never/index'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValueCastError extends TypeBoxError {
constructor(public readonly schema: TSchema, message: string) {
super(message)
}
}
// ------------------------------------------------------------------
// The following logic assigns a score to a schema based on how well
// it matches a given value. For object types, the score is calculated
// by evaluating each property of the value against the schema's
// properties. To avoid bias towards objects with many properties,
// each property contributes equally to the total score. Properties
// that exactly match literal values receive the highest possible
// score, as literals are often used as discriminators in union types.
// ------------------------------------------------------------------
function ScoreUnion(schema: TSchema, references: TSchema[], value: any): number {
if (schema[Kind] === 'Object' && typeof value === 'object' && !IsNull(value)) {
const object = schema as TObject
const keys = Object.getOwnPropertyNames(value)
const entries = Object.entries(object.properties)
return entries.reduce((acc, [key, schema]) => {
const literal = schema[Kind] === 'Literal' && schema.const === value[key] ? 100 : 0
const checks = Check(schema, references, value[key]) ? 10 : 0
const exists = keys.includes(key) ? 1 : 0
return acc + (literal + checks + exists)
}, 0)
} else if (schema[Kind] === 'Union') {
const schemas = schema.anyOf.map((schema: TSchema) => Deref(schema, references))
const scores = schemas.map((schema: TSchema) => ScoreUnion(schema, references, value))
return Math.max(...scores)
} else {
return Check(schema, references, value) ? 1 : 0
}
}
function SelectUnion(union: TUnion, references: TSchema[], value: any): TSchema {
const schemas = union.anyOf.map((schema) => Deref(schema, references))
let [select, best] = [schemas[0], 0]
for (const schema of schemas) {
const score = ScoreUnion(schema, references, value)
if (score > best) {
select = schema
best = score
}
}
return select
}
function CastUnion(union: TUnion, references: TSchema[], value: any) {
if ('default' in union) {
return typeof value === 'function' ? union.default : Clone(union.default)
} else {
const schema = SelectUnion(union, references, value)
return Cast(schema, references, value)
}
}
// ------------------------------------------------------------------
// Default
// ------------------------------------------------------------------
function DefaultClone(schema: TSchema, references: TSchema[], value: any): any {
return Check(schema, references, value) ? Clone(value) : Create(schema, references)
}
function Default(schema: TSchema, references: TSchema[], value: any): any {
return Check(schema, references, value) ? value : Create(schema, references)
}
// ------------------------------------------------------------------
// Cast
// ------------------------------------------------------------------
function FromArray(schema: TArray, references: TSchema[], value: any): any {
if (Check(schema, references, value)) return Clone(value)
const created = IsArray(value) ? Clone(value) : Create(schema, references)
const minimum = IsNumber(schema.minItems) && created.length < schema.minItems ? [...created, ...Array.from({ length: schema.minItems - created.length }, () => null)] : created
const maximum = IsNumber(schema.maxItems) && minimum.length > schema.maxItems ? minimum.slice(0, schema.maxItems) : minimum
const casted = maximum.map((value: unknown) => Visit(schema.items, references, value))
if (schema.uniqueItems !== true) return casted
const unique = [...new Set(casted)]
if (!Check(schema, references, unique)) throw new ValueCastError(schema, 'Array cast produced invalid data due to uniqueItems constraint')
return unique
}
function FromConstructor(schema: TConstructor, references: TSchema[], value: any): any {
if (Check(schema, references, value)) return Create(schema, references)
const required = new Set(schema.returns.required || [])
const result = function () {}
for (const [key, property] of Object.entries(schema.returns.properties)) {
if (!required.has(key) && value.prototype[key] === undefined) continue
result.prototype[key] = Visit(property as TSchema, references, value.prototype[key])
}
return result
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): boolean {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
// ------------------------------------------------------------------
// Intersect
// ------------------------------------------------------------------
function IntersectAssign(correct: unknown, value: unknown): unknown {
// trust correct on mismatch | value on non-object
if ((IsObject(correct) && !IsObject(value)) || (!IsObject(correct) && IsObject(value))) return correct
if (!IsObject(correct) || !IsObject(value)) return value
return globalThis.Object.getOwnPropertyNames(correct).reduce((result, key) => {
const property = key in value ? IntersectAssign(correct[key], value[key]) : correct[key]
return { ...result, [key]: property }
}, {})
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: any): any {
if (Check(schema, references, value)) return value
const correct = Create(schema, references)
const assigned = IntersectAssign(correct, value)
return Check(schema, references, assigned) ? assigned : correct
}
function FromNever(schema: TNever, references: TSchema[], value: any): any {
throw new ValueCastError(schema, 'Never types cannot be cast')
}
function FromObject(schema: TObject, references: TSchema[], value: any): any {
if (Check(schema, references, value)) return value
if (value === null || typeof value !== 'object') return Create(schema, references)
const required = new Set(schema.required || [])
const result = {} as Record<string, any>
for (const [key, property] of Object.entries(schema.properties)) {
if (!required.has(key) && value[key] === undefined) continue
result[key] = Visit(property, references, value[key])
}
// additional schema properties
if (typeof schema.additionalProperties === 'object') {
const propertyNames = Object.getOwnPropertyNames(schema.properties)
for (const propertyName of Object.getOwnPropertyNames(value)) {
if (propertyNames.includes(propertyName)) continue
result[propertyName] = Visit(schema.additionalProperties, references, value[propertyName])
}
}
return result
}
function FromRecord(schema: TRecord, references: TSchema[], value: any): any {
if (Check(schema, references, value)) return Clone(value)
if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) return Create(schema, references)
const subschemaPropertyName = Object.getOwnPropertyNames(schema.patternProperties)[0]
const subschema = schema.patternProperties[subschemaPropertyName]
const result = {} as Record<string, any>
for (const [propKey, propValue] of Object.entries(value)) {
result[propKey] = Visit(subschema, references, propValue)
}
return result
}
function FromRef(schema: TRef, references: TSchema[], value: any): any {
return Visit(Deref(schema, references), references, value)
}
function FromThis(schema: TThis, references: TSchema[], value: any): any {
return Visit(Deref(schema, references), references, value)
}
function FromTuple(schema: TTuple, references: TSchema[], value: any): any {
if (Check(schema, references, value)) return Clone(value)
if (!IsArray(value)) return Create(schema, references)
if (schema.items === undefined) return []
return schema.items.map((schema, index) => Visit(schema, references, value[index]))
}
function FromUnion(schema: TUnion, references: TSchema[], value: any): any {
return Check(schema, references, value) ? Clone(value) : CastUnion(schema, references, value)
}
function Visit(schema: TSchema, references: TSchema[], value: any): any {
const references_ = IsString(schema.$id) ? Pushref(schema, references) : references
const schema_ = schema as any
switch (schema[Kind]) {
// --------------------------------------------------------------
// Structural
// --------------------------------------------------------------
case 'Array':
return FromArray(schema_, references_, value)
case 'Constructor':
return FromConstructor(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Never':
return FromNever(schema_, references_, value)
case 'Object':
return FromObject(schema_, references_, value)
case 'Record':
return FromRecord(schema_, references_, value)
case 'Ref':
return FromRef(schema_, references_, value)
case 'This':
return FromThis(schema_, references_, value)
case 'Tuple':
return FromTuple(schema_, references_, value)
case 'Union':
return FromUnion(schema_, references_, value)
// --------------------------------------------------------------
// DefaultClone
// --------------------------------------------------------------
case 'Date':
case 'Symbol':
case 'Uint8Array':
return DefaultClone(schema, references, value)
// --------------------------------------------------------------
// Default
// --------------------------------------------------------------
default:
return Default(schema_, references_, value)
}
}
// ------------------------------------------------------------------
// Cast
// ------------------------------------------------------------------
/** Casts a value into a given type and references. The return value will retain as much information of the original value as possible. */
export function Cast<T extends TSchema>(schema: T, references: TSchema[], value: unknown): Static<T>
/** Casts a value into a given type. The return value will retain as much information of the original value as possible. */
export function Cast<T extends TSchema>(schema: T, value: unknown): Static<T>
/** Casts a value into a given type. The return value will retain as much information of the original value as possible. */
export function Cast(...args: any[]) {
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
}

29
src/value/cast/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './cast'

513
src/value/check/check.ts Normal file
View File

@@ -0,0 +1,513 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeSystemPolicy } from '../../system/index'
import { Deref, Pushref } from '../deref/index'
import { Hash } from '../hash/index'
import { Kind } from '../../type/symbols/index'
import { KeyOfPattern } from '../../type/keyof/index'
import { ExtendsUndefinedCheck } from '../../type/extends/index'
import { TypeRegistry, FormatRegistry } from '../../type/registry/index'
import { TypeBoxError } from '../../type/error/index'
import type { TSchema } from '../../type/schema/index'
import type { TAny } from '../../type/any/index'
import type { TArgument } from '../../type/argument/index'
import type { TArray } from '../../type/array/index'
import type { TAsyncIterator } from '../../type/async-iterator/index'
import type { TBigInt } from '../../type/bigint/index'
import type { TBoolean } from '../../type/boolean/index'
import type { TDate } from '../../type/date/index'
import type { TConstructor } from '../../type/constructor/index'
import type { TFunction } from '../../type/function/index'
import type { TInteger } from '../../type/integer/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TIterator } from '../../type/iterator/index'
import type { TLiteral } from '../../type/literal/index'
import type { TImport } from '../../type/module/index'
import { Never, type TNever } from '../../type/never/index'
import type { TNot } from '../../type/not/index'
import type { TNull } from '../../type/null/index'
import type { TNumber } from '../../type/number/index'
import type { TObject } from '../../type/object/index'
import type { TPromise } from '../../type/promise/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TRegExp } from '../../type/regexp/index'
import type { TTemplateLiteral } from '../../type/template-literal/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
import type { TUnknown } from '../../type/unknown/index'
import type { Static } from '../../type/static/index'
import type { TString } from '../../type/string/index'
import type { TSymbol } from '../../type/symbol/index'
import type { TUndefined } from '../../type/undefined/index'
import type { TUint8Array } from '../../type/uint8array/index'
import type { TVoid } from '../../type/void/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsArray, IsUint8Array, IsDate, IsPromise, IsFunction, IsAsyncIterator, IsIterator, IsBoolean, IsNumber, IsBigInt, IsString, IsSymbol, IsInteger, IsNull, IsUndefined } from '../guard/index'
// ------------------------------------------------------------------
// KindGuard
// ------------------------------------------------------------------
import { IsSchema } from '../../type/guard/kind'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValueCheckUnknownTypeError extends TypeBoxError {
constructor(public readonly schema: TSchema) {
super(`Unknown type`)
}
}
// ------------------------------------------------------------------
// TypeGuards
// ------------------------------------------------------------------
function IsAnyOrUnknown(schema: TSchema) {
return schema[Kind] === 'Any' || schema[Kind] === 'Unknown'
}
// ------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------
function IsDefined<T>(value: unknown): value is T {
return value !== undefined
}
// ------------------------------------------------------------------
// Types
// ------------------------------------------------------------------
function FromAny(schema: TAny, references: TSchema[], value: any): boolean {
return true
}
function FromArgument(schema: TArgument, references: TSchema[], value: any): boolean {
return true
}
function FromArray(schema: TArray, references: TSchema[], value: any): boolean {
if (!IsArray(value)) return false
if (IsDefined<number>(schema.minItems) && !(value.length >= schema.minItems)) {
return false
}
if (IsDefined<number>(schema.maxItems) && !(value.length <= schema.maxItems)) {
return false
}
if (!value.every((value) => Visit(schema.items, references, value))) {
return false
}
// prettier-ignore
if (schema.uniqueItems === true && !((function() { const set = new Set(); for(const element of value) { const hashed = Hash(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true })())) {
return false
}
// contains
if (!(IsDefined(schema.contains) || IsNumber(schema.minContains) || IsNumber(schema.maxContains))) {
return true // exit
}
const containsSchema = IsDefined<TSchema>(schema.contains) ? schema.contains : Never()
const containsCount = value.reduce((acc: number, value) => (Visit(containsSchema, references, value) ? acc + 1 : acc), 0)
if (containsCount === 0) {
return false
}
if (IsNumber(schema.minContains) && containsCount < schema.minContains) {
return false
}
if (IsNumber(schema.maxContains) && containsCount > schema.maxContains) {
return false
}
return true
}
function FromAsyncIterator(schema: TAsyncIterator, references: TSchema[], value: any): boolean {
return IsAsyncIterator(value)
}
function FromBigInt(schema: TBigInt, references: TSchema[], value: any): boolean {
if (!IsBigInt(value)) return false
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
return false
}
if (IsDefined<bigint>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
return false
}
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
return false
}
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
return false
}
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === BigInt(0))) {
return false
}
return true
}
function FromBoolean(schema: TBoolean, references: TSchema[], value: any): boolean {
return IsBoolean(value)
}
function FromConstructor(schema: TConstructor, references: TSchema[], value: any): boolean {
return Visit(schema.returns, references, value.prototype)
}
function FromDate(schema: TDate, references: TSchema[], value: any): boolean {
if (!IsDate(value)) return false
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
return false
}
if (IsDefined<number>(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
return false
}
if (IsDefined<number>(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
return false
}
if (IsDefined<number>(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
return false
}
if (IsDefined<number>(schema.multipleOfTimestamp) && !(value.getTime() % schema.multipleOfTimestamp === 0)) {
return false
}
return true
}
function FromFunction(schema: TFunction, references: TSchema[], value: any): boolean {
return IsFunction(value)
}
function FromImport(schema: TImport, references: TSchema[], value: any): boolean {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromInteger(schema: TInteger, references: TSchema[], value: any): boolean {
if (!IsInteger(value)) {
return false
}
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
return false
}
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
return false
}
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
return false
}
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
return false
}
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
return false
}
return true
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: any): boolean {
const check1 = schema.allOf.every((schema) => Visit(schema, references, value))
if (schema.unevaluatedProperties === false) {
const keyPattern = new RegExp(KeyOfPattern(schema))
const check2 = Object.getOwnPropertyNames(value).every((key) => keyPattern.test(key))
return check1 && check2
} else if (IsSchema(schema.unevaluatedProperties)) {
const keyCheck = new RegExp(KeyOfPattern(schema))
const check2 = Object.getOwnPropertyNames(value).every((key) => keyCheck.test(key) || Visit(schema.unevaluatedProperties as TSchema, references, value[key]))
return check1 && check2
} else {
return check1
}
}
function FromIterator(schema: TIterator, references: TSchema[], value: any): boolean {
return IsIterator(value)
}
function FromLiteral(schema: TLiteral, references: TSchema[], value: any): boolean {
return value === schema.const
}
function FromNever(schema: TNever, references: TSchema[], value: any): boolean {
return false
}
function FromNot(schema: TNot, references: TSchema[], value: any): boolean {
return !Visit(schema.not, references, value)
}
function FromNull(schema: TNull, references: TSchema[], value: any): boolean {
return IsNull(value)
}
function FromNumber(schema: TNumber, references: TSchema[], value: any): boolean {
if (!TypeSystemPolicy.IsNumberLike(value)) return false
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
return false
}
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
return false
}
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
return false
}
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
return false
}
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
return false
}
return true
}
function FromObject(schema: TObject, references: TSchema[], value: any): boolean {
if (!TypeSystemPolicy.IsObjectLike(value)) return false
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
return false
}
if (IsDefined<number>(schema.maxProperties) && !(Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
return false
}
const knownKeys = Object.getOwnPropertyNames(schema.properties)
for (const knownKey of knownKeys) {
const property = schema.properties[knownKey]
if (schema.required && schema.required.includes(knownKey)) {
if (!Visit(property, references, value[knownKey])) {
return false
}
if ((ExtendsUndefinedCheck(property) || IsAnyOrUnknown(property)) && !(knownKey in value)) {
return false
}
} else {
if (TypeSystemPolicy.IsExactOptionalProperty(value, knownKey) && !Visit(property, references, value[knownKey])) {
return false
}
}
}
if (schema.additionalProperties === false) {
const valueKeys = Object.getOwnPropertyNames(value)
// optimization: value is valid if schemaKey length matches the valueKey length
if (schema.required && schema.required.length === knownKeys.length && valueKeys.length === knownKeys.length) {
return true
} else {
return valueKeys.every((valueKey) => knownKeys.includes(valueKey))
}
} else if (typeof schema.additionalProperties === 'object') {
const valueKeys = Object.getOwnPropertyNames(value)
return valueKeys.every((key) => knownKeys.includes(key) || Visit(schema.additionalProperties as TSchema, references, value[key]))
} else {
return true
}
}
function FromPromise(schema: TPromise, references: TSchema[], value: any): boolean {
return IsPromise(value)
}
function FromRecord(schema: TRecord, references: TSchema[], value: any): boolean {
if (!TypeSystemPolicy.IsRecordLike(value)) {
return false
}
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
return false
}
if (IsDefined<number>(schema.maxProperties) && !(Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
return false
}
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
const regex = new RegExp(patternKey)
// prettier-ignore
const check1 = Object.entries(value).every(([key, value]) => {
return (regex.test(key)) ? Visit(patternSchema, references, value) : true
})
// prettier-ignore
const check2 = typeof schema.additionalProperties === 'object' ? Object.entries(value).every(([key, value]) => {
return (!regex.test(key)) ? Visit(schema.additionalProperties as TSchema, references, value) : true
}) : true
const check3 =
schema.additionalProperties === false
? Object.getOwnPropertyNames(value).every((key) => {
return regex.test(key)
})
: true
return check1 && check2 && check3
}
function FromRef(schema: TRef, references: TSchema[], value: any): boolean {
return Visit(Deref(schema, references), references, value)
}
function FromRegExp(schema: TRegExp, references: TSchema[], value: any): boolean {
const regex = new RegExp(schema.source, schema.flags)
if (IsDefined<number>(schema.minLength)) {
if (!(value.length >= schema.minLength)) return false
}
if (IsDefined<number>(schema.maxLength)) {
if (!(value.length <= schema.maxLength)) return false
}
return regex.test(value)
}
function FromString(schema: TString, references: TSchema[], value: any): boolean {
if (!IsString(value)) {
return false
}
if (IsDefined<number>(schema.minLength)) {
if (!(value.length >= schema.minLength)) return false
}
if (IsDefined<number>(schema.maxLength)) {
if (!(value.length <= schema.maxLength)) return false
}
if (IsDefined<string>(schema.pattern)) {
const regex = new RegExp(schema.pattern)
if (!regex.test(value)) return false
}
if (IsDefined<string>(schema.format)) {
if (!FormatRegistry.Has(schema.format)) return false
const func = FormatRegistry.Get(schema.format)!
return func(value)
}
return true
}
function FromSymbol(schema: TSymbol, references: TSchema[], value: any): boolean {
return IsSymbol(value)
}
function FromTemplateLiteral(schema: TTemplateLiteral, references: TSchema[], value: any): boolean {
return IsString(value) && new RegExp(schema.pattern).test(value)
}
function FromThis(schema: TThis, references: TSchema[], value: any): boolean {
return Visit(Deref(schema, references), references, value)
}
function FromTuple(schema: TTuple<any[]>, references: TSchema[], value: any): boolean {
if (!IsArray(value)) {
return false
}
if (schema.items === undefined && !(value.length === 0)) {
return false
}
if (!(value.length === schema.maxItems)) {
return false
}
if (!schema.items) {
return true
}
for (let i = 0; i < schema.items.length; i++) {
if (!Visit(schema.items[i], references, value[i])) return false
}
return true
}
function FromUndefined(schema: TUndefined, references: TSchema[], value: any): boolean {
return IsUndefined(value)
}
function FromUnion(schema: TUnion, references: TSchema[], value: any): boolean {
return schema.anyOf.some((inner) => Visit(inner, references, value))
}
function FromUint8Array(schema: TUint8Array, references: TSchema[], value: any): boolean {
if (!IsUint8Array(value)) {
return false
}
if (IsDefined<number>(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
return false
}
if (IsDefined<number>(schema.minByteLength) && !(value.length >= schema.minByteLength)) {
return false
}
return true
}
function FromUnknown(schema: TUnknown, references: TSchema[], value: any): boolean {
return true
}
function FromVoid(schema: TVoid, references: TSchema[], value: any): boolean {
return TypeSystemPolicy.IsVoidLike(value)
}
function FromKind(schema: TSchema, references: TSchema[], value: unknown): boolean {
if (!TypeRegistry.Has(schema[Kind])) return false
const func = TypeRegistry.Get(schema[Kind])!
return func(schema, value)
}
function Visit<T extends TSchema>(schema: T, references: TSchema[], value: any): boolean {
const references_ = IsDefined<string>(schema.$id) ? Pushref(schema, references) : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Any':
return FromAny(schema_, references_, value)
case 'Argument':
return FromArgument(schema_, references_, value)
case 'Array':
return FromArray(schema_, references_, value)
case 'AsyncIterator':
return FromAsyncIterator(schema_, references_, value)
case 'BigInt':
return FromBigInt(schema_, references_, value)
case 'Boolean':
return FromBoolean(schema_, references_, value)
case 'Constructor':
return FromConstructor(schema_, references_, value)
case 'Date':
return FromDate(schema_, references_, value)
case 'Function':
return FromFunction(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Integer':
return FromInteger(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Iterator':
return FromIterator(schema_, references_, value)
case 'Literal':
return FromLiteral(schema_, references_, value)
case 'Never':
return FromNever(schema_, references_, value)
case 'Not':
return FromNot(schema_, references_, value)
case 'Null':
return FromNull(schema_, references_, value)
case 'Number':
return FromNumber(schema_, references_, value)
case 'Object':
return FromObject(schema_, references_, value)
case 'Promise':
return FromPromise(schema_, references_, value)
case 'Record':
return FromRecord(schema_, references_, value)
case 'Ref':
return FromRef(schema_, references_, value)
case 'RegExp':
return FromRegExp(schema_, references_, value)
case 'String':
return FromString(schema_, references_, value)
case 'Symbol':
return FromSymbol(schema_, references_, value)
case 'TemplateLiteral':
return FromTemplateLiteral(schema_, references_, value)
case 'This':
return FromThis(schema_, references_, value)
case 'Tuple':
return FromTuple(schema_, references_, value)
case 'Undefined':
return FromUndefined(schema_, references_, value)
case 'Union':
return FromUnion(schema_, references_, value)
case 'Uint8Array':
return FromUint8Array(schema_, references_, value)
case 'Unknown':
return FromUnknown(schema_, references_, value)
case 'Void':
return FromVoid(schema_, references_, value)
default:
if (!TypeRegistry.Has(schema_[Kind])) throw new ValueCheckUnknownTypeError(schema_)
return FromKind(schema_, references_, value)
}
}
// --------------------------------------------------------------------------
// Check
// --------------------------------------------------------------------------
/** Returns true if the value matches the given type. */
export function Check<T extends TSchema>(schema: T, references: TSchema[], value: unknown): value is Static<T>
/** Returns true if the value matches the given type. */
export function Check<T extends TSchema>(schema: T, value: unknown): value is Static<T>
/** Returns true if the value matches the given type. */
export function Check(...args: any[]) {
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
}

29
src/value/check/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './check'

194
src/value/clean/clean.ts Normal file
View File

@@ -0,0 +1,194 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { KeyOfPropertyKeys } from '../../type/keyof/index'
import { Check } from '../check/index'
import { Clone } from '../clone/index'
import { Deref, Pushref } from '../deref/index'
import { Kind } from '../../type/symbols/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
// prettier-ignore
import {
HasPropertyKey,
IsString,
IsObject,
IsArray,
IsUndefined
} from '../guard/index'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
// prettier-ignore
import {
IsKind
} from '../../type/guard/kind'
// ------------------------------------------------------------------
// IsCheckable
// ------------------------------------------------------------------
function IsCheckable(schema: unknown): boolean {
return IsKind(schema) && schema[Kind] !== 'Unsafe'
}
// ------------------------------------------------------------------
// Types
// ------------------------------------------------------------------
function FromArray(schema: TArray, references: TSchema[], value: unknown): any {
if (!IsArray(value)) return value
return value.map((value) => Visit(schema.items, references, value))
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): any {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown): any {
const unevaluatedProperties = schema.unevaluatedProperties as TSchema
const intersections = schema.allOf.map((schema) => Visit(schema, references, Clone(value)))
const composite = intersections.reduce((acc: any, value: any) => (IsObject(value) ? { ...acc, ...value } : value), {})
if (!IsObject(value) || !IsObject(composite) || !IsKind(unevaluatedProperties)) return composite
const knownkeys = KeyOfPropertyKeys(schema) as string[]
for (const key of Object.getOwnPropertyNames(value)) {
if (knownkeys.includes(key)) continue
if (Check(unevaluatedProperties, references, value[key])) {
composite[key] = Visit(unevaluatedProperties, references, value[key])
}
}
return composite
}
function FromObject(schema: TObject, references: TSchema[], value: unknown): any {
if (!IsObject(value) || IsArray(value)) return value // Check IsArray for AllowArrayObject configuration
const additionalProperties = schema.additionalProperties as TSchema
for (const key of Object.getOwnPropertyNames(value)) {
if (HasPropertyKey(schema.properties, key)) {
value[key] = Visit(schema.properties[key], references, value[key])
continue
}
if (IsKind(additionalProperties) && Check(additionalProperties, references, value[key])) {
value[key] = Visit(additionalProperties, references, value[key])
continue
}
delete value[key]
}
return value
}
function FromRecord(schema: TRecord, references: TSchema[], value: unknown): any {
if (!IsObject(value)) return value
const additionalProperties = schema.additionalProperties as TSchema
const propertyKeys = Object.getOwnPropertyNames(value)
const [propertyKey, propertySchema] = Object.entries(schema.patternProperties)[0]
const propertyKeyTest = new RegExp(propertyKey)
for (const key of propertyKeys) {
if (propertyKeyTest.test(key)) {
value[key] = Visit(propertySchema, references, value[key])
continue
}
if (IsKind(additionalProperties) && Check(additionalProperties, references, value[key])) {
value[key] = Visit(additionalProperties, references, value[key])
continue
}
delete value[key]
}
return value
}
function FromRef(schema: TRef, references: TSchema[], value: unknown): any {
return Visit(Deref(schema, references), references, value)
}
function FromThis(schema: TThis, references: TSchema[], value: unknown): any {
return Visit(Deref(schema, references), references, value)
}
function FromTuple(schema: TTuple, references: TSchema[], value: unknown): any {
if (!IsArray(value)) return value
if (IsUndefined(schema.items)) return []
const length = Math.min(value.length, schema.items.length)
for (let i = 0; i < length; i++) {
value[i] = Visit(schema.items[i], references, value[i])
}
// prettier-ignore
return value.length > length
? value.slice(0, length)
: value
}
function FromUnion(schema: TUnion, references: TSchema[], value: unknown): any {
for (const inner of schema.anyOf) {
if (IsCheckable(inner) && Check(inner, references, value)) {
return Visit(inner, references, value)
}
}
return value
}
function Visit(schema: TSchema, references: TSchema[], value: unknown): unknown {
const references_ = IsString(schema.$id) ? Pushref(schema, references) : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Array':
return FromArray(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Object':
return FromObject(schema_, references_, value)
case 'Record':
return FromRecord(schema_, references_, value)
case 'Ref':
return FromRef(schema_, references_, value)
case 'This':
return FromThis(schema_, references_, value)
case 'Tuple':
return FromTuple(schema_, references_, value)
case 'Union':
return FromUnion(schema_, references_, value)
default:
return value
}
}
// ------------------------------------------------------------------
// Clean
// ------------------------------------------------------------------
/** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */
export function Clean(schema: TSchema, references: TSchema[], value: unknown): unknown
/** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */
export function Clean(schema: TSchema, value: unknown): unknown
/** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */
export function Clean(...args: any[]) {
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
}

29
src/value/clean/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './clean'

79
src/value/clone/clone.ts Normal file
View File

@@ -0,0 +1,79 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import type { ObjectType as FromObject, ArrayType as FromArray, TypedArrayType, ValueType } from '../guard/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsArray, IsDate, IsMap, IsSet, IsObject, IsTypedArray, IsValueType } from '../guard/index'
// ------------------------------------------------------------------
// Clonable
// ------------------------------------------------------------------
function FromObject(value: FromObject): any {
const Acc = {} as Record<PropertyKey, unknown>
for (const key of Object.getOwnPropertyNames(value)) {
Acc[key] = Clone(value[key])
}
for (const key of Object.getOwnPropertySymbols(value)) {
Acc[key] = Clone(value[key])
}
return Acc
}
function FromArray(value: FromArray): any {
return value.map((element: any) => Clone(element))
}
function FromTypedArray(value: TypedArrayType): any {
return value.slice()
}
function FromMap(value: Map<unknown, unknown>): any {
return new Map(Clone([...value.entries()]))
}
function FromSet(value: Set<unknown>): any {
return new Set(Clone([...value.entries()]))
}
function FromDate(value: Date): any {
return new Date(value.toISOString())
}
function FromValue(value: ValueType): any {
return value
}
// ------------------------------------------------------------------
// Clone
// ------------------------------------------------------------------
/** Returns a clone of the given value */
export function Clone<T extends unknown>(value: T): T {
if (IsArray(value)) return FromArray(value)
if (IsDate(value)) return FromDate(value)
if (IsTypedArray(value)) return FromTypedArray(value)
if (IsMap(value)) return FromMap(value)
if (IsSet(value)) return FromSet(value)
if (IsObject(value)) return FromObject(value)
if (IsValueType(value)) return FromValue(value)
throw new Error('ValueClone: Unable to clone value')
}

29
src/value/clone/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './clone'

View File

@@ -0,0 +1,318 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { Clone } from '../clone/index'
import { Check } from '../check/index'
import { Deref, Pushref } from '../deref/index'
import { Kind } from '../../type/symbols/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TBigInt } from '../../type/bigint/index'
import type { TBoolean } from '../../type/boolean/index'
import type { TDate } from '../../type/date/index'
import type { TInteger } from '../../type/integer/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TImport } from '../../type/module/index'
import type { TLiteral } from '../../type/literal/index'
import type { TNull } from '../../type/null/index'
import type { TNumber } from '../../type/number/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
import type { TString } from '../../type/string/index'
import type { TSymbol } from '../../type/symbol/index'
import type { TUndefined } from '../../type/undefined/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsArray, IsObject, IsDate, IsUndefined, IsString, IsNumber, IsBoolean, IsBigInt, IsSymbol, HasPropertyKey } from '../guard/index'
// ------------------------------------------------------------------
// Conversions
// ------------------------------------------------------------------
function IsStringNumeric(value: unknown): value is string {
return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value))
}
function IsValueToString(value: unknown): value is { toString: () => string } {
return IsBigInt(value) || IsBoolean(value) || IsNumber(value)
}
function IsValueTrue(value: unknown): value is true {
return value === true || (IsNumber(value) && value === 1) || (IsBigInt(value) && value === BigInt('1')) || (IsString(value) && (value.toLowerCase() === 'true' || value === '1'))
}
function IsValueFalse(value: unknown): value is false {
return value === false || (IsNumber(value) && (value === 0 || Object.is(value, -0))) || (IsBigInt(value) && value === BigInt('0')) || (IsString(value) && (value.toLowerCase() === 'false' || value === '0' || value === '-0'))
}
function IsTimeStringWithTimeZone(value: unknown): value is string {
return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
}
function IsTimeStringWithoutTimeZone(value: unknown): value is string {
return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
}
function IsDateTimeStringWithTimeZone(value: unknown): value is string {
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
}
function IsDateTimeStringWithoutTimeZone(value: unknown): value is string {
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
}
function IsDateString(value: unknown): value is string {
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value)
}
// ------------------------------------------------------------------
// Convert
// ------------------------------------------------------------------
function TryConvertLiteralString(value: unknown, target: string) {
const conversion = TryConvertString(value)
return conversion === target ? conversion : value
}
function TryConvertLiteralNumber(value: unknown, target: number) {
const conversion = TryConvertNumber(value)
return conversion === target ? conversion : value
}
function TryConvertLiteralBoolean(value: unknown, target: boolean) {
const conversion = TryConvertBoolean(value)
return conversion === target ? conversion : value
}
// prettier-ignore
function TryConvertLiteral(schema: TLiteral, value: unknown) {
return (
IsString(schema.const) ? TryConvertLiteralString(value, schema.const) :
IsNumber(schema.const) ? TryConvertLiteralNumber(value, schema.const) :
IsBoolean(schema.const) ? TryConvertLiteralBoolean(value, schema.const) :
value
)
}
function TryConvertBoolean(value: unknown) {
return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value
}
function TryConvertBigInt(value: unknown) {
const truncateInteger = (value: string) => value.split('.')[0]
return IsStringNumeric(value) ? BigInt(truncateInteger(value)) : IsNumber(value) ? BigInt(Math.trunc(value)) : IsValueFalse(value) ? BigInt(0) : IsValueTrue(value) ? BigInt(1) : value
}
function TryConvertString(value: unknown) {
return IsSymbol(value) && value.description !== undefined ? value.description.toString() : IsValueToString(value) ? value.toString() : value
}
function TryConvertNumber(value: unknown) {
return IsStringNumeric(value) ? parseFloat(value) : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value
}
function TryConvertInteger(value: unknown) {
return IsStringNumeric(value) ? parseInt(value) : IsNumber(value) ? Math.trunc(value) : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value
}
function TryConvertNull(value: unknown) {
return IsString(value) && value.toLowerCase() === 'null' ? null : value
}
function TryConvertUndefined(value: unknown) {
return IsString(value) && value === 'undefined' ? undefined : value
}
// ------------------------------------------------------------------
// note: this function may return an invalid dates for the regex
// tests above. Invalid dates will however be checked during the
// casting function and will return a epoch date if invalid.
// Consider better string parsing for the iso dates in future
// revisions.
// ------------------------------------------------------------------
// prettier-ignore
function TryConvertDate(value: unknown) {
return (
IsDate(value) ? value :
IsNumber(value) ? new Date(value) :
IsValueTrue(value) ? new Date(1) :
IsValueFalse(value) ? new Date(0) :
IsStringNumeric(value) ? new Date(parseInt(value)) :
IsTimeStringWithoutTimeZone(value) ? new Date(`1970-01-01T${value}.000Z`) :
IsTimeStringWithTimeZone(value) ? new Date(`1970-01-01T${value}`) :
IsDateTimeStringWithoutTimeZone(value) ? new Date(`${value}.000Z`) :
IsDateTimeStringWithTimeZone(value) ? new Date(value) :
IsDateString(value) ? new Date(`${value}T00:00:00.000Z`) :
value
)
}
// ------------------------------------------------------------------
// Default
// ------------------------------------------------------------------
function Default(value: unknown): unknown {
return value
}
// ------------------------------------------------------------------
// Convert
// ------------------------------------------------------------------
function FromArray(schema: TArray, references: TSchema[], value: any): any {
const elements = IsArray(value) ? value : [value]
return elements.map((element) => Visit(schema.items, references, element))
}
function FromBigInt(schema: TBigInt, references: TSchema[], value: any): unknown {
return TryConvertBigInt(value)
}
function FromBoolean(schema: TBoolean, references: TSchema[], value: any): unknown {
return TryConvertBoolean(value)
}
function FromDate(schema: TDate, references: TSchema[], value: any): unknown {
return TryConvertDate(value)
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): unknown {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromInteger(schema: TInteger, references: TSchema[], value: any): unknown {
return TryConvertInteger(value)
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: any): unknown {
return schema.allOf.reduce((value, schema) => Visit(schema, references, value), value)
}
function FromLiteral(schema: TLiteral, references: TSchema[], value: any): unknown {
return TryConvertLiteral(schema, value)
}
function FromNull(schema: TNull, references: TSchema[], value: any): unknown {
return TryConvertNull(value)
}
function FromNumber(schema: TNumber, references: TSchema[], value: any): unknown {
return TryConvertNumber(value)
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], value: any): unknown {
if(!IsObject(value) || IsArray(value)) return value
for(const propertyKey of Object.getOwnPropertyNames(schema.properties)) {
if(!HasPropertyKey(value, propertyKey)) continue
value[propertyKey] = Visit(schema.properties[propertyKey], references, value[propertyKey])
}
return value
}
function FromRecord(schema: TRecord, references: TSchema[], value: any): unknown {
const isConvertable = IsObject(value) && !IsArray(value)
if (!isConvertable) return value
const propertyKey = Object.getOwnPropertyNames(schema.patternProperties)[0]
const property = schema.patternProperties[propertyKey]
for (const [propKey, propValue] of Object.entries(value)) {
value[propKey] = Visit(property, references, propValue)
}
return value
}
function FromRef(schema: TRef, references: TSchema[], value: any): unknown {
return Visit(Deref(schema, references), references, value)
}
function FromString(schema: TString, references: TSchema[], value: any): unknown {
return TryConvertString(value)
}
function FromSymbol(schema: TSymbol, references: TSchema[], value: any): unknown {
return IsString(value) || IsNumber(value) ? Symbol(value) : value
}
function FromThis(schema: TThis, references: TSchema[], value: any): unknown {
return Visit(Deref(schema, references), references, value)
}
// prettier-ignore
function FromTuple(schema: TTuple, references: TSchema[], value: any): unknown {
const isConvertable = IsArray(value) && !IsUndefined(schema.items)
if(!isConvertable) return value
return value.map((value, index) => {
return (index < schema.items!.length)
? Visit(schema.items![index], references, value)
: value
})
}
function FromUndefined(schema: TUndefined, references: TSchema[], value: any): unknown {
return TryConvertUndefined(value)
}
function FromUnion(schema: TUnion, references: TSchema[], value: any): unknown {
// Check if original value already matches one of the union variants
for (const subschema of schema.anyOf) {
if (Check(subschema, references, value)) {
return value
}
}
// Attempt conversion for each variant
for (const subschema of schema.anyOf) {
const converted = Visit(subschema, references, Clone(value))
if (!Check(subschema, references, converted)) continue
return converted
}
return value
}
function Visit(schema: TSchema, references: TSchema[], value: any): unknown {
const references_ = Pushref(schema, references)
const schema_ = schema as any
switch (schema[Kind]) {
case 'Array':
return FromArray(schema_, references_, value)
case 'BigInt':
return FromBigInt(schema_, references_, value)
case 'Boolean':
return FromBoolean(schema_, references_, value)
case 'Date':
return FromDate(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Integer':
return FromInteger(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Literal':
return FromLiteral(schema_, references_, value)
case 'Null':
return FromNull(schema_, references_, value)
case 'Number':
return FromNumber(schema_, references_, value)
case 'Object':
return FromObject(schema_, references_, value)
case 'Record':
return FromRecord(schema_, references_, value)
case 'Ref':
return FromRef(schema_, references_, value)
case 'String':
return FromString(schema_, references_, value)
case 'Symbol':
return FromSymbol(schema_, references_, value)
case 'This':
return FromThis(schema_, references_, value)
case 'Tuple':
return FromTuple(schema_, references_, value)
case 'Undefined':
return FromUndefined(schema_, references_, value)
case 'Union':
return FromUnion(schema_, references_, value)
default:
return Default(value)
}
}
// ------------------------------------------------------------------
// Convert
// ------------------------------------------------------------------
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(schema: TSchema, references: TSchema[], value: unknown): unknown
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert(schema: TSchema, value: unknown): unknown
/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */
// prettier-ignore
export function Convert(...args: any[]) {
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
}

View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './convert'

488
src/value/create/create.ts Normal file
View File

@@ -0,0 +1,488 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { HasPropertyKey } from '../guard/index'
import { Check } from '../check/index'
import { Clone } from '../clone/index'
import { Deref, Pushref } from '../deref/index'
import { TemplateLiteralGenerate, IsTemplateLiteralFinite } from '../../type/template-literal/index'
import { PatternStringExact, PatternNumberExact } from '../../type/patterns/index'
import { TypeRegistry } from '../../type/registry/index'
import { Kind } from '../../type/symbols/index'
import { TypeBoxError } from '../../type/error/index'
import type { TSchema } from '../../type/schema/index'
import type { TAsyncIterator } from '../../type/async-iterator/index'
import type { TAny } from '../../type/any/index'
import type { TArray } from '../../type/array/index'
import type { TBigInt } from '../../type/bigint/index'
import type { TBoolean } from '../../type/boolean/index'
import type { TDate } from '../../type/date/index'
import type { TConstructor } from '../../type/constructor/index'
import type { TFunction } from '../../type/function/index'
import type { TImport } from '../../type/module/index'
import type { TInteger } from '../../type/integer/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TIterator } from '../../type/iterator/index'
import type { TLiteral } from '../../type/literal/index'
import type { TNever } from '../../type/never/index'
import type { TNot } from '../../type/not/index'
import type { TNull } from '../../type/null/index'
import type { TNumber } from '../../type/number/index'
import type { TObject } from '../../type/object/index'
import type { TPromise } from '../../type/promise/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TRegExp } from '../../type/regexp/index'
import type { TTemplateLiteral } from '../../type/template-literal/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
import type { TUnknown } from '../../type/unknown/index'
import type { Static } from '../../type/static/index'
import type { TString } from '../../type/string/index'
import type { TSymbol } from '../../type/symbol/index'
import type { TUndefined } from '../../type/undefined/index'
import type { TUint8Array } from '../../type/uint8array/index'
import type { TVoid } from '../../type/void/index'
import { IsFunction } from '../guard/guard'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValueCreateError extends TypeBoxError {
constructor(public readonly schema: TSchema, message: string) {
super(message)
}
}
// ------------------------------------------------------------------
// Default
// ------------------------------------------------------------------
function FromDefault(value: unknown) {
return IsFunction(value) ? value() : Clone(value)
}
// ------------------------------------------------------------------
// Create
// ------------------------------------------------------------------
function FromAny(schema: TAny, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return {}
}
}
function FromArgument(schema: TAny, references: TSchema[]): any {
return {}
}
function FromArray(schema: TArray, references: TSchema[]): any {
if (schema.uniqueItems === true && !HasPropertyKey(schema, 'default')) {
throw new ValueCreateError(schema, 'Array with the uniqueItems constraint requires a default value')
} else if ('contains' in schema && !HasPropertyKey(schema, 'default')) {
throw new ValueCreateError(schema, 'Array with the contains constraint requires a default value')
} else if ('default' in schema) {
return FromDefault(schema.default)
} else if (schema.minItems !== undefined) {
return Array.from({ length: schema.minItems }).map((item) => {
return Visit(schema.items, references)
})
} else {
return []
}
}
function FromAsyncIterator(schema: TAsyncIterator, references: TSchema[]) {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return (async function* () {})()
}
}
function FromBigInt(schema: TBigInt, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return BigInt(0)
}
}
function FromBoolean(schema: TBoolean, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return false
}
}
function FromConstructor(schema: TConstructor, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
const value = Visit(schema.returns, references) as any
if (typeof value === 'object' && !Array.isArray(value)) {
return class {
constructor() {
for (const [key, val] of Object.entries(value)) {
const self = this as any
self[key] = val
}
}
}
} else {
return class {}
}
}
}
function FromDate(schema: TDate, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if (schema.minimumTimestamp !== undefined) {
return new Date(schema.minimumTimestamp)
} else {
return new Date()
}
}
function FromFunction(schema: TFunction, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return () => Visit(schema.returns, references)
}
}
function FromImport(schema: TImport, references: TSchema[]): any {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions])
}
function FromInteger(schema: TInteger, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if (schema.minimum !== undefined) {
return schema.minimum
} else {
return 0
}
}
function FromIntersect(schema: TIntersect, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
// --------------------------------------------------------------
// Note: The best we can do here is attempt to instance each
// sub type and apply through object assign. For non-object
// sub types, we just escape the assignment and just return
// the value. In the latter case, this is typically going to
// be a consequence of an illogical intersection.
// --------------------------------------------------------------
const value = schema.allOf.reduce((acc, schema) => {
const next = Visit(schema, references) as any
return typeof next === 'object' ? { ...acc, ...next } : next
}, {})
if (!Check(schema, references, value)) throw new ValueCreateError(schema, 'Intersect produced invalid value. Consider using a default value.')
return value
}
}
function FromIterator(schema: TIterator, references: TSchema[]) {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return (function* () {})()
}
}
function FromLiteral(schema: TLiteral, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return schema.const
}
}
function FromNever(schema: TNever, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
throw new ValueCreateError(schema, 'Never types cannot be created. Consider using a default value.')
}
}
function FromNot(schema: TNot, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
throw new ValueCreateError(schema, 'Not types must have a default value')
}
}
function FromNull(schema: TNull, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return null
}
}
function FromNumber(schema: TNumber, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if (schema.minimum !== undefined) {
return schema.minimum
} else {
return 0
}
}
function FromObject(schema: TObject, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
const required = new Set(schema.required)
const Acc = {} as Record<PropertyKey, unknown>
for (const [key, subschema] of Object.entries(schema.properties)) {
if (!required.has(key)) continue
Acc[key] = Visit(subschema, references)
}
return Acc
}
}
function FromPromise(schema: TPromise, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return Promise.resolve(Visit(schema.item, references))
}
}
function FromRecord(schema: TRecord, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return {}
}
}
function FromRef(schema: TRef, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return Visit(Deref(schema, references), references)
}
}
function FromRegExp(schema: TRegExp, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
throw new ValueCreateError(schema, 'RegExp types cannot be created. Consider using a default value.')
}
}
function FromString(schema: TString, references: TSchema[]): any {
if (schema.pattern !== undefined) {
if (!HasPropertyKey(schema, 'default')) {
throw new ValueCreateError(schema, 'String types with patterns must specify a default value')
} else {
return FromDefault(schema.default)
}
} else if (schema.format !== undefined) {
if (!HasPropertyKey(schema, 'default')) {
throw new ValueCreateError(schema, 'String types with formats must specify a default value')
} else {
return FromDefault(schema.default)
}
} else {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if (schema.minLength !== undefined) {
// prettier-ignore
return Array.from({ length: schema.minLength }).map(() => ' ').join('')
} else {
return ''
}
}
}
function FromSymbol(schema: TSymbol, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if ('value' in schema) {
return Symbol.for(schema.value)
} else {
return Symbol()
}
}
function FromTemplateLiteral(schema: TTemplateLiteral, references: TSchema[]) {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
}
if (!IsTemplateLiteralFinite(schema)) throw new ValueCreateError(schema, 'Can only create template literals that produce a finite variants. Consider using a default value.')
const generated = TemplateLiteralGenerate(schema) as string[]
return generated[0]
}
function FromThis(schema: TThis, references: TSchema[]): any {
if (recursiveDepth++ > recursiveMaxDepth) throw new ValueCreateError(schema, 'Cannot create recursive type as it appears possibly infinite. Consider using a default.')
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return Visit(Deref(schema, references), references)
}
}
function FromTuple(schema: TTuple, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
}
if (schema.items === undefined) {
return []
} else {
return Array.from({ length: schema.minItems }).map((_, index) => Visit((schema.items as any[])[index], references))
}
}
function FromUndefined(schema: TUndefined, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return undefined
}
}
function FromUnion(schema: TUnion, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if (schema.anyOf.length === 0) {
throw new Error('ValueCreate.Union: Cannot create Union with zero variants')
} else {
return Visit(schema.anyOf[0], references)
}
}
function FromUint8Array(schema: TUint8Array, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else if (schema.minByteLength !== undefined) {
return new Uint8Array(schema.minByteLength)
} else {
return new Uint8Array(0)
}
}
function FromUnknown(schema: TUnknown, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return {}
}
}
function FromVoid(schema: TVoid, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
return void 0
}
}
function FromKind(schema: TSchema, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
} else {
throw new Error('User defined types must specify a default value')
}
}
function Visit(schema: TSchema, references: TSchema[]): unknown {
const references_ = Pushref(schema, references)
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Any':
return FromAny(schema_, references_)
case 'Argument':
return FromArgument(schema_, references_)
case 'Array':
return FromArray(schema_, references_)
case 'AsyncIterator':
return FromAsyncIterator(schema_, references_)
case 'BigInt':
return FromBigInt(schema_, references_)
case 'Boolean':
return FromBoolean(schema_, references_)
case 'Constructor':
return FromConstructor(schema_, references_)
case 'Date':
return FromDate(schema_, references_)
case 'Function':
return FromFunction(schema_, references_)
case 'Import':
return FromImport(schema_, references_)
case 'Integer':
return FromInteger(schema_, references_)
case 'Intersect':
return FromIntersect(schema_, references_)
case 'Iterator':
return FromIterator(schema_, references_)
case 'Literal':
return FromLiteral(schema_, references_)
case 'Never':
return FromNever(schema_, references_)
case 'Not':
return FromNot(schema_, references_)
case 'Null':
return FromNull(schema_, references_)
case 'Number':
return FromNumber(schema_, references_)
case 'Object':
return FromObject(schema_, references_)
case 'Promise':
return FromPromise(schema_, references_)
case 'Record':
return FromRecord(schema_, references_)
case 'Ref':
return FromRef(schema_, references_)
case 'RegExp':
return FromRegExp(schema_, references_)
case 'String':
return FromString(schema_, references_)
case 'Symbol':
return FromSymbol(schema_, references_)
case 'TemplateLiteral':
return FromTemplateLiteral(schema_, references_)
case 'This':
return FromThis(schema_, references_)
case 'Tuple':
return FromTuple(schema_, references_)
case 'Undefined':
return FromUndefined(schema_, references_)
case 'Union':
return FromUnion(schema_, references_)
case 'Uint8Array':
return FromUint8Array(schema_, references_)
case 'Unknown':
return FromUnknown(schema_, references_)
case 'Void':
return FromVoid(schema_, references_)
default:
if (!TypeRegistry.Has(schema_[Kind])) throw new ValueCreateError(schema_, 'Unknown type')
return FromKind(schema_, references_)
}
}
// ------------------------------------------------------------------
// State
// ------------------------------------------------------------------
const recursiveMaxDepth = 512
let recursiveDepth = 0
// ------------------------------------------------------------------
// Create
// ------------------------------------------------------------------
/** Creates a value from the given schema and references */
export function Create<T extends TSchema>(schema: T, references: TSchema[]): Static<T>
/** Creates a value from the given schema */
export function Create<T extends TSchema>(schema: T): Static<T>
/** Creates a value from the given schema */
export function Create(...args: any[]) {
recursiveDepth = 0
return args.length === 2 ? Visit(args[0], args[1]) : Visit(args[0], [])
}

29
src/value/create/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './create'

View File

@@ -0,0 +1,45 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { HasTransform, TransformDecode, TransformDecodeCheckError } from '../transform/index'
import { Check } from '../check/index'
import { Errors } from '../../errors/index'
import type { TSchema } from '../../type/schema/index'
import type { StaticDecode } from '../../type/static/index'
/** Decodes a value or throws if error */
export function Decode<T extends TSchema, Static = StaticDecode<T>, Result extends Static = Static>(schema: T, references: TSchema[], value: unknown): Result
/** Decodes a value or throws if error */
export function Decode<T extends TSchema, Static = StaticDecode<T>, Result extends Static = Static>(schema: T, value: unknown): Result
/** Decodes a value or throws if error */
export function Decode(...args: any[]): any {
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
if (!Check(schema, references, value)) throw new TransformDecodeCheckError(schema, value, Errors(schema, references, value).First()!)
return HasTransform(schema, references) ? TransformDecode(schema, references, value) : value
}

29
src/value/decode/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './decode'

View File

@@ -0,0 +1,208 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { Check } from '../check/index'
import { Clone } from '../clone/index'
import { Deref, Pushref } from '../deref/index'
import { Kind } from '../../type/symbols/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsArray, IsDate, IsFunction, IsObject, IsUndefined, HasPropertyKey } from '../guard/index'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
import { IsKind } from '../../type/guard/kind'
// ------------------------------------------------------------------
// ValueOrDefault
// ------------------------------------------------------------------
function ValueOrDefault(schema: TSchema, value: unknown): unknown {
const defaultValue = HasPropertyKey(schema, 'default') ? schema.default : undefined
const clone = IsFunction(defaultValue) ? defaultValue() : Clone(defaultValue)
return IsUndefined(value) ? clone : IsObject(value) && IsObject(clone) ? Object.assign(clone, value) : value
}
// ------------------------------------------------------------------
// HasDefaultProperty
// ------------------------------------------------------------------
function HasDefaultProperty(schema: unknown): schema is TSchema {
return IsKind(schema) && 'default' in schema
}
// ------------------------------------------------------------------
// Types
// ------------------------------------------------------------------
function FromArray(schema: TArray, references: TSchema[], value: unknown): any {
// if the value is an array, we attempt to initialize it's elements
if (IsArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i] = Visit(schema.items, references, value[i])
}
return value
}
// ... otherwise use default initialization
const defaulted = ValueOrDefault(schema, value)
if (!IsArray(defaulted)) return defaulted
for (let i = 0; i < defaulted.length; i++) {
defaulted[i] = Visit(schema.items, references, defaulted[i])
}
return defaulted
}
function FromDate(schema: TArray, references: TSchema[], value: unknown): any {
// special case intercept for dates
return IsDate(value) ? value : ValueOrDefault(schema, value)
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): any {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
return schema.allOf.reduce((acc, schema) => {
const next = Visit(schema, references, defaulted)
return IsObject(next) ? { ...acc, ...next } : next
}, {})
}
function FromObject(schema: TObject, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
// return defaulted
if (!IsObject(defaulted)) return defaulted
const knownPropertyKeys = Object.getOwnPropertyNames(schema.properties)
// properties
for (const key of knownPropertyKeys) {
// note: we need to traverse into the object and test if the return value
// yielded a non undefined result. Here we interpret an undefined result as
// a non assignable property and continue.
const propertyValue = Visit(schema.properties[key], references, defaulted[key])
if (IsUndefined(propertyValue)) continue
defaulted[key] = Visit(schema.properties[key], references, defaulted[key])
}
// return if not additional properties
if (!HasDefaultProperty(schema.additionalProperties)) return defaulted
// additional properties
for (const key of Object.getOwnPropertyNames(defaulted)) {
if (knownPropertyKeys.includes(key)) continue
defaulted[key] = Visit(schema.additionalProperties, references, defaulted[key])
}
return defaulted
}
function FromRecord(schema: TRecord, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
if (!IsObject(defaulted)) return defaulted
const additionalPropertiesSchema = schema.additionalProperties as TSchema
const [propertyKeyPattern, propertySchema] = Object.entries(schema.patternProperties)[0]
const knownPropertyKey = new RegExp(propertyKeyPattern)
// properties
for (const key of Object.getOwnPropertyNames(defaulted)) {
if (!(knownPropertyKey.test(key) && HasDefaultProperty(propertySchema))) continue
defaulted[key] = Visit(propertySchema, references, defaulted[key])
}
// return if not additional properties
if (!HasDefaultProperty(additionalPropertiesSchema)) return defaulted
// additional properties
for (const key of Object.getOwnPropertyNames(defaulted)) {
if (knownPropertyKey.test(key)) continue
defaulted[key] = Visit(additionalPropertiesSchema, references, defaulted[key])
}
return defaulted
}
function FromRef(schema: TRef, references: TSchema[], value: unknown): any {
return Visit(Deref(schema, references), references, ValueOrDefault(schema, value))
}
function FromThis(schema: TThis, references: TSchema[], value: unknown): any {
return Visit(Deref(schema, references), references, value)
}
function FromTuple(schema: TTuple, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
if (!IsArray(defaulted) || IsUndefined(schema.items)) return defaulted
const [items, max] = [schema.items!, Math.max(schema.items!.length, defaulted.length)]
for (let i = 0; i < max; i++) {
if (i < items.length) defaulted[i] = Visit(items[i], references, defaulted[i])
}
return defaulted
}
function FromUnion(schema: TUnion, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
for (const inner of schema.anyOf) {
const result = Visit(inner, references, Clone(defaulted))
if (Check(inner, references, result)) {
return result
}
}
return defaulted
}
function Visit(schema: TSchema, references: TSchema[], value: unknown): any {
const references_ = Pushref(schema, references)
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Array':
return FromArray(schema_, references_, value)
case 'Date':
return FromDate(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Object':
return FromObject(schema_, references_, value)
case 'Record':
return FromRecord(schema_, references_, value)
case 'Ref':
return FromRef(schema_, references_, value)
case 'This':
return FromThis(schema_, references_, value)
case 'Tuple':
return FromTuple(schema_, references_, value)
case 'Union':
return FromUnion(schema_, references_, value)
default:
return ValueOrDefault(schema_, value)
}
}
// ------------------------------------------------------------------
// Default
// ------------------------------------------------------------------
/** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */
export function Default(schema: TSchema, references: TSchema[], value: unknown): unknown
/** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */
export function Default(schema: TSchema, value: unknown): unknown
/** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */
export function Default(...args: any[]) {
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
}

View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './default'

215
src/value/delta/delta.ts Normal file
View File

@@ -0,0 +1,215 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { HasPropertyKey, IsStandardObject, IsArray, IsTypedArray, IsValueType } from '../guard/index'
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from '../guard/index'
import type { Static } from '../../type/static/index'
import { ValuePointer } from '../pointer/index'
import { Clone } from '../clone/index'
import { Equal } from '../equal/equal'
import { TypeBoxError } from '../../type/error/index'
import { Literal, type TLiteral } from '../../type/literal/index'
import { Object, type TObject } from '../../type/object/index'
import { String, type TString } from '../../type/string/index'
import { Unknown, type TUnknown } from '../../type/unknown/index'
import { Union, type TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// Commands
// ------------------------------------------------------------------
// Note: A TypeScript 5.4.2 compiler regression resulted in the type
// import paths being generated incorrectly. We can resolve this by
// explicitly importing the correct TSchema types above. Note also
// that the left-side annotations are optional, but since the types
// are imported we might as well use them. We should check this
// regression in future. The regression occured between TypeScript
// versions 5.3.3 -> 5.4.2.
export type Insert = Static<typeof Insert>
export const Insert: TObject<{
type: TLiteral<'insert'>
path: TString
value: TUnknown
}> = Object({
type: Literal('insert'),
path: String(),
value: Unknown(),
})
export type Update = Static<typeof Update>
export const Update: TObject<{
type: TLiteral<'update'>
path: TString
value: TUnknown
}> = Object({
type: Literal('update'),
path: String(),
value: Unknown(),
})
export type Delete = Static<typeof Delete>
export const Delete: TObject<{
type: TLiteral<'delete'>
path: TString
}> = Object({
type: Literal('delete'),
path: String(),
})
export type Edit = Static<typeof Edit>
export const Edit: TUnion<[typeof Insert, typeof Update, typeof Delete]> = Union([Insert, Update, Delete])
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValueDiffError extends TypeBoxError {
constructor(public readonly value: unknown, message: string) {
super(message)
}
}
// ------------------------------------------------------------------
// Command Factory
// ------------------------------------------------------------------
function CreateUpdate(path: string, value: unknown): Edit {
return { type: 'update', path, value }
}
function CreateInsert(path: string, value: unknown): Edit {
return { type: 'insert', path, value }
}
function CreateDelete(path: string): Edit {
return { type: 'delete', path }
}
// ------------------------------------------------------------------
// AssertDiffable
// ------------------------------------------------------------------
function AssertDiffable(value: unknown): asserts value is Record<string | number, unknown> {
if (globalThis.Object.getOwnPropertySymbols(value).length > 0) throw new ValueDiffError(value, 'Cannot diff objects with symbols')
}
// ------------------------------------------------------------------
// Diffing Generators
// ------------------------------------------------------------------
function* ObjectType(path: string, current: ObjectType, next: unknown): IterableIterator<Edit> {
AssertDiffable(current)
AssertDiffable(next)
if (!IsStandardObject(next)) return yield CreateUpdate(path, next)
const currentKeys = globalThis.Object.getOwnPropertyNames(current)
const nextKeys = globalThis.Object.getOwnPropertyNames(next)
// ----------------------------------------------------------------
// inserts
// ----------------------------------------------------------------
for (const key of nextKeys) {
if (HasPropertyKey(current, key)) continue
yield CreateInsert(`${path}/${key}`, next[key])
}
// ----------------------------------------------------------------
// updates
// ----------------------------------------------------------------
for (const key of currentKeys) {
if (!HasPropertyKey(next, key)) continue
if (Equal(current, next)) continue
yield* Visit(`${path}/${key}`, current[key], next[key])
}
// ----------------------------------------------------------------
// deletes
// ----------------------------------------------------------------
for (const key of currentKeys) {
if (HasPropertyKey(next, key)) continue
yield CreateDelete(`${path}/${key}`)
}
}
function* ArrayType(path: string, current: ArrayType, next: unknown): IterableIterator<Edit> {
if (!IsArray(next)) return yield CreateUpdate(path, next)
for (let i = 0; i < Math.min(current.length, next.length); i++) {
yield* Visit(`${path}/${i}`, current[i], next[i])
}
for (let i = 0; i < next.length; i++) {
if (i < current.length) continue
yield CreateInsert(`${path}/${i}`, next[i])
}
for (let i = current.length - 1; i >= 0; i--) {
if (i < next.length) continue
yield CreateDelete(`${path}/${i}`)
}
}
function* TypedArrayType(path: string, current: TypedArrayType, next: unknown): IterableIterator<Edit> {
if (!IsTypedArray(next) || current.length !== next.length || globalThis.Object.getPrototypeOf(current).constructor.name !== globalThis.Object.getPrototypeOf(next).constructor.name) return yield CreateUpdate(path, next)
for (let i = 0; i < Math.min(current.length, next.length); i++) {
yield* Visit(`${path}/${i}`, current[i], next[i])
}
}
function* ValueType(path: string, current: ValueType, next: unknown): IterableIterator<Edit> {
if (current === next) return
yield CreateUpdate(path, next)
}
function* Visit(path: string, current: unknown, next: unknown): IterableIterator<Edit> {
if (IsStandardObject(current)) return yield* ObjectType(path, current, next)
if (IsArray(current)) return yield* ArrayType(path, current, next)
if (IsTypedArray(current)) return yield* TypedArrayType(path, current, next)
if (IsValueType(current)) return yield* ValueType(path, current, next)
throw new ValueDiffError(current, 'Unable to diff value')
}
// ------------------------------------------------------------------
// Diff
// ------------------------------------------------------------------
export function Diff(current: unknown, next: unknown): Edit[] {
return [...Visit('', current, next)]
}
// ------------------------------------------------------------------
// Patch
// ------------------------------------------------------------------
function IsRootUpdate(edits: Edit[]): edits is [Update] {
return edits.length > 0 && edits[0].path === '' && edits[0].type === 'update'
}
function IsIdentity(edits: Edit[]) {
return edits.length === 0
}
export function Patch<T = any>(current: unknown, edits: Edit[]): T {
if (IsRootUpdate(edits)) {
return Clone(edits[0].value) as T
}
if (IsIdentity(edits)) {
return Clone(current) as T
}
const clone = Clone(current)
for (const edit of edits) {
switch (edit.type) {
case 'insert': {
ValuePointer.Set(clone, edit.path, edit.value)
break
}
case 'update': {
ValuePointer.Set(clone, edit.path, edit.value)
break
}
case 'delete': {
ValuePointer.Delete(clone, edit.path)
break
}
}
}
return clone as T
}

29
src/value/delta/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './delta'

59
src/value/deref/deref.ts Normal file
View File

@@ -0,0 +1,59 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import type { TSchema } from '../../type/schema/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import { TypeBoxError } from '../../type/error/index'
import { Kind } from '../../type/symbols/index'
import { IsString } from '../guard/guard'
export class TypeDereferenceError extends TypeBoxError {
constructor(public readonly schema: TRef | TThis) {
super(`Unable to dereference schema with $id '${schema.$ref}'`)
}
}
function Resolve(schema: TThis | TRef, references: TSchema[]): TSchema {
const target = references.find((target) => target.$id === schema.$ref)
if (target === undefined) throw new TypeDereferenceError(schema)
return Deref(target, references)
}
/** `[Internal]` Pushes a schema onto references if the schema has an $id and does not exist on references */
export function Pushref(schema: TSchema, references: TSchema[]): TSchema[] {
if (!IsString(schema.$id) || references.some((target) => target.$id === schema.$id)) return references
references.push(schema)
return references
}
/** `[Internal]` Dereferences a schema from the references array or throws if not found */
export function Deref(schema: TSchema, references: TSchema[]): TSchema {
// prettier-ignore
return (schema[Kind] === 'This' || schema[Kind] === 'Ref')
? Resolve(schema as never, references)
: schema
}

29
src/value/deref/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './deref'

View File

@@ -0,0 +1,46 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { HasTransform, TransformEncode, TransformEncodeCheckError } from '../transform/index'
import { Check } from '../check/index'
import { Errors } from '../../errors/index'
import type { TSchema } from '../../type/schema/index'
import type { StaticEncode } from '../../type/static/index'
/** Encodes a value or throws if error */
export function Encode<T extends TSchema, Static = StaticEncode<T>, Result extends Static = Static>(schema: T, references: TSchema[], value: unknown): Result
/** Encodes a value or throws if error */
export function Encode<T extends TSchema, Static = StaticEncode<T>, Result extends Static = Static>(schema: T, value: unknown): Result
/** Encodes a value or throws if error */
export function Encode(...args: any[]): any {
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
const encoded = HasTransform(schema, references) ? TransformEncode(schema, references, value) : value
if (!Check(schema, references, encoded)) throw new TransformEncodeCheckError(schema, encoded, Errors(schema, references, encoded).First()!)
return encoded
}

29
src/value/encode/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './encode'

67
src/value/equal/equal.ts Normal file
View File

@@ -0,0 +1,67 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsObject, IsDate, IsArray, IsTypedArray, IsValueType } from '../guard/index'
import type { ObjectType, ArrayType, TypedArrayType, ValueType } from '../guard/index'
// ------------------------------------------------------------------
// Equality Checks
// ------------------------------------------------------------------
function ObjectType(left: ObjectType, right: unknown): boolean {
if (!IsObject(right)) return false
const leftKeys = [...Object.keys(left), ...Object.getOwnPropertySymbols(left)]
const rightKeys = [...Object.keys(right), ...Object.getOwnPropertySymbols(right)]
if (leftKeys.length !== rightKeys.length) return false
return leftKeys.every((key) => Equal(left[key], right[key]))
}
function DateType(left: Date, right: unknown): any {
return IsDate(right) && left.getTime() === right.getTime()
}
function ArrayType(left: ArrayType, right: unknown): any {
if (!IsArray(right) || left.length !== right.length) return false
return left.every((value, index) => Equal(value, right[index]))
}
function TypedArrayType(left: TypedArrayType, right: unknown): any {
if (!IsTypedArray(right) || left.length !== right.length || Object.getPrototypeOf(left).constructor.name !== Object.getPrototypeOf(right).constructor.name) return false
return left.every((value, index) => Equal(value, right[index]))
}
function ValueType(left: ValueType, right: unknown): any {
return left === right
}
// ------------------------------------------------------------------
// Equal
// ------------------------------------------------------------------
/** Returns true if the left value deep-equals the right */
export function Equal<T>(left: T, right: unknown): right is T {
if (IsDate(left)) return DateType(left, right)
if (IsTypedArray(left)) return TypedArrayType(left, right)
if (IsArray(left)) return ArrayType(left, right)
if (IsObject(left)) return ObjectType(left, right)
if (IsValueType(left)) return ValueType(left, right)
throw new Error('ValueEquals: Unable to compare value')
}

29
src/value/equal/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './equal'

207
src/value/guard/guard.ts Normal file
View File

@@ -0,0 +1,207 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
// ------------------------------------------------------------------
// Types
// ------------------------------------------------------------------
export type ObjectType = Record<PropertyKey, unknown>
export type ArrayType = unknown[]
export type ValueType = null | undefined | symbol | bigint | number | boolean | string
// prettier-ignore
export type TypedArrayType =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array
// --------------------------------------------------------------------------
// Iterators
// --------------------------------------------------------------------------
/** Returns true if this value is an async iterator */
export function IsAsyncIterator(value: unknown): value is AsyncIterableIterator<any> {
return IsObject(value) && globalThis.Symbol.asyncIterator in value
}
/** Returns true if this value is an iterator */
export function IsIterator(value: unknown): value is IterableIterator<any> {
return IsObject(value) && globalThis.Symbol.iterator in value
}
// --------------------------------------------------------------------------
// Object Instances
// --------------------------------------------------------------------------
/** Returns true if this value is not an instance of a class */
export function IsStandardObject(value: unknown): value is ObjectType {
return IsObject(value) && (globalThis.Object.getPrototypeOf(value) === Object.prototype || globalThis.Object.getPrototypeOf(value) === null)
}
/** Returns true if this value is an instance of a class */
export function IsInstanceObject(value: unknown): value is ObjectType {
return IsObject(value) && !IsArray(value) && IsFunction(value.constructor) && value.constructor.name !== 'Object'
}
// --------------------------------------------------------------------------
// JavaScript
// --------------------------------------------------------------------------
/** Returns true if this value is a Promise */
export function IsPromise(value: unknown): value is Promise<unknown> {
return value instanceof globalThis.Promise
}
/** Returns true if this value is a Date */
export function IsDate(value: unknown): value is Date {
return value instanceof Date && globalThis.Number.isFinite(value.getTime())
}
/** Returns true if this value is an instance of Map<K, T> */
export function IsMap(value: unknown): value is Map<unknown, unknown> {
return value instanceof globalThis.Map
}
/** Returns true if this value is an instance of Set<T> */
export function IsSet(value: unknown): value is Set<unknown> {
return value instanceof globalThis.Set
}
/** Returns true if this value is RegExp */
export function IsRegExp(value: unknown): value is RegExp {
return value instanceof globalThis.RegExp
}
/** Returns true if this value is a typed array */
export function IsTypedArray(value: unknown): value is TypedArrayType {
return globalThis.ArrayBuffer.isView(value)
}
/** Returns true if the value is a Int8Array */
export function IsInt8Array(value: unknown): value is Int8Array {
return value instanceof globalThis.Int8Array
}
/** Returns true if the value is a Uint8Array */
export function IsUint8Array(value: unknown): value is Uint8Array {
return value instanceof globalThis.Uint8Array
}
/** Returns true if the value is a Uint8ClampedArray */
export function IsUint8ClampedArray(value: unknown): value is Uint8ClampedArray {
return value instanceof globalThis.Uint8ClampedArray
}
/** Returns true if the value is a Int16Array */
export function IsInt16Array(value: unknown): value is Int16Array {
return value instanceof globalThis.Int16Array
}
/** Returns true if the value is a Uint16Array */
export function IsUint16Array(value: unknown): value is Uint16Array {
return value instanceof globalThis.Uint16Array
}
/** Returns true if the value is a Int32Array */
export function IsInt32Array(value: unknown): value is Int32Array {
return value instanceof globalThis.Int32Array
}
/** Returns true if the value is a Uint32Array */
export function IsUint32Array(value: unknown): value is Uint32Array {
return value instanceof globalThis.Uint32Array
}
/** Returns true if the value is a Float32Array */
export function IsFloat32Array(value: unknown): value is Float32Array {
return value instanceof globalThis.Float32Array
}
/** Returns true if the value is a Float64Array */
export function IsFloat64Array(value: unknown): value is Float64Array {
return value instanceof globalThis.Float64Array
}
/** Returns true if the value is a BigInt64Array */
export function IsBigInt64Array(value: unknown): value is BigInt64Array {
return value instanceof globalThis.BigInt64Array
}
/** Returns true if the value is a BigUint64Array */
export function IsBigUint64Array(value: unknown): value is BigUint64Array {
return value instanceof globalThis.BigUint64Array
}
// --------------------------------------------------------------------------
// PropertyKey
// --------------------------------------------------------------------------
/** Returns true if this value has this property key */
export function HasPropertyKey<K extends PropertyKey>(value: Record<any, unknown>, key: K): value is Record<PropertyKey, unknown> & { [_ in K]: unknown } {
return key in value
}
// --------------------------------------------------------------------------
// Standard
// --------------------------------------------------------------------------
/** Returns true of this value is an object type */
export function IsObject(value: unknown): value is ObjectType {
return value !== null && typeof value === 'object'
}
/** Returns true if this value is an array, but not a typed array */
export function IsArray(value: unknown): value is ArrayType {
return globalThis.Array.isArray(value) && !globalThis.ArrayBuffer.isView(value)
}
/** Returns true if this value is an undefined */
export function IsUndefined(value: unknown): value is undefined {
return value === undefined
}
/** Returns true if this value is an null */
export function IsNull(value: unknown): value is null {
return value === null
}
/** Returns true if this value is an boolean */
export function IsBoolean(value: unknown): value is boolean {
return typeof value === 'boolean'
}
/** Returns true if this value is an number */
export function IsNumber(value: unknown): value is number {
return typeof value === 'number'
}
/** Returns true if this value is an integer */
export function IsInteger(value: unknown): value is number {
return globalThis.Number.isInteger(value)
}
/** Returns true if this value is bigint */
export function IsBigInt(value: unknown): value is bigint {
return typeof value === 'bigint'
}
/** Returns true if this value is string */
export function IsString(value: unknown): value is string {
return typeof value === 'string'
}
/** Returns true if this value is a function */
export function IsFunction(value: unknown): value is Function {
return typeof value === 'function'
}
/** Returns true if this value is a symbol */
export function IsSymbol(value: unknown): value is symbol {
return typeof value === 'symbol'
}
/** Returns true if this value is a value type such as number, string, boolean */
export function IsValueType(value: unknown): value is ValueType {
// prettier-ignore
return (
IsBigInt(value) ||
IsBoolean(value) ||
IsNull(value) ||
IsNumber(value) ||
IsString(value) ||
IsSymbol(value) ||
IsUndefined(value)
)
}

29
src/value/guard/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './guard'

162
src/value/hash/hash.ts Normal file
View File

@@ -0,0 +1,162 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsArray, IsBoolean, IsBigInt, IsDate, IsNull, IsNumber, IsObject, IsString, IsSymbol, IsUint8Array, IsUndefined } from '../guard/index'
import { TypeBoxError } from '../../type/error/index'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValueHashError extends TypeBoxError {
constructor(public readonly value: unknown) {
super(`Unable to hash value`)
}
}
// ------------------------------------------------------------------
// ByteMarker
// ------------------------------------------------------------------
enum ByteMarker {
Undefined,
Null,
Boolean,
Number,
String,
Object,
Array,
Date,
Uint8Array,
Symbol,
BigInt,
}
// ------------------------------------------------------------------
// State
// ------------------------------------------------------------------
let Accumulator = BigInt('14695981039346656037')
const [Prime, Size] = [BigInt('1099511628211'), BigInt('18446744073709551616' /* 2 ^ 64 */)]
const Bytes = Array.from({ length: 256 }).map((_, i) => BigInt(i))
const F64 = new Float64Array(1)
const F64In = new DataView(F64.buffer)
const F64Out = new Uint8Array(F64.buffer)
// ------------------------------------------------------------------
// NumberToBytes
// ------------------------------------------------------------------
function* NumberToBytes(value: number): IterableIterator<number> {
const byteCount = value === 0 ? 1 : Math.ceil(Math.floor(Math.log2(value) + 1) / 8)
for (let i = 0; i < byteCount; i++) {
yield (value >> (8 * (byteCount - 1 - i))) & 0xff
}
}
// ------------------------------------------------------------------
// Hashing Functions
// ------------------------------------------------------------------
function ArrayType(value: Array<unknown>) {
FNV1A64(ByteMarker.Array)
for (const item of value) {
Visit(item)
}
}
function BooleanType(value: boolean) {
FNV1A64(ByteMarker.Boolean)
FNV1A64(value ? 1 : 0)
}
function BigIntType(value: bigint) {
FNV1A64(ByteMarker.BigInt)
F64In.setBigInt64(0, value)
for (const byte of F64Out) {
FNV1A64(byte)
}
}
function DateType(value: Date) {
FNV1A64(ByteMarker.Date)
Visit(value.getTime())
}
function NullType(value: null) {
FNV1A64(ByteMarker.Null)
}
function NumberType(value: number) {
FNV1A64(ByteMarker.Number)
F64In.setFloat64(0, value)
for (const byte of F64Out) {
FNV1A64(byte)
}
}
function ObjectType(value: Record<string, unknown>) {
FNV1A64(ByteMarker.Object)
for (const key of globalThis.Object.getOwnPropertyNames(value).sort()) {
Visit(key)
Visit(value[key])
}
}
function StringType(value: string) {
FNV1A64(ByteMarker.String)
for (let i = 0; i < value.length; i++) {
for (const byte of NumberToBytes(value.charCodeAt(i))) {
FNV1A64(byte)
}
}
}
function SymbolType(value: symbol) {
FNV1A64(ByteMarker.Symbol)
Visit(value.description)
}
function Uint8ArrayType(value: Uint8Array) {
FNV1A64(ByteMarker.Uint8Array)
for (let i = 0; i < value.length; i++) {
FNV1A64(value[i])
}
}
function UndefinedType(value: undefined) {
return FNV1A64(ByteMarker.Undefined)
}
function Visit(value: any) {
if (IsArray(value)) return ArrayType(value)
if (IsBoolean(value)) return BooleanType(value)
if (IsBigInt(value)) return BigIntType(value)
if (IsDate(value)) return DateType(value)
if (IsNull(value)) return NullType(value)
if (IsNumber(value)) return NumberType(value)
if (IsObject(value)) return ObjectType(value)
if (IsString(value)) return StringType(value)
if (IsSymbol(value)) return SymbolType(value)
if (IsUint8Array(value)) return Uint8ArrayType(value)
if (IsUndefined(value)) return UndefinedType(value)
throw new ValueHashError(value)
}
function FNV1A64(byte: number) {
Accumulator = Accumulator ^ Bytes[byte]
Accumulator = (Accumulator * Prime) % Size
}
// ------------------------------------------------------------------
// Hash
// ------------------------------------------------------------------
/** Creates a FNV1A-64 non cryptographic hash of the given value */
export function Hash(value: unknown) {
Accumulator = BigInt('14695981039346656037')
Visit(value)
return Accumulator
}

29
src/value/hash/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './hash'

60
src/value/index.ts Normal file
View File

@@ -0,0 +1,60 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
// ------------------------------------------------------------------
// Errors (re-export)
// ------------------------------------------------------------------
export { ValueError, ValueErrorType, ValueErrorIterator } from '../errors/index'
// ------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------
export * from './guard/index'
// ------------------------------------------------------------------
// Operators
// ------------------------------------------------------------------
export * from './assert/index'
export * from './cast/index'
export * from './check/index'
export * from './clean/index'
export * from './clone/index'
export * from './convert/index'
export * from './create/index'
export * from './decode/index'
export * from './default/index'
export * from './delta/index'
export * from './encode/index'
export * from './equal/index'
export * from './hash/index'
export * from './mutate/index'
export * from './parse/index'
export * from './pointer/index'
export * from './transform/index'
// ------------------------------------------------------------------
// Namespace
// ------------------------------------------------------------------
export { Value } from './value/index'

29
src/value/mutate/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './mutate'

123
src/value/mutate/mutate.ts Normal file
View File

@@ -0,0 +1,123 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsObject, IsArray, IsTypedArray, IsValueType, type TypedArrayType } from '../guard/index'
import { ValuePointer } from '../pointer/index'
import { Clone } from '../clone/index'
import { TypeBoxError } from '../../type/error/index'
// ------------------------------------------------------------------
// IsStandardObject
// ------------------------------------------------------------------
function IsStandardObject(value: unknown): value is Record<PropertyKey, unknown> {
return IsObject(value) && !IsArray(value)
}
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValueMutateError extends TypeBoxError {
constructor(message: string) {
super(message)
}
}
// ------------------------------------------------------------------
// Mutators
// ------------------------------------------------------------------
export type Mutable = { [key: string]: unknown } | unknown[]
function ObjectType(root: Mutable, path: string, current: unknown, next: Record<string, unknown>) {
if (!IsStandardObject(current)) {
ValuePointer.Set(root, path, Clone(next))
} else {
const currentKeys = Object.getOwnPropertyNames(current)
const nextKeys = Object.getOwnPropertyNames(next)
for (const currentKey of currentKeys) {
if (!nextKeys.includes(currentKey)) {
delete current[currentKey]
}
}
for (const nextKey of nextKeys) {
if (!currentKeys.includes(nextKey)) {
current[nextKey] = null
}
}
for (const nextKey of nextKeys) {
Visit(root, `${path}/${nextKey}`, current[nextKey], next[nextKey])
}
}
}
function ArrayType(root: Mutable, path: string, current: unknown, next: unknown[]) {
if (!IsArray(current)) {
ValuePointer.Set(root, path, Clone(next))
} else {
for (let index = 0; index < next.length; index++) {
Visit(root, `${path}/${index}`, current[index], next[index])
}
current.splice(next.length)
}
}
function TypedArrayType(root: Mutable, path: string, current: unknown, next: TypedArrayType) {
if (IsTypedArray(current) && current.length === next.length) {
for (let i = 0; i < current.length; i++) {
current[i] = next[i]
}
} else {
ValuePointer.Set(root, path, Clone(next))
}
}
function ValueType(root: Mutable, path: string, current: unknown, next: unknown) {
if (current === next) return
ValuePointer.Set(root, path, next)
}
function Visit(root: Mutable, path: string, current: unknown, next: unknown) {
if (IsArray(next)) return ArrayType(root, path, current, next)
if (IsTypedArray(next)) return TypedArrayType(root, path, current, next)
if (IsStandardObject(next)) return ObjectType(root, path, current, next)
if (IsValueType(next)) return ValueType(root, path, current, next)
}
// ------------------------------------------------------------------
// IsNonMutableValue
// ------------------------------------------------------------------
function IsNonMutableValue(value: unknown): value is Mutable {
return IsTypedArray(value) || IsValueType(value)
}
function IsMismatchedValue(current: unknown, next: unknown) {
// prettier-ignore
return (
(IsStandardObject(current) && IsArray(next)) ||
(IsArray(current) && IsStandardObject(next))
)
}
// ------------------------------------------------------------------
// Mutate
// ------------------------------------------------------------------
/** `[Mutable]` Performs a deep mutable value assignment while retaining internal references */
export function Mutate(current: Mutable, next: Mutable): void {
if (IsNonMutableValue(current) || IsNonMutableValue(next)) throw new ValueMutateError('Only object and array types can be mutated at the root level')
if (IsMismatchedValue(current, next)) throw new ValueMutateError('Cannot assign due type mismatch of assignable values')
Visit(current, '', current, next)
}

29
src/value/parse/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './parse'

130
src/value/parse/parse.ts Normal file
View File

@@ -0,0 +1,130 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeBoxError } from '../../type/error/index'
import { TransformDecode, TransformEncode, HasTransform } from '../transform/index'
import { TSchema } from '../../type/schema/index'
import { StaticDecode } from '../../type/static/index'
import { Assert } from '../assert/index'
import { Cast } from '../cast/index'
import { Clean } from '../clean/index'
import { Clone } from '../clone/index'
import { Convert } from '../convert/index'
import { Default } from '../default/index'
// ------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------
import { IsArray, IsUndefined } from '../guard/index'
// ------------------------------------------------------------------
// Error
// ------------------------------------------------------------------
export class ParseError extends TypeBoxError {
constructor(message: string) {
super(message)
}
}
// ------------------------------------------------------------------
// ParseRegistry
// ------------------------------------------------------------------
export type TParseOperation = 'Assert' | 'Cast' | 'Clean' | 'Clone' | 'Convert' | 'Decode' | 'Default' | 'Encode' | ({} & string)
export type TParseFunction = (type: TSchema, references: TSchema[], value: unknown) => unknown
// prettier-ignore
export namespace ParseRegistry {
const registry = new Map<string, TParseFunction>([
['Assert', (type, references, value: unknown) => { Assert(type, references, value); return value }],
['Cast', (type, references, value: unknown) => Cast(type, references, value)],
['Clean', (type, references, value: unknown) => Clean(type, references, value)],
['Clone', (_type, _references, value: unknown) => Clone(value)],
['Convert', (type, references, value: unknown) => Convert(type, references, value)],
['Decode', (type, references, value: unknown) => (HasTransform(type, references) ? TransformDecode(type, references, value) : value)],
['Default', (type, references, value: unknown) => Default(type, references, value)],
['Encode', (type, references, value: unknown) => (HasTransform(type, references) ? TransformEncode(type, references, value) : value)],
])
// Deletes an entry from the registry
export function Delete(key: string): void {
registry.delete(key)
}
// Sets an entry in the registry
export function Set(key: string, callback: TParseFunction): void {
registry.set(key, callback)
}
// Gets an entry in the registry
export function Get(key: string): TParseFunction | undefined {
return registry.get(key)
}
}
// ------------------------------------------------------------------
// Default Parse Pipeline
// ------------------------------------------------------------------
// prettier-ignore
export const ParseDefault = [
'Clone',
'Clean',
'Default',
'Convert',
'Assert',
'Decode'
] as const
// ------------------------------------------------------------------
// ParseValue
// ------------------------------------------------------------------
function ParseValue<Type extends TSchema, Result extends StaticDecode<Type> = StaticDecode<Type>>(operations: TParseOperation[], type: Type, references: TSchema[], value: unknown): Result {
return operations.reduce((value, operationKey) => {
const operation = ParseRegistry.Get(operationKey)
if (IsUndefined(operation)) throw new ParseError(`Unable to find Parse operation '${operationKey}'`)
return operation(type, references, value)
}, value) as Result
}
// ------------------------------------------------------------------
// Parse
// ------------------------------------------------------------------
/** Parses a value using the default parse pipeline. Will throws an `AssertError` if invalid. */
export function Parse<Type extends TSchema, Output = StaticDecode<Type>, Result extends Output = Output>(schema: Type, references: TSchema[], value: unknown): Result
/** Parses a value using the default parse pipeline. Will throws an `AssertError` if invalid. */
export function Parse<Type extends TSchema, Output = StaticDecode<Type>, Result extends Output = Output>(schema: Type, value: unknown): Result
/** Parses a value using the specified operations. */
export function Parse<Type extends TSchema>(operations: TParseOperation[], schema: Type, references: TSchema[], value: unknown): unknown
/** Parses a value using the specified operations. */
export function Parse<Type extends TSchema>(operations: TParseOperation[], schema: Type, value: unknown): unknown
/** Parses a value */
export function Parse(...args: any[]): unknown {
// prettier-ignore
const [operations, schema, references, value] = (
args.length === 4 ? [args[0], args[1], args[2], args[3]] :
args.length === 3 ? IsArray(args[0]) ? [args[0], args[1], [], args[2]] : [ParseDefault, args[0], args[1], args[2]] :
args.length === 2 ? [ParseDefault, args[0], [], args[1]] :
(() => { throw new ParseError('Invalid Arguments') })()
)
return ParseValue(operations, schema, references, value)
}

View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * as ValuePointer from './pointer'

View File

@@ -0,0 +1,127 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeBoxError } from '../../type/error/index'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class ValuePointerRootSetError extends TypeBoxError {
constructor(public readonly value: unknown, public readonly path: string, public readonly update: unknown) {
super('Cannot set root value')
}
}
export class ValuePointerRootDeleteError extends TypeBoxError {
constructor(public readonly value: unknown, public readonly path: string) {
super('Cannot delete root value')
}
}
// ------------------------------------------------------------------
// ValuePointer
// ------------------------------------------------------------------
/** Provides functionality to update values through RFC6901 string pointers */
// prettier-ignore
function Escape(component: string) {
return component.indexOf('~') === -1 ? component : component.replace(/~1/g, '/').replace(/~0/g, '~')
}
/** Formats the given pointer into navigable key components */
// prettier-ignore
export function* Format(pointer: string): IterableIterator<string> {
if (pointer === '') return
let [start, end] = [0, 0]
for (let i = 0; i < pointer.length; i++) {
const char = pointer.charAt(i)
if (char === '/') {
if (i === 0) {
start = i + 1
} else {
end = i
yield Escape(pointer.slice(start, end))
start = i + 1
}
} else {
end = i
}
}
yield Escape(pointer.slice(start))
}
/** Sets the value at the given pointer. If the value at the pointer does not exist it is created */
// prettier-ignore
export function Set(value: any, pointer: string, update: unknown): void {
if (pointer === '') throw new ValuePointerRootSetError(value, pointer, update)
let [owner, next, key] = [null as any, value, '']
for (const component of Format(pointer)) {
if (next[component] === undefined) next[component] = {}
owner = next
next = next[component]
key = component
}
owner[key] = update
}
/** Deletes a value at the given pointer */
// prettier-ignore
export function Delete(value: any, pointer: string): void {
if (pointer === '') throw new ValuePointerRootDeleteError(value, pointer)
let [owner, next, key] = [null as any, value as any, '']
for (const component of Format(pointer)) {
if (next[component] === undefined || next[component] === null) return
owner = next
next = next[component]
key = component
}
if (Array.isArray(owner)) {
const index = parseInt(key)
owner.splice(index, 1)
} else {
delete owner[key]
}
}
/** Returns true if a value exists at the given pointer */
// prettier-ignore
export function Has(value: any, pointer: string): boolean {
if (pointer === '') return true
let [owner, next, key] = [null as any, value as any, '']
for (const component of Format(pointer)) {
if (next[component] === undefined) return false
owner = next
next = next[component]
key = component
}
return Object.getOwnPropertyNames(owner).includes(key)
}
/** Gets the value at the given pointer */
// prettier-ignore
export function Get(value: any, pointer: string): any {
if (pointer === '') return value
let current = value
for (const component of Format(pointer)) {
if (current[component] === undefined) return undefined
current = current[component]
}
return current
}

View File

@@ -0,0 +1,242 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeSystemPolicy } from '../../system/policy'
import { Kind, TransformKind } from '../../type/symbols/index'
import { TypeBoxError } from '../../type/error/index'
import { ValueError } from '../../errors/index'
import { KeyOfPropertyKeys, KeyOfPropertyEntries } from '../../type/keyof/index'
import { Deref, Pushref } from '../deref/index'
import { Check } from '../check/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TNot } from '../../type/not/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { HasPropertyKey, IsObject, IsArray, IsValueType, IsUndefined as IsUndefinedValue } from '../guard/index'
// ------------------------------------------------------------------
// KindGuard
// ------------------------------------------------------------------
import { IsTransform, IsSchema, IsUndefined } from '../../type/guard/kind'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
// thrown externally
// prettier-ignore
export class TransformDecodeCheckError extends TypeBoxError {
constructor(
public readonly schema: TSchema,
public readonly value: unknown,
public readonly error: ValueError
) {
super(`Unable to decode value as it does not match the expected schema`)
}
}
// prettier-ignore
export class TransformDecodeError extends TypeBoxError {
constructor(
public readonly schema: TSchema,
public readonly path: string,
public readonly value: unknown,
public readonly error: Error,
) {
super(error instanceof Error ? error.message : 'Unknown error')
}
}
// ------------------------------------------------------------------
// Decode
// ------------------------------------------------------------------
// prettier-ignore
function Default(schema: TSchema, path: string, value: any): unknown {
try {
return IsTransform(schema) ? schema[TransformKind].Decode(value) : value
} catch (error) {
throw new TransformDecodeError(schema, path, value, error as Error)
}
}
// prettier-ignore
function FromArray(schema: TArray, references: TSchema[], path: string, value: any): unknown {
return (IsArray(value))
? Default(schema, path, value.map((value: any, index) => Visit(schema.items, references, `${path}/${index}`, value)))
: Default(schema, path, value)
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any): unknown {
if (!IsObject(value) || IsValueType(value)) return Default(schema, path, value)
const knownEntries = KeyOfPropertyEntries(schema)
const knownKeys = knownEntries.map(entry => entry[0])
const knownProperties = { ...value } as Record<PropertyKey, unknown>
for(const [knownKey, knownSchema] of knownEntries) if(knownKey in knownProperties) {
knownProperties[knownKey] = Visit(knownSchema, references, `${path}/${knownKey}`, knownProperties[knownKey])
}
if (!IsTransform(schema.unevaluatedProperties)) {
return Default(schema, path, knownProperties)
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const unevaluatedProperties = schema.unevaluatedProperties as TSchema
const unknownProperties = { ...knownProperties } as Record<PropertyKey, unknown>
for(const key of unknownKeys) if(!knownKeys.includes(key)) {
unknownProperties[key] = Default(unevaluatedProperties, `${path}/${key}`, unknownProperties[key])
}
return Default(schema, path, unknownProperties)
}
// prettier-ignore
function FromImport(schema: TImport, references: TSchema[], path: string, value: unknown): unknown {
const additional = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
const result = Visit(target, [...references, ...additional], path, value)
return Default(schema, path, result)
}
function FromNot(schema: TNot, references: TSchema[], path: string, value: any): unknown {
return Default(schema, path, Visit(schema.not, references, path, value))
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], path: string, value: any): unknown {
if (!IsObject(value)) return Default(schema, path, value)
const knownKeys = KeyOfPropertyKeys(schema) as string[]
const knownProperties = { ...value } as Record<PropertyKey, unknown>
for(const key of knownKeys) {
if(!HasPropertyKey(knownProperties, key)) continue
// if the property value is undefined, but the target is not, nor does it satisfy exact optional
// property policy, then we need to continue. This is a special case for optional property handling
// where a transforms wrapped in a optional modifiers should not run.
if(IsUndefinedValue(knownProperties[key]) && (
!IsUndefined(schema.properties[key]) ||
TypeSystemPolicy.IsExactOptionalProperty(knownProperties, key)
)) continue
// decode property
knownProperties[key] = Visit(schema.properties[key], references, `${path}/${key}`, knownProperties[key])
}
if (!IsSchema(schema.additionalProperties)) {
return Default(schema, path, knownProperties)
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const additionalProperties = schema.additionalProperties as TSchema
const unknownProperties = { ...knownProperties } as Record<PropertyKey, unknown>
for(const key of unknownKeys) if(!knownKeys.includes(key)) {
unknownProperties[key] = Default(additionalProperties, `${path}/${key}`, unknownProperties[key])
}
return Default(schema, path, unknownProperties)
}
// prettier-ignore
function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any): unknown {
if (!IsObject(value)) return Default(schema, path, value)
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
const knownKeys = new RegExp(pattern)
const knownProperties = { ...value } as Record<PropertyKey, unknown>
for(const key of Object.getOwnPropertyNames(value)) if(knownKeys.test(key)) {
knownProperties[key] = Visit(schema.patternProperties[pattern], references, `${path}/${key}`, knownProperties[key])
}
if (!IsSchema(schema.additionalProperties)) {
return Default(schema, path, knownProperties)
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const additionalProperties = schema.additionalProperties as TSchema
const unknownProperties = {...knownProperties} as Record<PropertyKey, unknown>
for(const key of unknownKeys) if(!knownKeys.test(key)) {
unknownProperties[key] = Default(additionalProperties, `${path}/${key}`, unknownProperties[key])
}
return Default(schema, path, unknownProperties)
}
// prettier-ignore
function FromRef(schema: TRef, references: TSchema[], path: string, value: any): unknown {
const target = Deref(schema, references)
return Default(schema, path, Visit(target, references, path, value))
}
// prettier-ignore
function FromThis(schema: TThis, references: TSchema[], path: string, value: any): unknown {
const target = Deref(schema, references)
return Default(schema, path, Visit(target, references, path, value))
}
// prettier-ignore
function FromTuple(schema: TTuple, references: TSchema[], path: string, value: any): unknown {
return (IsArray(value) && IsArray(schema.items))
? Default(schema, path, schema.items.map((schema, index) => Visit(schema, references, `${path}/${index}`, value[index])))
: Default(schema, path, value)
}
// prettier-ignore
function FromUnion(schema: TUnion, references: TSchema[], path: string, value: any): unknown {
for (const subschema of schema.anyOf) {
if (!Check(subschema, references, value)) continue
// note: ensure interior is decoded first
const decoded = Visit(subschema, references, path, value)
return Default(schema, path, decoded)
}
return Default(schema, path, value)
}
// prettier-ignore
function Visit(schema: TSchema, references: TSchema[], path: string, value: any): any {
const references_ = Pushref(schema, references)
const schema_ = schema as any
switch (schema[Kind]) {
case 'Array':
return FromArray(schema_, references_, path, value)
case 'Import':
return FromImport(schema_, references_, path, value)
case 'Intersect':
return FromIntersect(schema_, references_, path, value)
case 'Not':
return FromNot(schema_, references_, path, value)
case 'Object':
return FromObject(schema_, references_, path, value)
case 'Record':
return FromRecord(schema_, references_, path, value)
case 'Ref':
return FromRef(schema_, references_, path, value)
case 'Symbol':
return Default(schema_, path, value)
case 'This':
return FromThis(schema_, references_, path, value)
case 'Tuple':
return FromTuple(schema_, references_, path, value)
case 'Union':
return FromUnion(schema_, references_, path, value)
default:
return Default(schema_, path, value)
}
}
/**
* `[Internal]` Decodes the value and returns the result. This function requires that
* the caller `Check` the value before use. Passing unchecked values may result in
* undefined behavior. Refer to the `Value.Decode()` for implementation details.
*/
export function TransformDecode(schema: TSchema, references: TSchema[], value: unknown): unknown {
return Visit(schema, references, '', value)
}

View File

@@ -0,0 +1,251 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeSystemPolicy } from '../../system/policy'
import { Kind, TransformKind } from '../../type/symbols/index'
import { TypeBoxError } from '../../type/error/index'
import { ValueError } from '../../errors/index'
import { KeyOfPropertyKeys, KeyOfPropertyEntries } from '../../type/keyof/index'
import { Deref, Pushref } from '../deref/index'
import { Check } from '../check/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TNot } from '../../type/not/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { HasPropertyKey, IsObject, IsArray, IsValueType, IsUndefined as IsUndefinedValue } from '../guard/index'
// ------------------------------------------------------------------
// KindGuard
// ------------------------------------------------------------------
import { IsTransform, IsSchema, IsUndefined } from '../../type/guard/kind'
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
// prettier-ignore
export class TransformEncodeCheckError extends TypeBoxError {
constructor(
public readonly schema: TSchema,
public readonly value: unknown,
public readonly error: ValueError
) {
super(`The encoded value does not match the expected schema`)
}
}
// prettier-ignore
export class TransformEncodeError extends TypeBoxError {
constructor(
public readonly schema: TSchema,
public readonly path: string,
public readonly value: unknown,
public readonly error: Error,
) {
super(`${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
// ------------------------------------------------------------------
// Encode
// ------------------------------------------------------------------
// prettier-ignore
function Default(schema: TSchema, path: string, value: any) {
try {
return IsTransform(schema) ? schema[TransformKind].Encode(value) : value
} catch (error) {
throw new TransformEncodeError(schema, path, value, error as Error)
}
}
// prettier-ignore
function FromArray(schema: TArray, references: TSchema[], path: string, value: any): any {
const defaulted = Default(schema, path, value)
return IsArray(defaulted)
? defaulted.map((value: any, index) => Visit(schema.items, references, `${path}/${index}`, value))
: defaulted
}
// prettier-ignore
function FromImport(schema: TImport, references: TSchema[], path: string, value: unknown): unknown {
const additional = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
const result = Default(schema, path, value)
return Visit(target, [...references, ...additional], path, result)
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value)
if (!IsObject(value) || IsValueType(value)) return defaulted
const knownEntries = KeyOfPropertyEntries(schema)
const knownKeys = knownEntries.map(entry => entry[0])
const knownProperties = { ...defaulted } as Record<PropertyKey, unknown>
for(const [knownKey, knownSchema] of knownEntries) if(knownKey in knownProperties) {
knownProperties[knownKey] = Visit(knownSchema, references, `${path}/${knownKey}`, knownProperties[knownKey])
}
if (!IsTransform(schema.unevaluatedProperties)) {
return knownProperties
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const unevaluatedProperties = schema.unevaluatedProperties as TSchema
const properties = { ...knownProperties } as Record<PropertyKey, unknown>
for(const key of unknownKeys) if(!knownKeys.includes(key)) {
properties[key] = Default(unevaluatedProperties, `${path}/${key}`, properties[key])
}
return properties
}
// prettier-ignore
function FromNot(schema: TNot, references: TSchema[], path: string, value: any) {
return Default(schema.not, path, Default(schema, path, value))
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value)
if (!IsObject(defaulted)) return defaulted
const knownKeys = KeyOfPropertyKeys(schema) as string[]
const knownProperties = { ...defaulted } as Record<PropertyKey, unknown>
for(const key of knownKeys) {
if(!HasPropertyKey(knownProperties, key)) continue
// if the property value is undefined, but the target is not, nor does it satisfy exact optional
// property policy, then we need to continue. This is a special case for optional property handling
// where a transforms wrapped in a optional modifiers should not run.
if(IsUndefinedValue(knownProperties[key]) && (
!IsUndefined(schema.properties[key]) ||
TypeSystemPolicy.IsExactOptionalProperty(knownProperties, key)
)) continue
// encode property
knownProperties[key] = Visit(schema.properties[key], references, `${path}/${key}`, knownProperties[key])
}
if (!IsSchema(schema.additionalProperties)) {
return knownProperties
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const additionalProperties = schema.additionalProperties as TSchema
const properties = { ...knownProperties } as Record<PropertyKey, unknown>
for(const key of unknownKeys) if(!knownKeys.includes(key)) {
properties[key] = Default(additionalProperties, `${path}/${key}`, properties[key])
}
return properties
}
// prettier-ignore
function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value) as Record<PropertyKey, unknown>
if (!IsObject(value)) return defaulted
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
const knownKeys = new RegExp(pattern)
const knownProperties = {...defaulted } as Record<PropertyKey, unknown>
for(const key of Object.getOwnPropertyNames(value)) if(knownKeys.test(key)) {
knownProperties[key] = Visit(schema.patternProperties[pattern], references, `${path}/${key}`, knownProperties[key])
}
if (!IsSchema(schema.additionalProperties)) {
return knownProperties
}
const unknownKeys = Object.getOwnPropertyNames(knownProperties)
const additionalProperties = schema.additionalProperties as TSchema
const properties = { ...knownProperties } as Record<PropertyKey, unknown>
for(const key of unknownKeys) if(!knownKeys.test(key)) {
properties[key] = Default(additionalProperties, `${path}/${key}`, properties[key])
}
return properties
}
// prettier-ignore
function FromRef(schema: TRef, references: TSchema[], path: string, value: any) {
const target = Deref(schema, references)
const resolved = Visit(target, references, path, value)
return Default(schema, path, resolved)
}
// prettier-ignore
function FromThis(schema: TThis, references: TSchema[], path: string, value: any) {
const target = Deref(schema, references)
const resolved = Visit(target, references, path, value)
return Default(schema, path, resolved)
}
// prettier-ignore
function FromTuple(schema: TTuple, references: TSchema[], path: string, value: any) {
const value1 = Default(schema, path, value)
return IsArray(schema.items) ? schema.items.map((schema, index) => Visit(schema, references, `${path}/${index}`, value1[index])) : []
}
// prettier-ignore
function FromUnion(schema: TUnion, references: TSchema[], path: string, value: any) {
// test value against union variants
for (const subschema of schema.anyOf) {
if (!Check(subschema, references, value)) continue
const value1 = Visit(subschema, references, path, value)
return Default(schema, path, value1)
}
// test transformed value against union variants
for (const subschema of schema.anyOf) {
const value1 = Visit(subschema, references, path, value)
if (!Check(schema, references, value1)) continue
return Default(schema, path, value1)
}
return Default(schema, path, value)
}
// prettier-ignore
function Visit(schema: TSchema, references: TSchema[], path: string, value: any): any {
const references_ = Pushref(schema, references)
const schema_ = schema as any
switch (schema[Kind]) {
case 'Array':
return FromArray(schema_, references_, path, value)
case 'Import':
return FromImport(schema_, references_, path, value)
case 'Intersect':
return FromIntersect(schema_, references_, path, value)
case 'Not':
return FromNot(schema_, references_, path, value)
case 'Object':
return FromObject(schema_, references_, path, value)
case 'Record':
return FromRecord(schema_, references_, path, value)
case 'Ref':
return FromRef(schema_, references_, path, value)
case 'This':
return FromThis(schema_, references_, path, value)
case 'Tuple':
return FromTuple(schema_, references_, path, value)
case 'Union':
return FromUnion(schema_, references_, path, value)
default:
return Default(schema_, path, value)
}
}
/**
* `[Internal]` Encodes the value and returns the result. This function expects the
* caller to pass a statically checked value. This function does not check the encoded
* result, meaning the result should be passed to `Check` before use. Refer to the
* `Value.Encode()` function for implementation details.
*/
export function TransformEncode(schema: TSchema, references: TSchema[], value: unknown): unknown {
return Visit(schema, references, '', value)
}

176
src/value/transform/has.ts Normal file
View File

@@ -0,0 +1,176 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { Deref, Pushref } from '../deref/index'
import { Kind } from '../../type/symbols/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TAsyncIterator } from '../../type/async-iterator/index'
import type { TConstructor } from '../../type/constructor/index'
import type { TFunction } from '../../type/function/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TIterator } from '../../type/iterator/index'
import type { TImport } from '../../type/module/index'
import type { TNot } from '../../type/not/index'
import type { TObject } from '../../type/object/index'
import type { TPromise } from '../../type/promise/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
// ------------------------------------------------------------------
// KindGuard
// ------------------------------------------------------------------
import { IsTransform, IsSchema } from '../../type/guard/kind'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsUndefined } from '../guard/index'
// prettier-ignore
function FromArray(schema: TArray, references: TSchema[]): boolean {
return IsTransform(schema) || Visit(schema.items, references)
}
// prettier-ignore
function FromAsyncIterator(schema: TAsyncIterator, references: TSchema[]): boolean {
return IsTransform(schema) || Visit(schema.items, references)
}
// prettier-ignore
function FromConstructor(schema: TConstructor, references: TSchema[]) {
return IsTransform(schema) || Visit(schema.returns, references) || schema.parameters.some((schema) => Visit(schema, references))
}
// prettier-ignore
function FromFunction(schema: TFunction, references: TSchema[]) {
return IsTransform(schema) || Visit(schema.returns, references) || schema.parameters.some((schema) => Visit(schema, references))
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[]) {
return IsTransform(schema) || IsTransform(schema.unevaluatedProperties) || schema.allOf.some((schema) => Visit(schema, references))
}
// prettier-ignore
function FromImport(schema: TImport, references: TSchema[]) {
const additional = globalThis.Object.getOwnPropertyNames(schema.$defs).reduce((result, key) => [...result, schema.$defs[key as never]], [] as TSchema[])
const target = schema.$defs[schema.$ref]
return IsTransform(schema) || Visit(target, [...additional, ...references])
}
// prettier-ignore
function FromIterator(schema: TIterator, references: TSchema[]) {
return IsTransform(schema) || Visit(schema.items, references)
}
// prettier-ignore
function FromNot(schema: TNot, references: TSchema[]) {
return IsTransform(schema) || Visit(schema.not, references)
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[]) {
return (
IsTransform(schema) ||
Object.values(schema.properties).some((schema) => Visit(schema, references)) ||
(
IsSchema(schema.additionalProperties) && Visit(schema.additionalProperties, references)
)
)
}
// prettier-ignore
function FromPromise(schema: TPromise, references: TSchema[]) {
return IsTransform(schema) || Visit(schema.item, references)
}
// prettier-ignore
function FromRecord(schema: TRecord, references: TSchema[]) {
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
const property = schema.patternProperties[pattern]
return IsTransform(schema) || Visit(property, references) || (IsSchema(schema.additionalProperties) && IsTransform(schema.additionalProperties))
}
// prettier-ignore
function FromRef(schema: TRef, references: TSchema[]) {
if (IsTransform(schema)) return true
return Visit(Deref(schema, references), references)
}
// prettier-ignore
function FromThis(schema: TThis, references: TSchema[]) {
if (IsTransform(schema)) return true
return Visit(Deref(schema, references), references)
}
// prettier-ignore
function FromTuple(schema: TTuple, references: TSchema[]) {
return IsTransform(schema) || (!IsUndefined(schema.items) && schema.items.some((schema) => Visit(schema, references)))
}
// prettier-ignore
function FromUnion(schema: TUnion, references: TSchema[]) {
return IsTransform(schema) || schema.anyOf.some((schema) => Visit(schema, references))
}
// prettier-ignore
function Visit(schema: TSchema, references: TSchema[]): boolean {
const references_ = Pushref(schema, references)
const schema_ = schema as any
if (schema.$id && visited.has(schema.$id)) return false
if (schema.$id) visited.add(schema.$id)
switch (schema[Kind]) {
case 'Array':
return FromArray(schema_, references_)
case 'AsyncIterator':
return FromAsyncIterator(schema_, references_)
case 'Constructor':
return FromConstructor(schema_, references_)
case 'Function':
return FromFunction(schema_, references_)
case 'Import':
return FromImport(schema_, references_)
case 'Intersect':
return FromIntersect(schema_, references_)
case 'Iterator':
return FromIterator(schema_, references_)
case 'Not':
return FromNot(schema_, references_)
case 'Object':
return FromObject(schema_, references_)
case 'Promise':
return FromPromise(schema_, references_)
case 'Record':
return FromRecord(schema_, references_)
case 'Ref':
return FromRef(schema_, references_)
case 'This':
return FromThis(schema_, references_)
case 'Tuple':
return FromTuple(schema_, references_)
case 'Union':
return FromUnion(schema_, references_)
default:
return IsTransform(schema)
}
}
const visited = new Set<string>()
/** Returns true if this schema contains a transform codec */
export function HasTransform(schema: TSchema, references: TSchema[]): boolean {
visited.clear()
return Visit(schema, references)
}

View File

@@ -0,0 +1,31 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './decode'
export * from './encode'
export * from './has'

29
src/value/value/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * as Value from './value'

45
src/value/value/value.ts Normal file
View File

@@ -0,0 +1,45 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export { Errors, ValueErrorIterator } from '../../errors/index'
export { Assert } from '../assert/index'
export { Cast } from '../cast/index'
export { Check } from '../check/index'
export { Clean } from '../clean/index'
export { Clone } from '../clone/index'
export { Convert } from '../convert/index'
export { Create } from '../create/index'
export { Decode } from '../decode/index'
export { Default } from '../default/index'
export { Diff, Patch, Edit } from '../delta/index'
export { Encode } from '../encode/index'
export { Equal } from '../equal/index'
export { Hash } from '../hash/index'
export { Mutate, type Mutable } from '../mutate/index'
export { Parse } from '../parse/index'