From 697a2980e250805ec071181f07a24a059d4f2f04 Mon Sep 17 00:00:00 2001 From: EllAchE <26192612+EllAchE@users.noreply.github.com> Date: Tue, 28 May 2024 00:01:10 -0700 Subject: [PATCH 1/2] initial commit --- package.json | 1 + pages/api/typescript-to-zod.ts | 32 +++++++++++--------- pages/json-to-typescript.tsx | 37 ++++++++++++++++------- pages/json-to-zod.tsx | 55 ++++++++++++++++++++++++++++++++++ tsconfig.json | 4 +-- yarn.lock | 45 +++++++++++++++++++++++++++- 6 files changed, 147 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 11a62302..ba8b1adc 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@babel/standalone": "^7.14.7", "@graphql-codegen/core": "^1.17.10", "@graphql-codegen/flow": "^1.19.3", + "maketypes": "1.1.2", "@graphql-codegen/flow-operations": "^1.18.11", "@graphql-codegen/flow-resolvers": "^1.17.16", "@graphql-codegen/fragment-matcher": "^2.0.1", diff --git a/pages/api/typescript-to-zod.ts b/pages/api/typescript-to-zod.ts index 05b21d0a..98670ffc 100644 --- a/pages/api/typescript-to-zod.ts +++ b/pages/api/typescript-to-zod.ts @@ -10,22 +10,9 @@ export default (req: NextApiRequest, res: NextApiResponse) => { const { query, body } = req; const { skipParseJSDoc, keepComments } = query; - const filePath = - path.join(tmpDir, crypto.randomBytes(16).toString("hex")) + ".ts"; try { - const schemaGenerator = generate({ - sourceText: body, - keepComments: keepComments === "true", - skipParseJSDoc: skipParseJSDoc === "true" - }); - - const schema = schemaGenerator.getZodSchemasFile(filePath); - - const formattedSchema = schema - .split(/\r?\n/) - .slice(1) - .join("\n"); + const { formattedSchema, schemaGenerator } = tsToZod(body, keepComments, skipParseJSDoc); res .status(200) @@ -34,3 +21,20 @@ export default (req: NextApiRequest, res: NextApiResponse) => { res.status(200).json({ error: e.message }); } }; +function tsToZod(body: any, keepComments: string | string[] = "true", skipParseJSDoc: string | string[] = "true") { + const schemaGenerator = generate({ + sourceText: body, + keepComments: keepComments === "true", + skipParseJSDoc: skipParseJSDoc === "true" + }); + + const filePath = path.join(tmpDir, crypto.randomBytes(16).toString("hex")) + ".ts"; + const schema = schemaGenerator.getZodSchemasFile(filePath); + + const formattedSchema = schema + .split(/\r?\n/) + .slice(1) + .join("\n"); + return { formattedSchema, schemaGenerator }; +} + diff --git a/pages/json-to-typescript.tsx b/pages/json-to-typescript.tsx index 6163155e..0d47b2b0 100644 --- a/pages/json-to-typescript.tsx +++ b/pages/json-to-typescript.tsx @@ -4,6 +4,7 @@ import Form, { InputType } from "@components/Form"; import { useSettings } from "@hooks/useSettings"; import * as React from "react"; import { useCallback } from "react"; +import { Writable } from "stream"; interface Settings { typealias: boolean; @@ -17,6 +18,24 @@ const formFields = [ } ]; +class StringWriter extends Writable { + _data: string; + + constructor(options: any) { + super(options); + this._data = ''; + } + + _write(chunk, _encoding, callback) { + this._data += chunk; + callback(); + } + + getData() { + return this._data; + } +} + export default function JsonToTypescript() { const name = "JSON to Typescript"; @@ -26,16 +45,14 @@ export default function JsonToTypescript() { const transformer = useCallback( async ({ value }) => { - const { run } = await import("json_typegen_wasm"); - return run( - "Root", - value, - JSON.stringify({ - output_mode: settings.typealias - ? "typescript/typealias" - : "typescript" - }) - ); + const mt = await import("maketypes") + const w = new StringWriter({}) + const emitter = new mt.Emitter( new mt.StreamWriter(w), new mt.NopWriter() ); + + emitter.emit(JSON.parse(value), "RootObject") + + return w.getData() + }, [settings] ); diff --git a/pages/json-to-zod.tsx b/pages/json-to-zod.tsx index 9a0f7ba2..d37c1d47 100644 --- a/pages/json-to-zod.tsx +++ b/pages/json-to-zod.tsx @@ -1,9 +1,14 @@ +import { generate } from "ts-to-zod"; +import crypto from "crypto"; +import path from "path"; import ConversionPanel from "@components/ConversionPanel"; import { EditorPanelProps } from "@components/EditorPanel"; import Form, { InputType } from "@components/Form"; import { useSettings } from "@hooks/useSettings"; import * as React from "react"; +import os from "os"; import { useCallback } from "react"; +import { Writable } from "stream"; interface Settings { rootName: string; @@ -17,6 +22,43 @@ const formFields = [ } ]; +const tmpDir = os.tmpdir?.(); + +function tsToZod(body: any, keepComments: string | string[] = "true", skipParseJSDoc: string | string[] = "true") { + const schemaGenerator = generate({ + sourceText: body, + keepComments: keepComments === "true", + skipParseJSDoc: skipParseJSDoc === "true" + }); + + const filePath = path.join(tmpDir, crypto.randomBytes(16).toString("hex")) + ".ts"; + const schema = schemaGenerator.getZodSchemasFile(filePath); + + const formattedSchema = schema + .split(/\r?\n/) + .slice(1) + .join("\n"); + return { formattedSchema, schemaGenerator }; +} + +class StringWriter extends Writable { + _data: string; + + constructor(options: any) { + super(options); + this._data = ''; + } + + _write(chunk, _encoding, callback) { + this._data += chunk; + callback(); + } + + getData() { + return this._data; + } +} + export default function JsonToZod() { const name = "JSON to Zod Schema"; @@ -26,6 +68,19 @@ export default function JsonToZod() { const transformer = useCallback( async ({ value }) => { + // first transform to typescript interface + const mt = await import("maketypes") + const w = new StringWriter({}) + const emitter = new mt.Emitter( new mt.StreamWriter(w), new mt.NopWriter() ); + + emitter.emit(JSON.parse(value), "RootObject") + + const tsInterface = w.getData() + + // then convert the interface to zod schema + const {formattedSchema} = tsToZod(tsInterface) + // return formattedSchema + const { jsonToZod } = await import("json-to-zod"); return jsonToZod(JSON.parse(value), settings.rootName, true); }, diff --git a/tsconfig.json b/tsconfig.json index c8fc3135..068945c0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "noUnusedLocals": false, - "noUnusedParameters": true, + "noUnusedParameters": false, "removeComments": false, "preserveConstEnums": true, "sourceMap": true, @@ -33,6 +33,6 @@ "esModuleInterop": true, "isolatedModules": true }, - "exclude": ["static", "public", ".next", "node_modules", "assets"], + "exclude": ["static", "public", ".next", "node_modules", ".node_modules", "assets"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] } diff --git a/yarn.lock b/yarn.lock index 94155dce..8522f296 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4824,6 +4824,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -6654,6 +6659,13 @@ make-dir@^3.0.2: dependencies: semver "^6.0.0" +maketypes@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/maketypes/-/maketypes-1.1.2.tgz#126b0dbf70301cffc02ce7895c10a17b0d5646a5" + integrity sha512-7JWq5PX9sMXI4s2XZFLHpLaWDd/QwPdE/FDT4EkuFXs5NdWDmJF0ogM+/2o7VQoPSWRnznEJZodFW6g/aF3pvw== + dependencies: + yargs "^6.5.0" + map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -9212,7 +9224,7 @@ string-hash@1.1.3: resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= -string-width@^1.0.1: +string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -10347,6 +10359,11 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ== + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -10525,6 +10542,13 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + integrity sha512-+QQWqC2xeL0N5/TE+TY6OGEqyNRM+g2/r712PDNYgiCdXYCApXf1vzfmDSLBxfGRwV+moTq/V8FnMI24JCm2Yg== + dependencies: + camelcase "^3.0.0" + yargs@^15.3.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -10555,6 +10579,25 @@ yargs@^16.1.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^6.5.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + integrity sha512-6/QWTdisjnu5UHUzQGst+UOEuEVwIzFVGBjq3jMTFNs5WJQsH/X6nMURSaScIdF5txylr1Ao9bvbWiKi2yXbwA== + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + yargs@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.6.0.tgz#cb4050c0159bfb6bb649c0f4af550526a84619dc" From a8ab29c94dee0a5421dc50699a1631bec4c19f7f Mon Sep 17 00:00:00 2001 From: EllAchE <26192612+EllAchE@users.noreply.github.com> Date: Tue, 28 May 2024 00:07:03 -0700 Subject: [PATCH 2/2] housekeeping --- package.json | 1 - pages/json-to-zod.tsx | 8 +++----- tsconfig.json | 2 +- yarn.lock | 9 +-------- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index ba8b1adc..0a9f5506 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "json-schema-to-typescript": "^10.1.4", "json-schema-to-zod": "^0.1.2", "json-to-go": "gist:0d0b8324131c80eeb7e1df20001be32f", - "json-to-zod": "^1.1.2", "json-ts": "^1.6.4", "json_typegen_wasm": "^0.7.0", "jsonld": "^5.2.0", diff --git a/pages/json-to-zod.tsx b/pages/json-to-zod.tsx index d37c1d47..8db8c505 100644 --- a/pages/json-to-zod.tsx +++ b/pages/json-to-zod.tsx @@ -77,12 +77,10 @@ export default function JsonToZod() { const tsInterface = w.getData() - // then convert the interface to zod schema + // then convert the interface to zod schema. We do this in two steps because + // the libraries that convert directly from JSON to Zod are not as mature const {formattedSchema} = tsToZod(tsInterface) - // return formattedSchema - - const { jsonToZod } = await import("json-to-zod"); - return jsonToZod(JSON.parse(value), settings.rootName, true); + return formattedSchema }, [settings] ); diff --git a/tsconfig.json b/tsconfig.json index 068945c0..76a6c336 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,6 @@ "esModuleInterop": true, "isolatedModules": true }, - "exclude": ["static", "public", ".next", "node_modules", ".node_modules", "assets"], + "exclude": ["static", "public", ".next", "node_modules", "assets"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] } diff --git a/yarn.lock b/yarn.lock index 8522f296..90f8090d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6196,13 +6196,6 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "1.0.0" resolved "https://gist.github.com/0d0b8324131c80eeb7e1df20001be32f.git#3c19dec6061daff466b13783fea94c49e4f62e86" -json-to-zod@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/json-to-zod/-/json-to-zod-1.1.2.tgz#626d4432f329c51089120858c9404234567dda1b" - integrity sha512-6YDvnY8oOS5v1H1CWUvfNJkCI3SGbmCwWMytndPHzwyrr1K9ayNXL4rvOSf7WCy3c3Pxu2brvt4idZCkKh9gfQ== - dependencies: - prettier "^2.3.2" - json-ts@^1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/json-ts/-/json-ts-1.6.4.tgz#e4423b8ccdb3069306f4727d6a579790675abbd0" @@ -8040,7 +8033,7 @@ prettier@^2.1.1, prettier@^2.2.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== -prettier@^2.3.2, prettier@^2.4.1: +prettier@^2.4.1: version "2.7.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==