Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/light-type/src/lib/chainable/ChainableArray.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnyLightType, InferInput, InferOutput } from '../types/LightType'
import { ChainableType } from './ChainableType'
import { arrays } from '../validators'

export class ChainableArray<
TElement extends AnyLightType,
Expand All @@ -10,9 +11,11 @@ export class ChainableArray<

constructor(protected readonly elementType: TElement) {
super({
type: 'array',
parse(input, ctx) {
if (Array.isArray(input)) {
const items = new Array<TOutput>(input.length)

for (let i = 0; i < input.length; i++) {
items[i] = elementType.t.parse(
input[i],
Expand All @@ -33,4 +36,8 @@ export class ChainableArray<
},
})
}

min = (min: number) => this.pipe(arrays.min(min))
max = (max: number) => this.pipe(arrays.max(max))
length = (length: number) => this.pipe(arrays.length(length))
}
1 change: 1 addition & 0 deletions packages/light-type/src/lib/chainable/ChainableObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class ChainableObject<
const keys = Object.keys(lightObject) as TKey[]

super({
type: 'object',
parse(input, ctx) {
if (typeof input === 'object' && input !== null) {
const obj = input as TInput
Expand Down
93 changes: 93 additions & 0 deletions packages/light-type/src/lib/chainable/ChainableProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { DataType, TypeInner as OldTypeInner } from '../types/TypeInner'
import { Simplify } from '../types/utils'
import { Assertion, numbers, strings } from '../validators'

type TypeInner<A, B> = OldTypeInner<A, B> & {
pipeline: unknown[]
}

type Any = TypeInner<unknown, unknown>
type ProducerOf<T> = TypeInner<unknown, T>

type NumberExtensions<T extends Any> = T & {
min(min: number): AddExtensions<T>
max(max: number): AddExtensions<T>
}
type StringExtensions<T extends Any> = T & {
min(min: number): AddExtensions<T>
max(max: number): AddExtensions<T>
length(length: number): AddExtensions<T>
regex(regex: RegExp): AddExtensions<T>
}

type CommonExtensions<T extends Any> = T extends TypeInner<
infer TInput,
infer TOutput
>
? {
optional(): AddExtensions<
TypeInner<TInput | undefined, TOutput | undefined>
>
default(
value: TOutput
): AddExtensions<TypeInner<TInput | undefined, TOutput>>
nullable(): AddExtensions<TypeInner<TInput | null, TOutput | null>>
defaultNull(
value: TOutput
): AddExtensions<TypeInner<TInput | null, TOutput>>
}
: never

type AddExtensions<T extends Any> = CommonExtensions<T> &
(T extends ProducerOf<number>
? NumberExtensions<T>
: T extends ProducerOf<string>
? StringExtensions<T>
: T)

function Next<T extends TypeInner<any, any>>(t: T) {
return new Proxy(t as AddExtensions<T>, {
get(target, p, receiver) {
if (target.type === 'number') {
const t = target as ProducerOf<number>

switch (p) {
case 'min': {
return function (min: number) {
t.pipeline.push(numbers.min(min))
return receiver
}
}
case 'max': {
return function (min: number) {
t.pipeline.push(numbers.min(min))
return receiver
}
}
}
}

return Reflect.get(target, p, receiver)
},
})
}

function number() {
return Next<TypeInner<number, number>>({
type: 'number',
parse(input: number, context) {
return input as number
},
pipeline: [],
})
}

function string() {
return Next<TypeInner<string, string>>({
type: 'number',
parse(input: string, context) {
return input as string
},
pipeline: [],
})
}
4 changes: 4 additions & 0 deletions packages/light-type/src/lib/chainable/ChainableType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class ChainableType<TInput, TOutput = TInput>
const t = this.t

return new ChainableType<TInput | undefined, TOutput | undefined>({
type: t.type,
parse(input, ctx) {
if (input === undefined) {
return undefined
Expand All @@ -86,6 +87,7 @@ export class ChainableType<TInput, TOutput = TInput>
const t = this.t

return new ChainableType<TInput | null, TOutput | null>({
type: t.type,
parse(input, ctx) {
if (input === null) {
return null
Expand Down Expand Up @@ -117,6 +119,7 @@ export class ChainableType<TInput, TOutput = TInput>
const t = this.t

return new ChainableType<TInput | undefined, TOutput>({
type: t.type,
parse(input, ctx) {
if (input === undefined) {
return defaultValue
Expand All @@ -141,6 +144,7 @@ export class ChainableType<TInput, TOutput = TInput>
const t = this.t

return new ChainableType<TInput | null, TOutput>({
type: t.type,
parse(input, ctx) {
if (input === null) {
return defaultValue
Expand Down
15 changes: 13 additions & 2 deletions packages/light-type/src/lib/lt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import { AnyLightObject } from './types/LightObject'
import { Primitive, LiteralBase, AnyKey } from './types/utils'
import { ChainableObject } from './chainable/ChainableObject'
import { LightTypeError } from './errors/LightTypeError'
import { ChainableArray } from './chainable/ChainableArray'

import { AnyTupleInput, AnyUnionInput } from './types/creators'
Expand Down Expand Up @@ -56,6 +55,7 @@ export function array<TLightArrayElement extends AnyLightType>(
export function any() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new ChainableType<any, any>({
type: 'any',
parse(input) {
return input
},
Expand All @@ -71,6 +71,7 @@ export function any() {
*/
export function unknown() {
return new ChainableType<unknown, unknown>({
type: 'unknown',
parse(input) {
return input
},
Expand All @@ -86,6 +87,7 @@ export function unknown() {
*/
export function boolean() {
return new ChainableType<boolean, boolean>({
type: 'boolean',
parse(input, ctx) {
if (typeof input === 'boolean') {
return input
Expand All @@ -111,6 +113,7 @@ export function boolean() {
*/
export function number() {
return new ChainableType<number, number>({
type: 'number',
parse(input, ctx) {
if (typeof input === 'number') {
return input
Expand All @@ -136,6 +139,7 @@ export function number() {
*/
export function string() {
return new ChainableType<string, string>({
type: 'string',
parse(input, ctx) {
if (typeof input === 'string') {
return input
Expand All @@ -161,6 +165,7 @@ export function string() {
*/
export function date() {
return new ChainableType<Date, Date>({
type: 'date',
parse(input, ctx) {
if (input instanceof Date && !isNaN(input.valueOf())) {
return input
Expand Down Expand Up @@ -198,6 +203,7 @@ export function literal<TLiteral extends Primitive>(
const list = Array.from(values).join(', ')

return new ChainableType<LiteralBase<TLiteral>, TLiteral>({
type: 'literal',
parse(input: unknown, ctx) {
if (values.has(input as TLiteral)) {
return input as TLiteral
Expand Down Expand Up @@ -235,6 +241,7 @@ export function record<
type TOutput = Record<KeyOutput, ValueOutput>

return new ChainableType<TInput, TOutput>({
type: 'record',
parse(input, ctx) {
if (typeof input === 'object' && input !== null) {
const maybeTInput = input as TInput
Expand Down Expand Up @@ -288,6 +295,7 @@ export function map<
type TOutput = Map<KeyOutput, ValueOutput>

return new ChainableType<TInput, TOutput>({
type: 'map',
parse(_input, ctx) {
const input =
_input instanceof Map
Expand Down Expand Up @@ -343,6 +351,7 @@ export function tuple<T extends AnyTupleInput>(tuple: T) {
}

return new ChainableType<TInput, TOutput>({
type: 'tuple',
parse(input, ctx) {
if (Array.isArray(input)) {
if (input.length !== tuple.length) {
Expand Down Expand Up @@ -393,6 +402,7 @@ export function union<T extends AnyUnionInput>(types: T) {
}[number]

return new ChainableType<TInput, TOutput>({
type: 'union',
parse(input, ctx) {
for (const type of types) {
const specialContext = new Context()
Expand Down Expand Up @@ -428,6 +438,7 @@ export function union<T extends AnyUnionInput>(types: T) {
*/
export function set<TInput, TOutput>(valueType: LightType<TInput, TOutput>) {
return new ChainableType<TInput[] | Set<TInput>, Set<TOutput>>({
type: 'set',
parse(_input, ctx) {
let input = [] as TInput[]
if (_input instanceof Set) {
Expand Down Expand Up @@ -466,4 +477,4 @@ export function set<TInput, TOutput>(valueType: LightType<TInput, TOutput>) {
* // `unknown -> Date`
* ```
*/
export const pipe = createPipeFunction(unknown())
export const pipe = createPipeFunction(unknown().t)
18 changes: 18 additions & 0 deletions packages/light-type/src/lib/types/TypeInner.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import { InternalContext } from '../context/Context'

export type DataType =
| 'object'
| 'array'
| 'any'
| 'unknown'
| 'boolean'
| 'number'
| 'string'
| 'date'
| 'literal'
| 'record'
| 'map'
| 'tuple'
| 'union'
| 'set'
| 'none'

export interface TypeInner<TInput, TOutput = TInput> {
type: DataType
parse(input: unknown, context: InternalContext): TOutput
}
1 change: 1 addition & 0 deletions packages/light-type/src/lib/types/pipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function createPipeFunction<TInput, TOutput>(

function pipe(...funcs: PipeElem[]) {
return new ChainableType<TInput, any>({
type: t.type,
parse(input, ctx) {
const nextInput = t.parse(input, ctx)

Expand Down
43 changes: 43 additions & 0 deletions packages/light-type/src/lib/validators/arrays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Assertion } from './assert'

export function min(elements: number): Assertion<unknown[]> {
return (input, ctx) => {
if (input.length < elements) {
ctx.addIssue({
type: 'min',
message: 'Min Length is ' + elements,
value: elements,
})
}

return input
}
}

export function max(elements: number): Assertion<unknown[]> {
return (input, ctx) => {
if (input.length > elements) {
ctx.addIssue({
type: 'max',
message: 'Max Length is ' + elements,
value: elements,
})
}

return input
}
}

export function length(elements: number): Assertion<unknown[]> {
return (input, ctx) => {
if (input.length !== elements) {
ctx.addIssue({
type: 'length',
message: 'Expected Length is ' + elements,
value: input,
})
}

return input
}
}
1 change: 1 addition & 0 deletions packages/light-type/src/lib/validators/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * as strings from './strings'
export * as numbers from './numbers'
export * as arrays from './arrays'
export * from './assert'