sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.98k stars 157 forks source link

[feat] code generation #396

Closed loynoir closed 1 year ago

loynoir commented 1 year ago

feat

import { Type } from '@sinclair/typebox'
import { SourceCompiler } from '@sinclair/typebox/compiler'

console.log(
  SourceCompiler({
    schemas: {
      foo: Type.Object({ foo: Type.String() }, { additionalProperties: false }),
      bar: Type.Object({ bar: Type.String() }, { additionalProperties: false }),
    },
    code: { esm: true },
  })
)
export function foo(value) {
  // ...
}
export function bar(value) {
  // ...
}

ajv

https://ajv.js.org/standalone.html#generating-functions-s-for-multiple-schemas-using-the-js-library-es6-and-cjs-exports

Generating functions(s) for multiple schemas using the JS library - ES6 and CJS exports

const fs = require("fs")
const path = require("path")
const Ajv = require("ajv")
const standaloneCode = require("ajv/dist/standalone").default

const schemaFoo = {
  $id: "#/definitions/Foo",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    foo: {"$ref": "#/definitions/Bar"}
  }
}
const schemaBar = {
  $id: "#/definitions/Bar",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    bar: {type: "string"},
  },
  "required": ["bar"]
}

// For CJS, it generates an exports array, will generate
// `exports["#/definitions/Foo"] = ...;exports["#/definitions/Bar"] = ... ;`
const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true}})
let moduleCode = standaloneCode(ajv)

// Now you can write the module code to file
fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode)

typia

https://typia.io/docs/validators/is/

# INSTALL TYPIA
npm install --save typia
npm install --save-dev typescript

# GENERATE TRANSFORMED TYPESCRIPT CODES
npx typia generate \
    --input src/templates \
    --output src/generated \
    --project tsconfig.json
import typia from "typia";

import { IMember } from "../structures/IMember";

export const check = typia.createIs<IMember>();
import typia from "typia";
import { IMember } from "../structures/IMember";
export const check = (input: any): input is IMember => {
    const $is_uuid = (typia.createIs as any).is_uuid;
    const $is_email = (typia.createIs as any).is_email;
    return "object" === typeof input && 
        null !== input && 
        (
            "string" === typeof input.id && is_uuid(input.id) && 
            ("string" === typeof input.email && $is_email(input.email)) && 
            ("number" === typeof input.age && 19 <= input.age && 100 >= input.age)
        );
};
sinclairzx81 commented 1 year ago

@loynoir Hi.

Generating specific code files for AOT is considered out of scope for the TypeBox project. While the TypeCompiler does support the generation of assertion routines, the arrangement of these routines into a output file format is a task left for users.

The reason for this is there are a many different module systems, naming preferences, and various code organization preferences. Rather than trying to make all possible outputs preferences configurable, TypeBox instead just tries to provide a small API to generate the assertion routine, leaving the output / configuration / and general preferences to the users (or package authors who may want to write a CLI interface to generate)

The API for code generation is quite simple. The following generates a JavaScript ESM module to assert multiple types.

import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Type, TSchema } from '@sinclair/typebox'

function CompileFunction(name: string, schema: TSchema) {
  return `export const ${name} = (() => { ${TypeCompiler.Code(schema)} })();`
}
function CompileModule(schemas: Record<string, TSchema>): string {
  return Object.entries(schemas).reduce((acc, [name, value]) => {
    return [...acc, CompileFunction(name, value)]
  }, [] as string[]).join('\n')
}
const Code = CompileModule({
  A: Type.Object({
    x: Type.Number(),
    y: Type.Number(),
    z: Type.Number()
  }),
  B: Type.Object({
    a: Type.String(),
    b: Type.String(),
    c: Type.String()
  })
})

console.log(Code)

Which produces the following output

export const A = (() => {
return function check(value) {
  return (
    (typeof value === 'object' && value !== null && !Array.isArray(value)) &&
    (typeof value.x === 'number' && Number.isFinite(value.x)) &&
    (typeof value.y === 'number' && Number.isFinite(value.y)) &&
    (typeof value.z === 'number' && Number.isFinite(value.z))
 )
} })();
export const B = (() => {
return function check(value) {
  return (
    (typeof value === 'object' && value !== null && !Array.isArray(value)) &&
    (typeof value.a === 'string') &&
    (typeof value.b === 'string') &&
    (typeof value.c === 'string')
 )
} })();

This should be enough functionality to implement a variety of code generation outputs external to TypeBox.

loynoir commented 1 year ago

But what about error details?

sinclairzx81 commented 1 year ago

@loynoir Heya,

But what about error details?

Compiler supported error generation is planned, but currently pending until there's been some formalization of error reporting established alongside the JSON Schema specification. See https://github.com/json-schema-org/json-schema-spec/issues/1396 for an issue I've submitted recently regarding this.

For now, TypeBox uses dynamic error generation, but will move to compiled once there's been some formal guidelines established with the spec (and until i18n mapping has been fully worked out in the dynamic error reporter).

So, planned, but probably not for several months.

sinclairzx81 commented 1 year ago

@loynoir Going to close this issue off as code generation is out of scope for TypeBox (as a library)

However, there are some efforts happening elsewhere to offer code generation functionality in some capacity. There is a private package currently under development which is being worked on to provide a variety of code generation cases, as well as two initial projects utilizing some of the work there. You can find these at the links below.

Ts2TypeBox

CLI code generation for TypeBox

https://github.com/xddq/ts2typebox

TypeBox WorkBench

https://sinclairzx81.github.io/typebox-workbench/

https://github.com/sinclairzx81/typebox-workbench

Will close off this issue for now, but for suggestions on code generation features (with may be inclusive of error generation), the best place to submit issues is on the typebox workbench project until some of the codegen work is made formally public.

Cheers S