Skip to content

Commit

Permalink
support template literal for mappped constraint
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Jul 31, 2023
1 parent 6949e50 commit 3b30273
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 81 deletions.
53 changes: 20 additions & 33 deletions example/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Type, Static } from '@sinclair/typebox'
import * as Codegen from '@typebox/codegen'
import * as util from 'node:util'

function Print(transform: string, code: any) {
const data = typeof code === 'string' ? code : util.inspect(code, false, 100)
const data = typeof code === 'string' ? Codegen.Formatter.Format(code) : util.inspect(code, false, 100)
const length = 72
console.log('┌' + '─'.repeat(length) + '┐')
console.log('│', transform.padEnd(length - 1) + '│')
Expand All @@ -12,52 +11,40 @@ function Print(transform: string, code: any) {
console.log(data)
console.log('')
}

const Code = `
export type A = {
x: number,
y: string,
z: boolean
}
export type B = {
a: number,
b: string,
c: boolean
}
export type T = A & B
}
export type T = A & B
export type M = {
[K in keyof T]:
T[K] extends string ? 'a string' :
T[K] extends number ? 'a number' :
T[K] extends boolean ? 'a boolean' :
never
}
type M = {[K in keyof T]: 1 }
`
// ----------------------------------------------------------------------------
// Typescript Base
// ----------------------------------------------------------------------------
Print('Typescript code (base)', Code)

Print('Typescript', Code)
// ----------------------------------------------------------------------------
// Immediate Transform
// TypeBox Transform
// ----------------------------------------------------------------------------
Print('TypeScript To TypeBox', Codegen.TypeScriptToTypeBox.Generate(Code))

// // ----------------------------------------------------------------------------
// // Model Transform
// // ----------------------------------------------------------------------------
const inlineModel = Codegen.TypeScriptToModel.Generate(Code, 'inline')
Print('TypeScript To Inline Model', inlineModel)
Print('Model To JsonSchema Inline', Codegen.ModelToJsonSchema.Generate(inlineModel))
Print('Model To JavaScript', Codegen.ModelToJavaScript.Generate(inlineModel))
Print('Model To TypeScript', Codegen.ModelToTypeScript.Generate(inlineModel))
Print('Model To Valibot', Codegen.ModelToValibot.Generate(inlineModel))
Print('Model To Value', Codegen.ModelToValue.Generate(inlineModel))
Print('Model To Yup', Codegen.ModelToYup.Generate(inlineModel))
Print('Model To Zod', Codegen.ModelToZod.Generate(inlineModel))

const cyclicModel = Codegen.TypeScriptToModel.Generate(Code, 'cyclic')
Print('TypeScript To Cyclic Model', cyclicModel)
Print('Model To JsonSchema Cyclic', Codegen.ModelToJsonSchema.Generate(cyclicModel))
Print('Model To ArkType', Codegen.ModelToArkType.Generate(cyclicModel))
// ----------------------------------------------------------------------------
// Model Transform
// ----------------------------------------------------------------------------
const model = Codegen.TypeScriptToModel.Generate(Code)
Print('TypeScript To Inline Model', model)
Print('Model To JsonSchema Inline', Codegen.ModelToJsonSchema.Generate(model))
Print('Model To JavaScript', Codegen.ModelToJavaScript.Generate(model))
Print('Model To TypeScript', Codegen.ModelToTypeScript.Generate(model))
Print('Model To Valibot', Codegen.ModelToValibot.Generate(model))
Print('Model To Value', Codegen.ModelToValue.Generate(model))
Print('Model To Yup', Codegen.ModelToYup.Generate(model))
Print('Model To Zod', Codegen.ModelToZod.Generate(model))
Print('Model To ArkType', Codegen.ModelToArkType.Generate(model))
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"@sinclair/typebox": "^0.30.0-dev-3",
"prettier": "^2.8.7",
"typescript": "^5.1.5"
"typescript": "^5.1.6"
},
"prettier": {
"printWidth": 240,
Expand Down
2 changes: 2 additions & 0 deletions src/model/model-to-arktype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,10 @@ export namespace ModelToArkType {
return ConstrainedNumericType('number', schema)
}
function Object(schema: Types.TObject) {
console.log(1, schema)
const properties = globalThis.Object.entries(schema.properties)
.map(([key, schema]) => {
console.log(1, key)
const optional = Types.TypeGuard.TOptional(schema)
const property1 = PropertyEncoder.Encode(key)
const property2 = optional ? `'${property1}?'` : `${property1}`
Expand Down
11 changes: 5 additions & 6 deletions src/typescript/typescript-to-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/

import { TypeScriptToTypeBox, ReferenceModel } from './typescript-to-typebox'
import { Type, Kind, TypeClone, TypeGuard, TSchema } from '@sinclair/typebox'
import { TypeScriptToTypeBox } from './typescript-to-typebox'
import { Type, Kind, TSchema, TypeClone, TypeGuard, TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralGenerator } from '@sinclair/typebox'
import { TypeBoxModel } from '../model/model'
import * as ts from 'typescript'

Expand All @@ -36,8 +36,8 @@ export namespace TypeScriptToModel {
}
export function Exports(code: string): Map<string, TSchema | Function> {
const exports = {}
const evaluate = new Function('exports', 'Type', 'TypeGuard', 'TypeClone', 'Kind', code)
evaluate(exports, Type, TypeGuard, TypeClone, Kind)
const evaluate = new Function('exports', 'Type', 'Kind', 'TypeGuard', 'TypeClone', 'TemplateLiteralParser', 'TemplateLiteralFinite', 'TemplateLiteralGenerator', code)
evaluate(exports, Type, Kind, TypeGuard, TypeClone, TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralGenerator)
return new Map(globalThis.Object.entries(exports))
}
export function Types(exports: Map<string, TSchema | Function>): TSchema[] {
Expand All @@ -48,9 +48,8 @@ export namespace TypeScriptToModel {
}
return types
}
export function Generate(typescriptCode: string, referenceModel: ReferenceModel = 'inline'): TypeBoxModel {
export function Generate(typescriptCode: string): TypeBoxModel {
const typescript = TypeScriptToTypeBox.Generate(typescriptCode, {
referenceModel: referenceModel,
useExportEverything: true,
useTypeBoxImport: false,
useIdentifiers: true,
Expand Down
47 changes: 13 additions & 34 deletions src/typescript/typescript-to-typebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,7 @@ export class TypeScriptToTypeBoxError extends Error {
// TypeScriptToTypeBox
// --------------------------------------------------------------------------

export type ReferenceModel = 'inline' | 'ordered' | 'cyclic'

export interface TypeScriptToTypeBoxOptions {
/**
* Specifies the output reference model used to reference types. The `inline` model uses
* inline ordered JavaScript references. The `ordered` model uses `Type.Ref()` and requires
* type ordering. The `cyclic` model generates synthetic references via `Type.Unsafe()` at
* a cost of losing static type information. The default is `inline`
*/
referenceModel?: ReferenceModel
/**
* Setting this to true will ensure all types are exports as const values. This setting is
* used by the TypeScriptToTypeBoxModel to gather TypeBox definitions during runtime eval
Expand Down Expand Up @@ -99,8 +90,6 @@ export namespace TypeScriptToTypeBox {
let useExportsEverything = false
// (option) inject identifiers
let useIdentifiers = false
// (option) specifies the referencing model.
let referenceModel = 'inline' as ReferenceModel
// (option) specifies if typebox imports should be included
let useTypeBoxImport = true
// ------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -447,14 +436,7 @@ export namespace TypeScriptToTypeBox {
if (name === 'Uncapitalize') return yield `Type.Uncapitalize${args}`
if (recursiveDeclaration !== null && FindRecursiveParent(recursiveDeclaration, node)) return yield `This`
if (FindTypeName(node.getSourceFile(), name) && args.length === 0 /** non-resolvable */) {
switch (referenceModel) {
case 'cyclic':
return yield `Type.Unsafe({ [Kind]: 'Ref', $ref: '${name}' })`
case 'ordered':
return yield `Type.Ref(${name})`
case 'inline':
return yield `${name}${args}`
}
return yield `${name}${args}`
}
if (name in globalThis) return yield `Type.Never()`
return yield `${name}${args}`
Expand Down Expand Up @@ -550,9 +532,6 @@ export namespace TypeScriptToTypeBox {
function ImportStatement(): string {
if (!(useImports && useTypeBoxImport)) return ''
const set = new Set<string>(['Type', 'Static'])
if (referenceModel === 'cyclic') {
set.add('Kind')
}
if (useGenerics) {
set.add('TSchema')
}
Expand All @@ -563,31 +542,32 @@ export namespace TypeScriptToTypeBox {
set.add('TypeClone')
}
if (useMapped) {
set.add('TemplateLiteralFinite')
set.add('TemplateLiteralParser')
set.add('TemplateLiteralGenerator')
set.add('TTemplateLiteral')
set.add('TypeGuard')
set.add('TSchema')
set.add('TRecord')
set.add('TUnion')
set.add('TLiteral')
}

const imports = [...set].join(', ')
return `import { ${imports} } from '@sinclair/typebox'`
}

// type MappedFunction<K, S extends TSchema = TSchema> = (key: K) => S
// type MappedParameter = TUnion<TLiteral<string>[]> | TLiteral<string>
// function Mapped<K extends MappedParameter, F extends MappedFunction<K>>(
function MappedSupport() {
return useMapped
? [
'// ---------------------------------------------------------------------------------------',
'// Type.Mapped<C, F>: TypeScript Inference Not Supported',
'// ---------------------------------------------------------------------------------------',
'type MappedConstraint = TUnion<TLiteral<string>[]> | TLiteral<string>',
'type MappedConstraint = TTemplateLiteral | TUnion<TLiteral<string>[]> | TLiteral<string>',
'type MappedFunction<C extends MappedConstraint, S extends TSchema = TSchema> = (C: C) => S',
'function Mapped<C extends MappedConstraint, F extends MappedFunction<C>>(C: C, F: F): TRecord<C, ReturnType<F>> {',
' return (TypeGuard.TUnion(C)',
' ? Type.Object(C.anyOf.reduce((A, K) => ({ ...A, [K.const]: F(K as any)}), {}))',
' return (',
' TypeGuard.TTemplateLiteral(C) ? (() => {',
' const E = TemplateLiteralParser.ParseExact(C.pattern)',
' const K = TemplateLiteralFinite.Check(E) ? [...TemplateLiteralGenerator.Generate(E)] : []',
' return Type.Object(K.reduce((A, K) => ({ ...A, [K]: F(Type.Literal(K) as any)}), {}))',
' })() :',
' TypeGuard.TUnion(C) ? Type.Object(C.anyOf.reduce((A, K) => ({ ...A, [K.const]: F(K as any)}), {}))',
' : Type.Object({ [C.const]: F(C) })) as any',
'}',
].join('\n')
Expand All @@ -597,7 +577,6 @@ export namespace TypeScriptToTypeBox {
export function Generate(typescriptCode: string, options?: TypeScriptToTypeBoxOptions) {
useExportsEverything = options?.useExportEverything ?? false
useIdentifiers = options?.useIdentifiers ?? false
referenceModel = options?.referenceModel ?? 'inline'
useTypeBoxImport = options?.useTypeBoxImport ?? true
typenames.clear()
useMapped = false
Expand Down

0 comments on commit 3b30273

Please sign in to comment.