diff --git a/.github/workflows/experiments.yml b/.github/workflows/experiments.yml index c53f61a0e66..c9beb6871f5 100644 --- a/.github/workflows/experiments.yml +++ b/.github/workflows/experiments.yml @@ -35,3 +35,7 @@ jobs: - name: Test Experiments (unplugin) working-directory: experiments/unplugin run: npm install && npm start + + - name: Test Experiments (setup) + working-directory: experiments/setup + run: npm install && npm start diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 09d387c8b1e..7d581ef91be 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -31,6 +31,11 @@ jobs: # pnpm install # pnpm run package:tgz + - name: Toolchain + run: | + pnpm install + pnpm build:toolchain + - name: Build working-directory: website run: | diff --git a/.gitignore b/.gitignore index 5c84beffab3..89b4485294f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,24 +2,20 @@ .codex/ bin/ !/bin -!toolchain/ttsc/bin/ -!toolchain/ttsc/bin/ttsc.js -!toolchain/ttsc/bin/ttsc-dev.js -!toolchain/ttsx/bin/ -!toolchain/ttsx/bin/ttsx.js -!packages/typia/bin/ -!packages/typia/bin/ttsc-typia.js node_modules/ + packages/*/lib/ packages/*/dist/ toolchain/*/lib/ tests/*/bin/ toolchain-tests/*/bin/ +toolchain/ttsc/native/ toolchain-tests/test-typia-ttsc/fixtures/*/dist/ toolchain-tests/test-typia-ttsc/fixtures/*/build/output/ wiki/memo/ package-lock.json +!experiments/setup/package-lock.json *.env *.log diff --git a/config/tsconfig.json b/config/tsconfig.json index 6c670e9193c..f85718880f7 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -31,12 +31,13 @@ /* Modules */ "module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ + "ignoreDeprecations": "6.0", // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": ["*"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ @@ -98,8 +99,8 @@ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ diff --git a/examples/package.json b/examples/package.json index d53cc6ab2ee..e9c70f9f5f6 100644 --- a/examples/package.json +++ b/examples/package.json @@ -4,7 +4,7 @@ "version": "12.0.2", "description": "Example codes for typia website", "scripts": { - "build": "rimraf bin && ttsc build && prettier --write bin/**/*.js", + "build": "rimraf bin && ttsc && prettier --write --ignore-path /dev/null \"bin/**/*.js\"", "dev": "ttsc --watch" }, "repository": { diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 7b5003d43d4..7e06318dddf 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -29,12 +29,12 @@ /* Modules */ "module": "esnext", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": ["*"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/experiments/setup/.gitignore b/experiments/setup/.gitignore new file mode 100644 index 00000000000..ac617faedfd --- /dev/null +++ b/experiments/setup/.gitignore @@ -0,0 +1,2 @@ +.tmp/ +node_modules/ diff --git a/experiments/setup/README.md b/experiments/setup/README.md new file mode 100644 index 00000000000..440abdcada7 --- /dev/null +++ b/experiments/setup/README.md @@ -0,0 +1,39 @@ +# Setup Wizard Experiment + +This experiment validates the published-package path of `typia setup` with npm. + +It builds local `.tgz` packages, installs them into a temporary npm project, runs +the built `typia` setup wizard, and verifies that: + +- npm installed the local tarballs into `node_modules`; +- `@typescript/native-preview`, `@typia/ttsc`, and `@typia/ttsx` are installed + by valid setup runs; +- `typia/lib/transform` is the only setup plugin path; +- `typia/lib/ttsc/plugin` is not exported as a package path; +- `bin/ttsc-typia.js` is not included in the installed package; +- the setup wizard writes a valid `tsconfig.json`. +- legacy `ts-patch` prepare steps, `dependencies.ts-patch`, and + `devDependencies.ts-patch` are removed after a valid setup. +- existing `tsconfig.json` files without `compilerOptions` receive a new + `compilerOptions` object while preserving unrelated fields such as `extends`. +- invalid `compilerOptions.plugins` values fail before dependency installation + or package.json cleanup. + +The failure rule is intentional: an invalid plugin configuration must not leave +a partially migrated package behind. Error scenarios assert both rejection and +absence of npm install/package.json side effects. + +The entrypoint is `src/index.js`. Scenario cases live under `src/features/*.js` +and are executed through `@nestia/e2e`. + +Run: + +```bash +npm run start +``` + +To reuse already-built tarballs: + +```bash +npm run start -- --skip-pack +``` diff --git a/experiments/setup/package-lock.json b/experiments/setup/package-lock.json new file mode 100644 index 00000000000..f0d681283be --- /dev/null +++ b/experiments/setup/package-lock.json @@ -0,0 +1,23 @@ +{ + "name": "@typia/experiment-setup", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@typia/experiment-setup", + "version": "0.0.0", + "license": "MIT", + "devDependencies": { + "@nestia/e2e": "^11.0.2" + } + }, + "node_modules/@nestia/e2e": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestia/e2e/-/e2e-11.0.2.tgz", + "integrity": "sha512-pkixyxTYxj8a1byyqMCJ/Jbd/BSGHoXWy2zjL8rqO9YJAg1kILSrQ8JWzKLSydaaQODi35vWcFJr3wgGz9EFbA==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/experiments/setup/package.json b/experiments/setup/package.json new file mode 100644 index 00000000000..4923d87c0ba --- /dev/null +++ b/experiments/setup/package.json @@ -0,0 +1,18 @@ +{ + "private": true, + "name": "@typia/experiment-setup", + "version": "0.0.0", + "description": "Experiment for typia setup wizard with npm and local tgz installation", + "scripts": { + "start": "node src/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/samchon/typia" + }, + "author": "Jeongho Nam", + "license": "MIT", + "devDependencies": { + "@nestia/e2e": "^11.0.2" + } +} diff --git a/experiments/setup/src/features/test_auto_detects_single_tsconfig.js b/experiments/setup/src/features/test_auto_detects_single_tsconfig.js new file mode 100644 index 00000000000..20484ef5028 --- /dev/null +++ b/experiments/setup/src/features/test_auto_detects_single_tsconfig.js @@ -0,0 +1,34 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_auto_detects_single_tsconfig(context) { + runScenario(context, { + name: "auto-detects-single-tsconfig", + project: "auto", + packageJson: {}, + tsconfig: { + compilerOptions: {}, + }, + verify: ({ tsconfig }) => { + TestValidator.equals( + "plugin list is created in auto-detected tsconfig", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + TestValidator.equals( + "strict is enabled in auto-detected tsconfig", + true, + tsconfig.compilerOptions.strict, + ); + TestValidator.equals( + "skipLibCheck is enabled in auto-detected tsconfig", + true, + tsconfig.compilerOptions.skipLibCheck, + ); + }, + }); +} + +module.exports = { + test_auto_detects_single_tsconfig, +}; diff --git a/experiments/setup/src/features/test_mixed_legacy_prepare.js b/experiments/setup/src/features/test_mixed_legacy_prepare.js new file mode 100644 index 00000000000..58c3a54124c --- /dev/null +++ b/experiments/setup/src/features/test_mixed_legacy_prepare.js @@ -0,0 +1,68 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_mixed_legacy_prepare(context) { + runScenario(context, { + name: "mixed-legacy-prepare", + scripts: { + prepare: "npm run before && ts-patch install && echo keep && typia patch", + postinstall: "echo keep-postinstall", + }, + devDependencies: { + "ts-patch": "^3.3.0", + vitest: "^3.0.0", + }, + tsconfig: { + compilerOptions: { + plugins: [ + { transform: "typia/lib/transform" }, + { name: "keep-me" }, + { transform: "typia/lib/transform" }, + ], + skipLibCheck: false, + strictNullChecks: false, + }, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "mixed legacy prepare is cleaned", + "npm run before && echo keep", + manifest.scripts.prepare, + ); + TestValidator.equals( + "postinstall script is preserved", + "echo keep-postinstall", + manifest.scripts.postinstall, + ); + TestValidator.equals( + "ts-patch dev dependency is removed", + undefined, + manifest.devDependencies["ts-patch"], + ); + TestValidator.equals( + "unrelated dev dependency is preserved", + "^3.0.0", + manifest.devDependencies.vitest, + ); + TestValidator.equals( + "plugin list is normalized", + [{ name: "keep-me" }, { transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + TestValidator.equals( + "skipLibCheck is enabled", + true, + tsconfig.compilerOptions.skipLibCheck, + ); + TestValidator.equals( + "strictNullChecks is enabled", + true, + tsconfig.compilerOptions.strictNullChecks, + ); + }, + }); +} + +module.exports = { + test_mixed_legacy_prepare, +}; diff --git a/experiments/setup/src/features/test_no_existing_tsconfig.js b/experiments/setup/src/features/test_no_existing_tsconfig.js new file mode 100644 index 00000000000..a1952b79dba --- /dev/null +++ b/experiments/setup/src/features/test_no_existing_tsconfig.js @@ -0,0 +1,55 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_no_existing_tsconfig(context) { + runScenario(context, { + name: "no-existing-tsconfig", + scripts: undefined, + devDependencies: { + "ts-patch": "^3.3.0", + eslint: "^9.0.0", + }, + tsconfig: null, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "scripts remain absent", + undefined, + manifest.scripts, + ); + TestValidator.equals( + "ts-patch dev dependency is removed", + undefined, + manifest.devDependencies["ts-patch"], + ); + TestValidator.equals( + "unrelated dev dependency is preserved", + "^9.0.0", + manifest.devDependencies.eslint, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + TestValidator.equals( + "strict is enabled", + true, + tsconfig.compilerOptions.strict, + ); + TestValidator.equals( + "strictNullChecks is enabled", + true, + tsconfig.compilerOptions.strictNullChecks, + ); + TestValidator.equals( + "skipLibCheck is enabled", + true, + tsconfig.compilerOptions.skipLibCheck, + ); + }, + }); +} + +module.exports = { + test_no_existing_tsconfig, +}; diff --git a/experiments/setup/src/features/test_package_json_deletes_empty_dependencies.js b/experiments/setup/src/features/test_package_json_deletes_empty_dependencies.js new file mode 100644 index 00000000000..aae35a80e80 --- /dev/null +++ b/experiments/setup/src/features/test_package_json_deletes_empty_dependencies.js @@ -0,0 +1,42 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_package_json_deletes_empty_dependencies(context) { + runScenario(context, { + name: "package-json-deletes-empty-dependencies", + packageJson: { + dependencies: { + "ts-patch": "^3.3.0", + }, + devDependencies: { + vitest: "^3.0.0", + }, + }, + tsconfig: { + compilerOptions: {}, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "empty dependencies object is removed", + undefined, + manifest.dependencies, + ); + TestValidator.equals( + "unrelated devDependencies are preserved", + { + vitest: "^3.0.0", + }, + manifest.devDependencies, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + }, + }); +} + +module.exports = { + test_package_json_deletes_empty_dependencies, +}; diff --git a/experiments/setup/src/features/test_package_json_deletes_empty_scripts_and_dev_dependencies.js b/experiments/setup/src/features/test_package_json_deletes_empty_scripts_and_dev_dependencies.js new file mode 100644 index 00000000000..b873a449994 --- /dev/null +++ b/experiments/setup/src/features/test_package_json_deletes_empty_scripts_and_dev_dependencies.js @@ -0,0 +1,42 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_package_json_deletes_empty_scripts_and_dev_dependencies( + context, +) { + runScenario(context, { + name: "package-json-deletes-empty-scripts-and-dev-dependencies", + packageJson: { + scripts: { + prepare: "typia patch && ts-patch install", + }, + devDependencies: { + "ts-patch": "^3.3.0", + }, + }, + tsconfig: { + compilerOptions: {}, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "empty scripts object is removed", + undefined, + manifest.scripts, + ); + TestValidator.equals( + "empty devDependencies object is removed", + undefined, + manifest.devDependencies, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + }, + }); +} + +module.exports = { + test_package_json_deletes_empty_scripts_and_dev_dependencies, +}; diff --git a/experiments/setup/src/features/test_package_json_keeps_non_legacy_prepare.js b/experiments/setup/src/features/test_package_json_keeps_non_legacy_prepare.js new file mode 100644 index 00000000000..1a43c1ed9b8 --- /dev/null +++ b/experiments/setup/src/features/test_package_json_keeps_non_legacy_prepare.js @@ -0,0 +1,44 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_package_json_keeps_non_legacy_prepare(context) { + runScenario(context, { + name: "package-json-keeps-non-legacy-prepare", + packageJson: { + scripts: { + prepare: "npm run build && node scripts/prepare.js", + }, + devDependencies: { + eslint: "^9.0.0", + }, + }, + tsconfig: { + compilerOptions: {}, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "non legacy prepare is preserved", + { + prepare: "npm run build && node scripts/prepare.js", + }, + manifest.scripts, + ); + TestValidator.equals( + "devDependencies are preserved", + { + eslint: "^9.0.0", + }, + manifest.devDependencies, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + }, + }); +} + +module.exports = { + test_package_json_keeps_non_legacy_prepare, +}; diff --git a/experiments/setup/src/features/test_package_json_preserves_unrelated_fields.js b/experiments/setup/src/features/test_package_json_preserves_unrelated_fields.js new file mode 100644 index 00000000000..7169f7e86ce --- /dev/null +++ b/experiments/setup/src/features/test_package_json_preserves_unrelated_fields.js @@ -0,0 +1,81 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_package_json_preserves_unrelated_fields(context) { + runScenario(context, { + name: "package-json-preserves-unrelated-fields", + packageJson: { + description: "consumer manifest", + scripts: { + build: "tsc -p tsconfig.json", + prepare: + "npm run prepack && ts-patch install --silent && npm run postpack", + test: "node test.js", + }, + dependencies: { + "ts-patch": "^3.3.0", + zod: "^3.25.0", + }, + devDependencies: { + "@types/node": "^24.0.0", + "ts-patch": "^3.3.0", + vitest: "^3.0.0", + }, + peerDependencies: { + typescript: "^5.0.0", + }, + }, + tsconfig: { + compilerOptions: { + plugins: [], + }, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "description is preserved", + "consumer manifest", + manifest.description, + ); + TestValidator.equals( + "scripts are cleaned and preserved", + { + build: "tsc -p tsconfig.json", + prepare: "npm run prepack && npm run postpack", + test: "node test.js", + }, + manifest.scripts, + ); + TestValidator.equals( + "dependencies remove only ts-patch", + { + zod: "^3.25.0", + }, + manifest.dependencies, + ); + TestValidator.equals( + "devDependencies remove only ts-patch", + { + "@types/node": "^24.0.0", + vitest: "^3.0.0", + }, + manifest.devDependencies, + ); + TestValidator.equals( + "peerDependencies are preserved", + { + typescript: "^5.0.0", + }, + manifest.peerDependencies, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + }, + }); +} + +module.exports = { + test_package_json_preserves_unrelated_fields, +}; diff --git a/experiments/setup/src/features/test_prepare_only_legacy.js b/experiments/setup/src/features/test_prepare_only_legacy.js new file mode 100644 index 00000000000..2c1e08a2f0c --- /dev/null +++ b/experiments/setup/src/features/test_prepare_only_legacy.js @@ -0,0 +1,55 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_prepare_only_legacy(context) { + runScenario(context, { + name: "prepare-only-legacy", + scripts: { + prepare: " ts-patch install ", + }, + devDependencies: { + "ts-patch": "^3.3.0", + }, + tsconfig: { + compilerOptions: { + plugins: [], + }, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "empty scripts object is removed", + undefined, + manifest.scripts, + ); + TestValidator.equals( + "empty devDependencies object is removed", + undefined, + manifest.devDependencies, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + TestValidator.equals( + "strict is enabled", + true, + tsconfig.compilerOptions.strict, + ); + TestValidator.equals( + "strictNullChecks is enabled", + true, + tsconfig.compilerOptions.strictNullChecks, + ); + TestValidator.equals( + "skipLibCheck is enabled", + true, + tsconfig.compilerOptions.skipLibCheck, + ); + }, + }); +} + +module.exports = { + test_prepare_only_legacy, +}; diff --git a/experiments/setup/src/features/test_prepare_with_empty_segments.js b/experiments/setup/src/features/test_prepare_with_empty_segments.js new file mode 100644 index 00000000000..14b9bcb30dd --- /dev/null +++ b/experiments/setup/src/features/test_prepare_with_empty_segments.js @@ -0,0 +1,46 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_prepare_with_empty_segments(context) { + runScenario(context, { + name: "prepare-with-empty-segments", + scripts: { + prepare: " && echo first && && ts-patch install && echo second && ", + }, + devDependencies: undefined, + tsconfig: { + compilerOptions: { + plugins: [{ name: "keep-me" }], + strict: true, + strictNullChecks: true, + skipLibCheck: true, + }, + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "empty prepare segments are removed", + "echo first && echo second", + manifest.scripts.prepare, + ); + TestValidator.equals( + "devDependencies remain absent", + undefined, + manifest.devDependencies, + ); + TestValidator.equals( + "plugin list is appended", + [{ name: "keep-me" }, { transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + TestValidator.equals( + "strict remains enabled", + true, + tsconfig.compilerOptions.strict, + ); + }, + }); +} + +module.exports = { + test_prepare_with_empty_segments, +}; diff --git a/experiments/setup/src/features/test_tsconfig_creates_missing_compiler_options.js b/experiments/setup/src/features/test_tsconfig_creates_missing_compiler_options.js new file mode 100644 index 00000000000..d0049135528 --- /dev/null +++ b/experiments/setup/src/features/test_tsconfig_creates_missing_compiler_options.js @@ -0,0 +1,58 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_tsconfig_creates_missing_compiler_options(context) { + runScenario(context, { + name: "tsconfig-creates-missing-compiler-options", + packageJson: { + scripts: { + prepare: "ts-patch install", + }, + dependencies: { + "ts-patch": "^3.3.0", + }, + devDependencies: { + "ts-patch": "^3.3.0", + }, + }, + tsconfig: { + extends: "@company/tsconfig/base.json", + }, + verify: ({ manifest, tsconfig }) => { + TestValidator.equals( + "legacy prepare is removed", + undefined, + manifest.scripts, + ); + TestValidator.equals( + "empty dependencies object is removed", + undefined, + manifest.dependencies, + ); + TestValidator.equals( + "empty devDependencies object is removed", + undefined, + manifest.devDependencies, + ); + TestValidator.equals( + "extends is preserved", + "@company/tsconfig/base.json", + tsconfig.extends, + ); + TestValidator.equals( + "compilerOptions is created", + { + skipLibCheck: true, + strict: true, + strictNullChecks: true, + plugins: [{ transform: "typia/lib/transform" }], + }, + tsconfig.compilerOptions, + ); + }, + }); +} + +module.exports = { + test_tsconfig_creates_missing_compiler_options, +}; diff --git a/experiments/setup/src/features/test_tsconfig_preserves_strict_false.js b/experiments/setup/src/features/test_tsconfig_preserves_strict_false.js new file mode 100644 index 00000000000..c412a8a0664 --- /dev/null +++ b/experiments/setup/src/features/test_tsconfig_preserves_strict_false.js @@ -0,0 +1,42 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenario } = require("../internal/runScenario"); + +async function test_tsconfig_preserves_strict_false(context) { + runScenario(context, { + name: "tsconfig-preserves-strict-false", + packageJson: {}, + tsconfig: { + compilerOptions: { + plugins: [], + strict: false, + skipLibCheck: false, + }, + }, + verify: ({ tsconfig }) => { + TestValidator.equals( + "strict false is preserved", + false, + tsconfig.compilerOptions.strict, + ); + TestValidator.equals( + "strictNullChecks is still enabled", + true, + tsconfig.compilerOptions.strictNullChecks, + ); + TestValidator.equals( + "skipLibCheck is enabled", + true, + tsconfig.compilerOptions.skipLibCheck, + ); + TestValidator.equals( + "plugin list is created", + [{ transform: "typia/lib/transform" }], + tsconfig.compilerOptions.plugins, + ); + }, + }); +} + +module.exports = { + test_tsconfig_preserves_strict_false, +}; diff --git a/experiments/setup/src/features/test_tsconfig_rejects_non_array_plugins.js b/experiments/setup/src/features/test_tsconfig_rejects_non_array_plugins.js new file mode 100644 index 00000000000..4f2341252a6 --- /dev/null +++ b/experiments/setup/src/features/test_tsconfig_rejects_non_array_plugins.js @@ -0,0 +1,73 @@ +const { TestValidator } = require("@nestia/e2e"); +const { runScenarioWithError } = require("../internal/runScenarioWithError"); + +async function test_tsconfig_rejects_non_array_plugins(context) { + runScenarioWithError(context, { + name: "tsconfig-rejects-non-array-plugins", + packageJson: { + scripts: { + prepare: "typia patch && npm run keep", + }, + dependencies: { + "ts-patch": "^3.3.0", + }, + devDependencies: { + "ts-patch": "^3.3.0", + }, + }, + tsconfig: { + compilerOptions: { + plugins: { + transform: "typia/lib/transform", + }, + }, + }, + verify: ({ error, installCommands, manifest }) => { + TestValidator.predicate( + "setup wizard rejects non-array plugins", + error instanceof Error && + error.message.includes("typia setup exited with status"), + ); + TestValidator.equals( + "failed setup does not install TypeScript Go preview", + installCommands.before.typescript, + installCommands.after.typescript, + ); + TestValidator.equals( + "failed setup does not install ttsc", + installCommands.before.ttsc, + installCommands.after.ttsc, + ); + TestValidator.equals( + "failed setup does not install ttsx", + installCommands.before.ttsx, + installCommands.after.ttsx, + ); + TestValidator.equals( + "failed setup does not rewrite package scripts", + { + prepare: "typia patch && npm run keep", + }, + manifest.scripts, + ); + TestValidator.equals( + "failed setup does not rewrite dependencies", + { + "ts-patch": "^3.3.0", + }, + manifest.dependencies, + ); + TestValidator.equals( + "failed setup does not rewrite devDependencies", + { + "ts-patch": "^3.3.0", + }, + manifest.devDependencies, + ); + }, + }); +} + +module.exports = { + test_tsconfig_rejects_non_array_plugins, +}; diff --git a/experiments/setup/src/index.js b/experiments/setup/src/index.js new file mode 100644 index 00000000000..3a268aa63ad --- /dev/null +++ b/experiments/setup/src/index.js @@ -0,0 +1,123 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +const { DynamicExecutor, TestValidator } = require("@nestia/e2e"); + +const { createContext } = require("./internal/createContext"); +const { prepareWorkspace } = require("./internal/prepareWorkspace"); + +function getArguments(key) { + const prefix = `--${key}=`; + return process.argv + .filter((arg) => arg.startsWith(prefix)) + .flatMap((arg) => + arg + .slice(prefix.length) + .split(",") + .map((value) => value.trim()) + .filter((value) => value.length !== 0), + ); +} + +async function main() { + const context = createContext({ + skipPack: process.argv.includes("--skip-pack"), + }); + prepareWorkspace(context); + if (!context.skipPack) { + context.run("pnpm package:tgz", context.root); + } + context.installTarballs(); + context.verifyInstalledPackage(); + validateFeatureLayout(); + validateInternalLayout(); + + const include = getArguments("include"); + const exclude = getArguments("exclude"); + const report = await DynamicExecutor.validate({ + prefix: "test_", + location: `${__dirname}/features`, + extension: "js", + parameters: () => [context], + onComplete: (exec) => { + if (exec.error !== null) { + console.log(` - ${exec.name}: ${exec.error.name}`); + return; + } + const elapsed = Math.max( + 0, + new Date(exec.completed_at).getTime() - + new Date(exec.started_at).getTime(), + ); + console.log(` - ${exec.name}: ${elapsed.toLocaleString()} ms`); + }, + filter: (name) => + (include.length ? include.some((str) => name.includes(str)) : true) && + (exclude.length ? exclude.every((str) => !name.includes(str)) : true), + }); + + const exceptions = report.executions + .filter((exec) => exec.error !== null) + .map((exec) => exec.error); + if (exceptions.length !== 0) { + for (const error of exceptions) { + console.error(error); + } + console.log("Failed"); + process.exit(1); + } + + context.verifyWizardInstallCommands(); + console.log("Success"); + console.log("Elapsed time", Math.max(0, report.time).toLocaleString(), "ms"); +} + +function validateFeatureLayout() { + const directory = path.join(__dirname, "features"); + const files = fs + .readdirSync(directory) + .filter((file) => file.startsWith("test_") && file.endsWith(".js")) + .sort(); + TestValidator.predicate( + "setup wizard experiment has feature files", + files.length !== 0, + ); + + for (const file of files) { + const exports = require(path.join(directory, file)); + const functions = Object.entries(exports).filter( + ([key, value]) => key.startsWith("test_") && typeof value === "function", + ); + TestValidator.equals( + `${file} exports exactly one test function`, + 1, + functions.length, + ); + TestValidator.equals( + `${file} export matches file name`, + path.basename(file, ".js"), + functions[0]?.[0], + ); + } +} + +function validateInternalLayout() { + const directory = path.join(__dirname, "internal"); + const files = fs + .readdirSync(directory) + .filter((file) => file.endsWith(".js")) + .sort(); + for (const file of files) { + const exports = require(path.join(directory, file)); + TestValidator.equals( + `${file} exports exactly its file-named function`, + [path.basename(file, ".js")], + Object.keys(exports), + ); + } +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/experiments/setup/src/internal/createContext.js b/experiments/setup/src/internal/createContext.js new file mode 100644 index 00000000000..b50e1af56b2 --- /dev/null +++ b/experiments/setup/src/internal/createContext.js @@ -0,0 +1,250 @@ +const cp = require("node:child_process"); +const fs = require("node:fs"); +const path = require("node:path"); + +const { TestValidator } = require("@nestia/e2e"); + +const { readJson } = require("./readJson"); + +function createContext(props) { + const experimentRoot = path.resolve(__dirname, "../.."); + const root = path.resolve(experimentRoot, "../.."); + const tarballs = path.join(root, "experiments", "tarballs"); + const workspace = path.join(experimentRoot, ".tmp", "project"); + const fakeBin = path.join(experimentRoot, ".tmp", "fake-bin"); + const npmLog = path.join(experimentRoot, ".tmp", "npm-wizard.log"); + const state = { + installScenarios: 0, + packageJson: null, + }; + + return { + experimentRoot, + fakeBin, + npmLog, + root, + skipPack: props.skipPack, + tarballs, + workspace, + installTarballs: () => { + installTarballs({ tarballs, workspace }); + state.packageJson = readJson(path.join(workspace, "package.json")); + }, + getWizardInstallCommandCounts: () => getWizardInstallCommandCounts(npmLog), + readBaselinePackageJson: () => structuredClone(state.packageJson), + readPackageJson: () => readJson(path.join(workspace, "package.json")), + run: runCommand, + runSetupWizard: (project) => + runSetupWizard({ + fakeBin, + project, + state, + workspace, + }), + tarball: (name) => tarball(tarballs, name), + verifyInstalledPackage: () => verifyInstalledPackage(workspace), + verifyWizardInstallCommands: () => + verifyWizardInstallCommands(npmLog, state.installScenarios), + writePackageJson: (manifest) => writePackageJson(workspace, manifest), + }; +} + +function installTarballs(context) { + runCommand( + [ + "npm install", + "--ignore-scripts", + "--no-audit", + "--no-fund", + tarball(context.tarballs, "interface"), + tarball(context.tarballs, "ttsc"), + tarball(context.tarballs, "utils"), + tarball(context.tarballs, "typia"), + "@typescript/native-preview@7.0.0-dev.20260421.2", + ].join(" "), + context.workspace, + ); +} + +function runSetupWizard(context) { + const executable = path.join( + context.workspace, + "node_modules", + "typia", + "lib", + "executable", + "typia.js", + ); + const args = [executable, "setup", "--manager", "npm"]; + if (context.project !== null) { + args.push("--project", context.project); + } + const result = cp.spawnSync(process.execPath, args, { + cwd: context.workspace, + env: { + ...process.env, + PATH: `${context.fakeBin}${path.delimiter}${process.env.PATH}`, + }, + encoding: "utf8", + }); + if (result.stdout) { + process.stdout.write(result.stdout); + } + if (result.stderr) { + process.stderr.write(result.stderr); + } + if (result.status !== 0) { + throw new Error(`typia setup exited with status ${result.status}`); + } + context.state.installScenarios += 1; +} + +function verifyInstalledPackage(workspace) { + const installedTypia = path.join(workspace, "node_modules", "typia"); + TestValidator.predicate( + "typia exposes CommonJS transform entry", + fs.existsSync(path.join(installedTypia, "lib", "transform.js")), + ); + TestValidator.predicate( + "typia exposes ESM transform entry", + fs.existsSync(path.join(installedTypia, "lib", "transform.mjs")), + ); + TestValidator.predicate( + "typia exposes ttsc generate executable", + fs.existsSync( + path.join(installedTypia, "lib", "executable", "generate", "ttsc.js"), + ), + ); + TestValidator.predicate( + "typia no longer exposes legacy ttsc plugin path", + fs.existsSync(path.join(installedTypia, "lib", "ttsc", "plugin.js")) === + false, + ); + TestValidator.predicate( + "typia package has no generated JavaScript bin wrapper", + fs.existsSync(path.join(installedTypia, "bin", "ttsc-typia.js")) === false, + ); + + const installedPackage = readJson(path.join(installedTypia, "package.json")); + TestValidator.equals( + "typia package removes legacy transform export", + undefined, + installedPackage.exports["./lib/ttsc/plugin"], + ); + TestValidator.equals( + "typia package exposes transform export object", + "object", + typeof installedPackage.exports["./lib/transform"], + ); + + const factory = require( + path.join(installedTypia, "lib", "transform.js"), + ).default; + const plugin = factory( + { transform: "typia/lib/transform" }, + { + binary: "ttsc", + cwd: workspace, + projectRoot: workspace, + tsconfig: path.join(workspace, "tsconfig.json"), + }, + ); + TestValidator.equals( + "typia transform native mode", + "typia", + plugin.native.mode, + ); + TestValidator.equals( + "typia transform native contract version", + 1, + plugin.native.contractVersion, + ); + TestValidator.predicate( + "typia transform native binary exists", + fs.existsSync(plugin.native.binary), + ); + TestValidator.equals( + "typia transform native binary path", + path.join(installedTypia, "lib", "executable", "generate", "ttsc.js"), + plugin.native.binary, + ); +} + +function verifyWizardInstallCommands(npmLog, count) { + const commands = getWizardInstallCommandCounts(npmLog); + TestValidator.equals( + "setup wizard installs TypeScript Go preview once per scenario", + count, + commands.typescript, + ); + TestValidator.equals( + "setup wizard installs ttsc once per scenario", + count, + commands.ttsc, + ); + TestValidator.equals( + "setup wizard installs ttsx once per scenario", + count, + commands.ttsx, + ); + TestValidator.predicate( + "setup wizard does not install ts-patch", + commands.legacy === 0, + ); +} + +function getWizardInstallCommandCounts(npmLog) { + const wizardLog = fs.existsSync(npmLog) ? fs.readFileSync(npmLog, "utf8") : ""; + return { + legacy: countMatches(wizardLog, "ts-patch"), + ttsc: countMatches(wizardLog, "i -D @typia/ttsc@latest"), + ttsx: countMatches(wizardLog, "i -D @typia/ttsx@latest"), + typescript: countMatches( + wizardLog, + "i -D @typescript/native-preview@latest", + ), + }; +} + +function tarball(tarballs, name) { + const file = path.join(tarballs, `${name}.tgz`); + TestValidator.predicate( + `${name} package tarball exists`, + fs.existsSync(file), + ); + return file; +} + +function runCommand(command, cwd) { + console.log(`$ ${command}`); + cp.execSync(command, { + cwd, + stdio: "inherit", + }); +} + +function writePackageJson(workspace, manifest) { + const next = { ...manifest }; + if (next.scripts === undefined) { + delete next.scripts; + } + if (next.dependencies === undefined) { + delete next.dependencies; + } + if (next.devDependencies === undefined) { + delete next.devDependencies; + } + fs.writeFileSync( + path.join(workspace, "package.json"), + JSON.stringify(next, null, 2), + "utf8", + ); +} + +function countMatches(actual, expected) { + return actual.split(expected).length - 1; +} + +module.exports = { + createContext, +}; diff --git a/experiments/setup/src/internal/prepareWorkspace.js b/experiments/setup/src/internal/prepareWorkspace.js new file mode 100644 index 00000000000..4692535aeee --- /dev/null +++ b/experiments/setup/src/internal/prepareWorkspace.js @@ -0,0 +1,36 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +function prepareWorkspace(context) { + fs.rmSync(path.join(context.experimentRoot, ".tmp"), { + recursive: true, + force: true, + }); + fs.mkdirSync(context.workspace, { recursive: true }); + fs.mkdirSync(context.fakeBin, { recursive: true }); + fs.writeFileSync( + path.join(context.workspace, "package.json"), + JSON.stringify( + { + private: true, + name: "@typia/experiment-setup-consumer", + version: "0.0.0", + }, + null, + 2, + ), + ); + fs.writeFileSync( + path.join(context.fakeBin, "npm"), + [ + "#!/usr/bin/env node", + 'const fs = require("node:fs");', + `fs.appendFileSync(${JSON.stringify(context.npmLog)}, process.argv.slice(2).join(" ") + "\\n");`, + ].join("\n"), + ); + fs.chmodSync(path.join(context.fakeBin, "npm"), 0o755); +} + +module.exports = { + prepareWorkspace, +}; diff --git a/experiments/setup/src/internal/readJson.js b/experiments/setup/src/internal/readJson.js new file mode 100644 index 00000000000..40144dad655 --- /dev/null +++ b/experiments/setup/src/internal/readJson.js @@ -0,0 +1,9 @@ +const fs = require("node:fs"); + +function readJson(file) { + return JSON.parse(fs.readFileSync(file, "utf8")); +} + +module.exports = { + readJson, +}; diff --git a/experiments/setup/src/internal/runScenario.js b/experiments/setup/src/internal/runScenario.js new file mode 100644 index 00000000000..bd16d73cca4 --- /dev/null +++ b/experiments/setup/src/internal/runScenario.js @@ -0,0 +1,52 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +const { readJson } = require("./readJson"); + +function runScenario(context, scenario) { + console.log(`Scenario: ${scenario.name}`); + fs.rmSync(path.join(context.workspace, "tsconfig.json"), { force: true }); + fs.rmSync(path.join(context.workspace, "cases", scenario.name), { + recursive: true, + force: true, + }); + + const baseline = context.readBaselinePackageJson(); + context.writePackageJson({ + ...baseline, + ...(scenario.packageJson ?? { + scripts: scenario.scripts, + dependencies: scenario.dependencies, + devDependencies: scenario.devDependencies, + }), + }); + + const project = + scenario.project === "auto" + ? path.join(context.workspace, "tsconfig.json") + : path.join(context.workspace, "cases", scenario.name, "tsconfig.json"); + fs.mkdirSync(path.dirname(project), { recursive: true }); + if (scenario.tsconfig !== null) { + fs.writeFileSync( + project, + JSON.stringify(scenario.tsconfig, null, 2), + "utf8", + ); + } + + context.runSetupWizard( + scenario.project === "auto" || scenario.tsconfig === null ? null : project, + ); + scenario.verify({ + manifest: context.readPackageJson(), + project, + tsconfig: + scenario.tsconfig === null || scenario.project === "auto" + ? readJson(path.join(context.workspace, "tsconfig.json")) + : readJson(project), + }); +} + +module.exports = { + runScenario, +}; diff --git a/experiments/setup/src/internal/runScenarioWithError.js b/experiments/setup/src/internal/runScenarioWithError.js new file mode 100644 index 00000000000..80dfceb6c9f --- /dev/null +++ b/experiments/setup/src/internal/runScenarioWithError.js @@ -0,0 +1,62 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +function runScenarioWithError(context, scenario) { + console.log(`Scenario: ${scenario.name}`); + fs.rmSync(path.join(context.workspace, "tsconfig.json"), { force: true }); + fs.rmSync(path.join(context.workspace, "cases", scenario.name), { + recursive: true, + force: true, + }); + + const baseline = context.readBaselinePackageJson(); + context.writePackageJson({ + ...baseline, + ...(scenario.packageJson ?? { + scripts: scenario.scripts, + dependencies: scenario.dependencies, + devDependencies: scenario.devDependencies, + }), + }); + + const project = + scenario.project === "auto" + ? path.join(context.workspace, "tsconfig.json") + : path.join(context.workspace, "cases", scenario.name, "tsconfig.json"); + fs.mkdirSync(path.dirname(project), { recursive: true }); + if (scenario.tsconfig !== null) { + fs.writeFileSync( + project, + JSON.stringify(scenario.tsconfig, null, 2), + "utf8", + ); + } + + const installCommands = { + before: context.getWizardInstallCommandCounts(), + }; + scenario.verify({ + error: (() => { + try { + context.runSetupWizard( + scenario.project === "auto" || scenario.tsconfig === null + ? null + : project, + ); + return null; + } catch (error) { + return error; + } + })(), + installCommands: { + ...installCommands, + after: context.getWizardInstallCommandCounts(), + }, + manifest: context.readPackageJson(), + project, + }); +} + +module.exports = { + runScenarioWithError, +}; diff --git a/experiments/tarballs/index.js b/experiments/tarballs/index.js index 4e421d69b81..c7b2597ca4b 100644 --- a/experiments/tarballs/index.js +++ b/experiments/tarballs/index.js @@ -61,6 +61,9 @@ function listTargets(baseDir) { return fs .readdirSync(baseDir, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) + .filter((entry) => + fs.existsSync(path.join(baseDir, entry.name, "package.json")), + ) .map((entry) => ({ name: entry.name, dir: path.join(baseDir, entry.name), diff --git a/experiments/unplugin/package.json b/experiments/unplugin/package.json index 5776649b472..d6a02afa3ad 100644 --- a/experiments/unplugin/package.json +++ b/experiments/unplugin/package.json @@ -14,9 +14,7 @@ "author": "Jeongho Nam", "license": "MIT", "dependencies": { - "@typia/core": "../tarballs/core.tgz", "@typia/interface": "../tarballs/interface.tgz", - "@typia/transform": "../tarballs/transform.tgz", "@typia/ttsc": "../tarballs/ttsc.tgz", "@typia/unplugin": "../tarballs/unplugin.tgz", "@typia/utils": "../tarballs/utils.tgz", diff --git a/next.bash b/next.bash index 31294972554..cced90fd643 100644 --- a/next.bash +++ b/next.bash @@ -1,4 +1,5 @@ #!/bin/bash set -e pnpm bumpp "$1" --no-commit --no-tag --no-push --recursive --yes -pnpm --filter=./packages/* -r publish --tag next --access public --no-git-checks \ No newline at end of file +pnpm --filter=./toolchain/* -r publish --tag next --access public --no-git-checks +pnpm --filter=./packages/* -r publish --tag next --access public --no-git-checks diff --git a/package.json b/package.json index c9612076378..5d0be6cf047 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:ci": "pnpm run guard:generated && pnpm run build && pnpm run build:native-consumers && pnpm run test:packages && pnpm run test:toolchain", "test:packages": "pnpm --filter=./tests/test-* -r --workspace-concurrency=1 start", "test:toolchain": "pnpm --filter @typia/ttsc test && pnpm --filter=./toolchain-tests/* -r --workspace-concurrency=1 start", - "package:latest": "pnpm --filter=./packages/* -r publish --tag latest --access public", + "package:latest": "bash -c 'pnpm --filter=./toolchain/* -r publish --tag latest --access public \"$@\" && pnpm --filter=./packages/* -r publish --tag latest --access public \"$@\"' --", "package:next": "bash next.bash", "package:tgz": "node experiments/tarballs", "release": "bumpp -r", diff --git a/packages/core/README.md b/packages/core/README.md deleted file mode 100644 index 9301b0467c6..00000000000 --- a/packages/core/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# `@typia/core` - -![Typia Logo](https://typia.io/logo.png) - -[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samchon/typia/blob/master/LICENSE) -[![NPM Version](https://img.shields.io/npm/v/typia.svg)](https://www.npmjs.com/package/typia) -[![NPM Downloads](https://img.shields.io/npm/dm/typia.svg)](https://www.npmjs.com/package/typia) -[![Build Status](https://github.com/samchon/typia/workflows/test/badge.svg)](https://github.com/samchon/typia/actions?query=workflow%3Atest) -[![Guide Documents](https://img.shields.io/badge/Guide-Documents-forestgreen)](https://typia.io/docs/) -[![Gurubase](https://img.shields.io/badge/Gurubase-Document%20Chatbot-006BFF)](https://gurubase.io/g/typia) -[![Discord Badge](https://img.shields.io/badge/discord-samchon-d91965?style=flat&labelColor=5866f2&logo=discord&logoColor=white&link=https://discord.gg/E94XhzrUCZ)](https://discord.gg/E94XhzrUCZ) - -Core compile-time code generation logic for [`typia`](https://github.com/samchon/typia). - -This is an **internal package** of `typia`. You don't need to install it directly — it is automatically included as a dependency of `typia`. - -## Key Modules - -| Module | Description | -|--------|-------------| -| `programmers/` | Code generators for `assert`, `is`, `validate`, `random`, and more | -| `factories/` | AST node factory functions for TypeScript code generation | -| `schemas/` | Metadata and Protocol Buffer schema analyzers | -| `context/` | Transformer context management (`ITypiaContext`, `ITransformOptions`) | diff --git a/packages/core/package.json b/packages/core/package.json deleted file mode 100644 index 52367bea11c..00000000000 --- a/packages/core/package.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "name": "@typia/core", - "version": "12.0.2", - "description": "Superfast runtime validators with only one line", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "default": "./lib/index.js" - }, - "./package.json": "./package.json" - }, - "scripts": { - "build": "rimraf lib && ttsc build && rollup -c", - "dev": "ttsc --watch", - "prepack": "pnpm run build" - }, - "repository": { - "type": "git", - "url": "https://github.com/samchon/typia" - }, - "author": "Jeongho Nam", - "license": "MIT", - "bugs": { - "url": "https://github.com/samchon/typia/issues" - }, - "homepage": "https://typia.io", - "dependencies": { - "@typia/interface": "workspace:^", - "@typia/utils": "workspace:^" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "catalog:rollup", - "@rollup/plugin-node-resolve": "catalog:rollup", - "@types/node": "catalog:utils", - "@typescript/native-preview": "catalog:typescript", - "@typia/ttsc": "workspace:^", - "rimraf": "catalog:utils", - "rollup": "catalog:rollup", - "rollup-plugin-auto-external": "catalog:rollup", - "rollup-plugin-node-externals": "catalog:rollup", - "tinyglobby": "^0.2.12" - }, - "sideEffects": false, - "files": [ - "README.md", - "native", - "package.json", - "lib", - "src" - ], - "keywords": [ - "fast", - "json", - "stringify", - "transform", - "ajv", - "io-ts", - "zod", - "schema", - "json-schema", - "generator", - "assert", - "clone", - "is", - "validate", - "equal", - "runtime", - "type", - "typebox", - "checker", - "validator", - "safe", - "parse", - "prune", - "random", - "protobuf", - "llm", - "llm-function-calling", - "structured-output", - "openai", - "chatgpt", - "claude", - "gemini", - "llama" - ], - "packageManager": "pnpm@10.6.4", - "publishConfig": { - "access": "public", - "main": "lib/index.js", - "module": "lib/index.mjs", - "types": "lib/index.d.ts", - "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.mjs", - "default": "./lib/index.js" - }, - "./package.json": "./package.json" - } - } -} diff --git a/packages/core/rollup.config.mjs b/packages/core/rollup.config.mjs deleted file mode 100644 index 077e7f4a1e1..00000000000 --- a/packages/core/rollup.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from "../../config/rollup.config.mjs"; diff --git a/packages/core/src/context/IProgrammerProps.ts b/packages/core/src/context/IProgrammerProps.ts deleted file mode 100644 index 66feaf72e18..00000000000 --- a/packages/core/src/context/IProgrammerProps.ts +++ /dev/null @@ -1,60 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "./ITypiaContext"; - -/** - * Properties passed to typia code programmers. - * - * `IProgrammerProps` contains all the information needed for programmer - * functions to generate runtime validation, serialization, or transformation - * code. Each programmer receives these props and produces TypeScript AST nodes - * that implement the operation for the target type. - * - * The programmers are internal to typia's transformation system and are invoked - * by the TypeScript transformer when processing `typia.*()` function calls. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export interface IProgrammerProps { - /** - * Typia transformation context. - * - * Contains TypeScript compiler APIs, transformation context, import manager, - * and configuration options. Shared across all programmers. - */ - context: ITypiaContext; - - /** - * Module expression for typia references. - * - * The expression representing the typia module (typically the identifier - * `typia`). Used when generating code that references typia internals. - */ - modulo: ts.LeftHandSideExpression; - - /** - * TypeScript type to generate code for. - * - * The resolved type from the generic parameter of the typia function call - * (e.g., `T` from `typia.is()`). Analyzed to generate appropriate - * validation/serialization logic. - */ - type: ts.Type; - - /** - * Optional type name for error messages. - * - * Human-readable name for the type, used in generated error messages and - * debug output. May be `undefined` for anonymous types. - */ - name: string | undefined; - - /** - * Optional initial value expression. - * - * For functions that take an input value (like `typia.is(value)`), this is - * the expression representing that value. Used as the input to the generated - * validation code. - */ - init?: ts.Expression | undefined; -} diff --git a/packages/core/src/context/ITransformOptions.ts b/packages/core/src/context/ITransformOptions.ts deleted file mode 100644 index 18f3e0c9854..00000000000 --- a/packages/core/src/context/ITransformOptions.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Typia transformer configuration options. - * - * `ITransformOptions` controls how typia generates validation code at compile - * time. These options affect edge cases in type checking such as special - * numeric values (NaN, Infinity), function properties, and undefined handling. - * - * Configure these options in your `tsconfig.json` under the typia plugin: - * - * ```json - * { - * "compilerOptions": { - * "plugins": [{ "transform": "typia/lib/ttsc/plugin", "finite": true }] - * } - * } - * ``` - * - * @author Jeongho Nam - https://github.com/samchon - */ -export interface ITransformOptions { - /** - * Validate that numbers are finite (not NaN or Infinity). - * - * When `true`, generated validation code includes `Number.isFinite()` checks - * for all number types. This catches both `NaN` and `Infinity` values that - * might otherwise pass numeric type checks. - * - * Behavior varies by operation context: - * - * - **Validation** (`is`, `assert`, `validate`): Follows this setting - * - **Marshaling** (`stringify`, `encode`): Always `true` (JSON doesn't support - * NaN/Infinity) - * - **Parsing** (`parse`, `decode`): Always `false` (JSON can't produce - * NaN/Infinity) - * - * @default false - */ - finite?: undefined | boolean; - - /** - * Validate that numbers are not NaN. - * - * When `true`, generated validation code includes `Number.isNaN()` checks for - * all number types. This is less strict than `finite` - it allows `Infinity` - * but rejects `NaN`. - * - * This option is **ignored** if `finite` is `true` (which already rejects - * NaN). Same context-dependent behavior as `finite`. - * - * @default false - */ - numeric?: undefined | boolean; - - /** - * Validate function-typed properties. - * - * When `true`, generated validation code includes `typeof x === "function"` - * checks for function properties. When `false`, function properties are - * skipped during validation. - * - * Always `false` during marshaling/parsing since functions cannot be - * serialized to JSON or other formats. - * - * @default false - */ - functional?: undefined | boolean; - - /** - * Allow `undefined` values in extra/superfluous properties. - * - * Affects strict equality checking (`equals` functions) behavior when objects - * have additional properties beyond the type definition. - * - * When `true`, extra properties with `undefined` values are tolerated. When - * `false`, any extra property (even if `undefined`) fails equality. - * - * Only affects `equals*` functions; other validation functions always allow - * `undefined` in extra properties. - * - * @default true - */ - undefined?: undefined | boolean; - - /** @internal */ - runtime?: "ts" | "js"; -} diff --git a/packages/core/src/context/ITypiaContext.ts b/packages/core/src/context/ITypiaContext.ts deleted file mode 100644 index 5aa4fc1f82b..00000000000 --- a/packages/core/src/context/ITypiaContext.ts +++ /dev/null @@ -1,100 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ImportProgrammer } from "../programmers/ImportProgrammer"; -import { ITransformOptions } from "./ITransformOptions"; - -/** - * Typia transformation context containing compiler dependencies. - * - * `ITypiaContext` holds all the dependencies needed during compilation when - * transforming `typia.*()` function calls into runtime validation code. This - * context is created by the typia transformer and passed to all programmer - * functions. - * - * The context provides access to: - * - * - TypeScript compiler APIs for type analysis ({@link checker}) - * - AST manipulation utilities ({@link printer}, {@link transformer}) - * - Import management ({@link importer}) - * - Error reporting ({@link extras}) - * - Configuration ({@link options}) - * - * @author Jeongho Nam - https://github.com/samchon - */ -export interface ITypiaContext { - /** - * TypeScript program instance. - * - * The compiled TypeScript program containing all source files and type - * information. Used to access the type checker and resolve type definitions - * across files. - */ - program: ts.Program; - - /** - * TypeScript compiler options. - * - * The compiler options from `tsconfig.json`. Affects code generation - * decisions like module format and strict mode settings. - */ - compilerOptions: ts.CompilerOptions; - - /** - * TypeScript type checker for type analysis. - * - * Provides type information for AST nodes. Used extensively to analyze - * generic type parameters, resolve type aliases, and extract property - * information from types. - */ - checker: ts.TypeChecker; - - /** - * TypeScript AST printer for code generation. - * - * Converts generated AST nodes back to TypeScript source code. Used for debug - * output and error messages. - */ - printer: ts.Printer; - - /** - * Typia-specific transformer options. - * - * Configuration from the typia plugin in `tsconfig.json`. Controls validation - * behavior for edge cases. - */ - options: ITransformOptions; - - /** - * TypeScript transformation context. - * - * Provides factory functions for creating AST nodes during transformation. - * All generated code uses this context's factory. - */ - transformer: ts.TransformationContext; - - /** - * Import statement manager. - * - * Tracks and generates import statements for runtime dependencies. Ensures - * required modules are imported when validation code references external - * functions. - */ - importer: ImportProgrammer; - - /** - * Diagnostic utilities for error reporting. - * - * Provided by the active compiler host for reporting compilation errors and - * warnings. Used to surface transformation errors in the IDE and build - * output. - */ - extras: { - /** - * Adds a diagnostic message to the compilation output. - * - * @param diag - The diagnostic to report - * @returns The diagnostic ID - */ - addDiagnostic: (diag: ts.Diagnostic) => number; - }; -} diff --git a/packages/core/src/context/TransformerError.ts b/packages/core/src/context/TransformerError.ts deleted file mode 100644 index c409d123853..00000000000 --- a/packages/core/src/context/TransformerError.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { NamingConvention } from "@typia/utils"; - -import { MetadataFactory } from "../factories/MetadataFactory"; -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; - -/** - * Error thrown during typia transformation. - * - * Thrown when `typia.*()` receives unsupported types (e.g., tuples for some - * LLM providers, recursive types without `$ref`, native class types). The error - * message lists specific type violations. Use {@link from} to create from - * multiple {@link MetadataFactory.IError} instances. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export class TransformerError extends Error { - /** Error code identifying the error type. */ - public readonly code: string; - - public constructor(props: TransformerError.IProps) { - super(props.message); - this.code = props.code; - - // INHERITANCE POLYFILL - const proto = new.target.prototype; - if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto); - else (this as any).__proto__ = proto; - } -} -export namespace TransformerError { - /** Constructor properties for TransformerError. */ - export interface IProps { - /** Error code. */ - code: string; - - /** Error message. */ - message: string; - } - - /** - * Create error from metadata factory errors. - * - * Formats multiple type errors into a single TransformerError. - */ - export const from = (props: { - code: string; - errors: MetadataFactory.IError[]; - }): TransformerError => { - const body: string = props.errors - .map((e) => { - const subject: string = - e.explore.object === null - ? "" - : join(e.explore.object)(e.explore.property); - const middle: string = e.explore.parameter - ? `(parameter: ${JSON.stringify(e.explore.parameter)})` - : e.explore.output - ? "(return type)" - : ""; - const type: string = `${subject.length ? `${subject}: ` : ""}${e.name}`; - return `- ${type}${middle}\n${e.messages - .map((msg) => ` - ${msg}`) - .join("\n")}`; - }) - .join("\n\n"); - return new TransformerError({ - code: props.code, - message: `unsupported type detected\n\n${body}`, - }); - }; - - const join = - (object: MetadataObjectType) => (key: string | object | null) => { - if (key === null) return object.name; - else if (typeof key === "object") return `${object.name}[key]`; - else if (NamingConvention.variable(key)) return `${object.name}.${key}`; - return `${object.name}[${JSON.stringify(key)}]`; - }; -} diff --git a/packages/core/src/context/index.ts b/packages/core/src/context/index.ts deleted file mode 100644 index b494adaa8cb..00000000000 --- a/packages/core/src/context/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./IProgrammerProps"; -export * from "./ITransformOptions"; -export * from "./ITypiaContext"; -export * from "./TransformerError"; diff --git a/packages/core/src/factories/CommentFactory.ts b/packages/core/src/factories/CommentFactory.ts deleted file mode 100644 index 76b6c0eea40..00000000000 --- a/packages/core/src/factories/CommentFactory.ts +++ /dev/null @@ -1,87 +0,0 @@ -import ts from "@typescript/native-preview"; - -/** - * Factory for extracting JSDoc comments from TypeScript symbols. - * - * Extracts documentation comments and JSDoc tags from TypeScript AST symbols. - * Handles both legacy (TS < 5.2) and modern TypeScript versions. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace CommentFactory { - export const description = ( - symbol: ts.Symbol, - includeTags: boolean = false, - ): string | undefined => { - const node = symbol.declarations?.[0]; - if (!node) return undefined; - - // FOR LEGACY TS < 5.2 - const [major, minor] = ts.versionMajorMinor.split(".").map(Number) as [ - number, - number, - ]; - if (major < 5 || (major === 5 && minor < 1)) { - const content: string[] = []; - const main: string = ts.displayPartsToString( - symbol.getDocumentationComment(undefined), - ); - if (main.length) { - content.push(main); - if (includeTags && symbol.getJsDocTags().length) content.push(""); - } - if (includeTags) - for (const tag of symbol.getJsDocTags()) { - content.push( - tag.text - ? `@${tag.name} ${ts.displayPartsToString(tag.text)}` - : `@${tag.name}`, - ); - } - return content.length - ? content.map((line) => line.split("\r\n").join("\n")).join("\n") - : undefined; - } - - // NEW FEATURE OF TS 5.2 - const elements: readonly (ts.JSDoc | ts.JSDocTag)[] = - ts.getJSDocCommentsAndTags(node); - if (elements.length === 0) return undefined; - - const content: string[] = []; - for (const comment of elements) { - if (ts.isJSDoc(comment)) { - const parsed: string | undefined = ts.getTextOfJSDocComment( - comment.comment, - ); - if (parsed?.length) { - content.push(parsed); - if (includeTags && comment.tags?.length) content.push(""); - } - if (includeTags) - for (const tag of comment.tags ?? []) - content.push(parseJSDocTag(tag)); - } else if (includeTags) content.push(parseJSDocTag(comment)); - } - const output: string = content - .map((line) => line.split("\r\n").join("\n")) - .join("\n"); - return output.length ? output : undefined; - }; - - export const merge = (comments: ts.SymbolDisplayPart[]): string => - comments - .map((part) => part.text) - .map((str) => str.split("\r\n").join("\n")) - .join(""); -} - -const parseJSDocTag = (tag: ts.JSDocTag): string => { - const name: string | undefined = ( - tag as ts.JSDocParameterTag - ).name?.getText(); - const parsed: string | undefined = ts.getTextOfJSDocComment(tag.comment); - return [`@${tag.tagName.text}`, name, parsed] - .filter((str) => !!str?.length) - .join(" "); -}; diff --git a/packages/core/src/factories/ExpressionFactory.ts b/packages/core/src/factories/ExpressionFactory.ts deleted file mode 100644 index 51019981d77..00000000000 --- a/packages/core/src/factories/ExpressionFactory.ts +++ /dev/null @@ -1,233 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ImportProgrammer } from "../programmers/ImportProgrammer"; - -/** - * Factory for creating TypeScript expression nodes. - * - * Creates numeric literals, type checks, instanceof expressions, and more. Also - * provides {@link transpile} for converting string code to AST with `$input` - * placeholder substitution and import handling. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace ExpressionFactory { - export const number = (value: number) => - value < 0 - ? ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.MinusToken, - ts.factory.createNumericLiteral(Math.abs(value)), - ) - : ts.factory.createNumericLiteral(value); - - export const bigint = (value: number | bigint) => - ts.factory.createCallExpression( - ts.factory.createIdentifier("BigInt"), - undefined, - [ts.factory.createIdentifier(value.toString())], - ); - - export const isRequired = (input: ts.Expression): ts.Expression => - ts.factory.createStrictInequality( - ts.factory.createIdentifier("undefined"), - input, - ); - - export const isArray = (input: ts.Expression): ts.Expression => - ts.factory.createCallExpression( - ts.factory.createIdentifier("Array.isArray"), - undefined, - [input], - ); - - export const isObject = (props: { - checkNull: boolean; - checkArray: boolean; - input: ts.Expression; - }): ts.Expression => { - const conditions: ts.Expression[] = [ - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("object"), - ts.factory.createTypeOfExpression(props.input), - ), - ]; - if (props.checkNull === true) - conditions.push( - ts.factory.createStrictInequality(ts.factory.createNull(), props.input), - ); - if (props.checkArray === true) - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createCallExpression( - ts.factory.createIdentifier("Array.isArray"), - undefined, - [props.input], - ), - ), - ); - - return conditions.length === 1 - ? conditions[0]! - : conditions.reduce((x, y) => ts.factory.createLogicalAnd(x, y)); - }; - - export const isInstanceOf = ( - type: string, - input: ts.Expression, - ): ts.Expression => { - return ts.factory.createBinaryExpression( - input, - ts.factory.createToken(ts.SyntaxKind.InstanceOfKeyword), - ts.factory.createIdentifier(type), - ); - }; - - export const coalesce = (x: ts.Expression, y: ts.Expression): ts.Expression => - ts.factory.createBinaryExpression( - x, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), - y, - ); - - export const currying = (props: { - function: ts.Expression; - arguments: ts.Expression[]; - }): ts.CallExpression => { - if (props.arguments.length === 0) - return ts.factory.createCallExpression( - props.function, - undefined, - undefined, - ); - let prev: ts.CallExpression = ts.factory.createCallExpression( - props.function, - undefined, - [props.arguments[0]!], - ); - for (const param of props.arguments.slice(1)) - prev = ts.factory.createCallExpression(prev, undefined, [param]); - return prev; - }; - - export const selfCall = ( - body: ts.ConciseBody, - type?: ts.TypeNode | undefined, - ) => - ts.isCallExpression(body) - ? body - : ts.factory.createCallExpression( - ts.factory.createParenthesizedExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - [], - type, - undefined, - body, - ), - ), - undefined, - undefined, - ); - - export const getEscapedText = (props: { - printer: ts.Printer; - input: ts.Expression; - }): string => - props.printer.printNode( - ts.EmitHint.Expression, - props.input, - props.input.getSourceFile(), - ); - - export const transpile = (props: { - transformer?: ts.TransformationContext; - importer?: ImportProgrammer; - script: string; - }) => { - const file: ts.SourceFile = ts.createSourceFile( - `${_randomFormatUuid()}.ts`, - props.script, - ts.ScriptTarget.ESNext, - true, - ts.ScriptKind.TS, - ); - const statement: ts.Statement | undefined = file.statements[0]; - if (statement === undefined) - throw new ReferenceError( - "Error on ExpressionFactory.transpile(): no statement exists.", - ); - else if (!ts.isExpressionStatement(statement)) - throw new TypeError( - "Error on ExpressionFactory.transpile(): statement is not an expression statement.", - ); - return (input: ts.Expression): ts.Expression => { - const visitor = (node: ts.Node): ts.Node => { - if (ts.isIdentifier(node) && node.text === "$input") return input; - else if (props.importer !== undefined && ts.isCallExpression(node)) - if ( - node.expression.getText() === "$importInternal" && - node.arguments.length === 1 && - ts.isStringLiteralLike(node.arguments[0]!) - ) { - const name: string = node.arguments[0]!.text; - return props.importer.internal(name); - } else if ( - node.expression.getText() === "$importInstance" && - node.arguments.length === 2 && - ts.isStringLiteralLike(node.arguments[0]!) && - ts.isStringLiteralLike(node.arguments[1]!) - ) { - const name: string = node.arguments[0]!.text; - const file: string = node.arguments[1]!.text; - return props.importer.instance({ - file, - name, - alias: null, - }); - } else if ( - node.expression.getText() === "$importNamespace" && - node.arguments.length === 2 && - ts.isStringLiteralLike(node.arguments[0]!) && - ts.isStringLiteralLike(node.arguments[1]!) - ) { - const name: string = node.arguments[0]!.text; - const file: string = node.arguments[1]!.text; - return props.importer.namespace({ - file, - name, - }); - } else if ( - node.expression.getText() === "$importDefault" && - node.arguments.length === 3 && - ts.isStringLiteralLike(node.arguments[0]!) && - ts.isStringLiteralLike(node.arguments[1]!) - ) { - const name: string = node.arguments[0]!.text; - const file: string = node.arguments[1]!.text; - return props.importer.default({ - file, - name, - type: false, - }); - } - return ts.visitEachChild( - ts.factory.cloneNode(node), - visitor, - props.transformer, - ); - }; - return visitor( - ts.factory.cloneNode(statement.expression), - ) as ts.Expression; - }; - }; -} - -const _randomFormatUuid = (): string => - "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0; - const v = c === "x" ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); diff --git a/packages/core/src/factories/FormatCheatSheet.ts b/packages/core/src/factories/FormatCheatSheet.ts deleted file mode 100644 index a0d20799886..00000000000 --- a/packages/core/src/factories/FormatCheatSheet.ts +++ /dev/null @@ -1,71 +0,0 @@ -const RegexCall = (text: Text) => - `${text}.test($input)` as const; - -/** @reference https://github.dev/ajv-validator/ajv-formats/blob/master/src/formats.ts */ -export const FormatCheatSheet = { - // SPECIAL CHARACTERS - byte: RegexCall( - `/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm`, - ), - password: `true`, - regex: `(() => { try { new RegExp($input); return true; } catch { return false; } })()`, - uuid: RegexCall( - `/^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i`, - ), - - // ADDRESSES - email: RegexCall( - `/^[a-z0-9!#$%&'*+/=?^_\`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i`, - ), - hostname: RegexCall( - `/^(?=.{1,253}\\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\\.?$/i`, - ), - "idn-email": RegexCall( - `/^(([^<>()[\\]\\.,;:\\s@\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\"]+)*)|(\\".+\\"))@(([^<>()[\\]\\.,;:\\s@\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\"]{2,})$/i`, - ), - "idn-hostname": RegexCall( - `/^([a-z0-9\\u00a1-\\uffff0-9]+(-[a-z0-9\\u00a1-\\uffff0-9]+)*\\.)+[a-z\\u00a1-\\uffff]{2,}$/i`, - ), - iri: RegexCall( - `/^[A-Za-z][\\d+-.A-Za-z]*:[^\\u0000-\\u0020"<>\\\\^\`{|}]*$/u`, - ), - "iri-reference": RegexCall( - `/^[A-Za-z][\\d+-.A-Za-z]*:[^\\u0000-\\u0020"<>\\\\^\`{|}]*$/u`, - ), - ipv4: RegexCall( - `/^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$/`, - ), - ipv6: RegexCall( - `/^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i`, - ), - uri: `${RegexCall(`/\\/|:/`)} && ${RegexCall( - `/^(?:[a-z][a-z0-9+\\-.]*:)(?:\\/?\\/(?:(?:[a-z0-9\\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\\.[a-z0-9\\-._~!$&'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)|(?:[a-z0-9\\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\\d*)?(?:\\/(?:[a-z0-9\\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\\/(?:(?:[a-z0-9\\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\\/(?:[a-z0-9\\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\\/(?:[a-z0-9\\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\\?(?:[a-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i`, - )}`, - "uri-reference": RegexCall( - `/^(?:[a-z][a-z0-9+\\-.]*:)?(?:\\/?\\/(?:(?:[a-z0-9\\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\\.[a-z0-9\\-._~!$&'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)|(?:[a-z0-9\\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\\d*)?(?:\\/(?:[a-z0-9\\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\\/(?:(?:[a-z0-9\\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\\/(?:[a-z0-9\\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\\/(?:[a-z0-9\\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\\?(?:[a-z0-9\\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i`, - ), - "uri-template": RegexCall( - `/^(?:(?:[^\\x00-\\x20"'<>%\\\\^\`{|}]|%[0-9a-f]{2})|\\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\\*)?)*\\})*$/i`, - ), - url: RegexCall( - `/^(?:https?|ftp):\\/\\/(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z0-9\\u{00a1}-\\u{ffff}]+-)*[a-z0-9\\u{00a1}-\\u{ffff}]+)(?:\\.(?:[a-z0-9\\u{00a1}-\\u{ffff}]+-)*[a-z0-9\\u{00a1}-\\u{ffff}]+)*(?:\\.(?:[a-z\\u{00a1}-\\u{ffff}]{2,})))(?::\\d{2,5})?(?:\\/[^\\s]*)?$/iu`, - ), - - // TIMESTAMPS - "date-time": RegexCall( - `/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(T|\\s)([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\\.[0-9]{1,9})?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$/i`, - ), - date: RegexCall(`/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/`), - time: RegexCall( - `/^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\\.[0-9]{1,9})?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$/i`, - ), - duration: RegexCall( - `/^P(?!$)((\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?|(\\d+W)?)$/`, - ), - - // POINTERS - "json-pointer": RegexCall(`/^(?:\\/(?:[^~/]|~0|~1)*)*$/`), - "relative-json-pointer": RegexCall( - `/^(?:0|[1-9][0-9]*)(?:#|(?:\\/(?:[^~/]|~0|~1)*)*)$/`, - ), -} as const; diff --git a/packages/core/src/factories/IdentifierFactory.ts b/packages/core/src/factories/IdentifierFactory.ts deleted file mode 100644 index 9a977934537..00000000000 --- a/packages/core/src/factories/IdentifierFactory.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { TypeFactory } from "./TypeFactory"; - -/** - * Factory for creating TypeScript identifier and property access nodes. - * - * Creates identifiers, property access expressions, and parameter declarations. - * Handles both valid JavaScript identifiers and string literals for property - * names that contain special characters. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace IdentifierFactory { - export const identifier = (name: string) => - NamingConvention.variable(name) - ? ts.factory.createIdentifier(name) - : ts.factory.createStringLiteral(name); - - export const access = ( - input: ts.Expression, - key: string, - chain?: boolean, - ) => { - const postfix = identifier(key); - return ts.isStringLiteral(postfix) - ? chain === true - ? ts.factory.createElementAccessChain( - input, - ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), - postfix, - ) - : ts.factory.createElementAccessExpression(input, postfix) - : chain === true - ? ts.factory.createPropertyAccessChain( - input, - ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), - postfix, - ) - : ts.factory.createPropertyAccessExpression(input, postfix); - }; - - export const getName = (input: ts.Expression): string => { - const value: any = (input as any).escapedText?.toString(); - if (typeof value === "string") return value; - - if (ts.isPropertyAccessExpression(input)) - return `${getName( - input.expression, - )}.${input.name.escapedText.toString()}`; - else if (ts.isElementAccessExpression(input)) - return `${getName(input.expression)}[${getName( - input.argumentExpression, - )}]`; - return "unknown"; - }; - - export const postfix = (str: string): string => - NamingConvention.variable(str) - ? `".${str}"` - : `"[${JSON.stringify(str).split('"').join('\\"')}]"`; - - export const parameter = ( - name: string | ts.BindingName, - type?: ts.TypeNode | undefined, - init?: - | ts.Expression - | ts.PunctuationToken - | undefined, - ): ts.ParameterDeclaration => { - // instead of ts.version >= "4.8" - if (ts.getDecorators !== undefined) - return ts.factory.createParameterDeclaration( - undefined, - undefined, - name, - init?.kind === ts.SyntaxKind.QuestionToken - ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) - : undefined, - type ?? TypeFactory.keyword("any"), - init && init.kind !== ts.SyntaxKind.QuestionToken ? init : undefined, - ); - // eslint-disable-next-line - return (ts.factory.createParameterDeclaration as any)( - undefined, - undefined, - undefined, - name, - init?.kind === ts.SyntaxKind.QuestionToken - ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) - : undefined, - type, - init && init.kind !== ts.SyntaxKind.QuestionToken ? init : undefined, - ); - }; -} diff --git a/packages/core/src/factories/JsonMetadataFactory.ts b/packages/core/src/factories/JsonMetadataFactory.ts deleted file mode 100644 index 3ff558eeff9..00000000000 --- a/packages/core/src/factories/JsonMetadataFactory.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { TransformerError } from "../context/TransformerError"; -import { AtomicPredicator } from "../programmers/helpers/AtomicPredicator"; -import { MetadataCollection } from "../schemas/metadata/MetadataCollection"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { MetadataFactory } from "./MetadataFactory"; - -export namespace JsonMetadataFactory { - export interface IProps { - method: string; - checker: ts.TypeChecker; - transformer?: ts.TransformationContext; - type: ts.Type; - validate?: MetadataFactory.Validator; - } - export interface IOutput { - collection: MetadataCollection; - metadata: MetadataSchema; - } - - export const analyze = (props: IProps): IOutput => { - const collection: MetadataCollection = new MetadataCollection(); - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.checker, - transformer: props.transformer, - options: { - absorb: true, - escape: true, - constant: true, - validate: props.validate - ? (next) => { - const errors: string[] = validate(next); - errors.push(...props.validate!(next)); - return errors; - } - : (next) => validate(next), - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.method, - errors: result.errors, - }); - return { - collection, - metadata: result.data, - }; - }; - - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const output: string[] = []; - if ( - props.metadata.atomics.some((a) => a.type === "bigint") || - props.metadata.constants.some((c) => c.type === "bigint") - ) - output.push("JSON does not support bigint type."); - if ( - props.metadata.tuples.some((t) => - t.type.elements.some((e) => e.isRequired() === false), - ) || - props.metadata.arrays.some((a) => a.type.value.isRequired() === false) - ) - output.push("JSON does not support undefined type in array."); - if (props.metadata.maps.length) - output.push("JSON does not support Map type."); - if (props.metadata.sets.length) - output.push("JSON does not support Set type."); - for (const native of props.metadata.natives) - if ( - AtomicPredicator.native(native.name) === false && - native.name !== "Date" - ) - output.push(`JSON does not support ${native.name} type.`); - return output; - }; -} diff --git a/packages/core/src/factories/LiteralFactory.ts b/packages/core/src/factories/LiteralFactory.ts deleted file mode 100644 index e9c7e0f6919..00000000000 --- a/packages/core/src/factories/LiteralFactory.ts +++ /dev/null @@ -1,52 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ExpressionFactory } from "./ExpressionFactory"; -import { IdentifierFactory } from "./IdentifierFactory"; - -export namespace LiteralFactory { - export const write = (input: any): ts.Expression => { - if (input === null) return ts.factory.createNull(); - else if (ts.isArrowFunction(input)) return input; - else if (ts.isCallExpression(input)) return input; - else if (ts.isIdentifier(input)) return input; - else if (input instanceof Array) return writeArray(input); - else if (typeof input === "object") return writeObject(input); - else if (typeof input === "boolean") return writeBoolean(input); - else if (typeof input === "bigint") return writeBigint(input); - else if (typeof input === "number") return writeNumber(input); - else if (typeof input === "string") return writeStrinng(input); - // unreachable code - else if (typeof input === "function") - return ts.factory.createIdentifier("undefined"); - else - throw new TypeError("Error on LiteralFactory.generate(): unknown type."); - }; - - const writeObject = (obj: object): ts.ObjectLiteralExpression => - ts.factory.createObjectLiteralExpression( - Object.entries(obj) - .filter((tuple) => tuple[1] !== undefined) - .map(([key, value]) => - ts.factory.createPropertyAssignment( - IdentifierFactory.identifier(key), - write(value), - ), - ), - true, - ); - - const writeArray = (array: any[]): ts.ArrayLiteralExpression => - ts.factory.createArrayLiteralExpression(array.map(write), true); - - const writeBoolean = (value: boolean): ts.Expression => - value ? ts.factory.createTrue() : ts.factory.createFalse(); - - const writeNumber = (value: number): ts.Expression => - ExpressionFactory.number(value); - - const writeBigint = (value: bigint): ts.Expression => - ExpressionFactory.bigint(value); - - const writeStrinng = (value: string): ts.StringLiteral => - ts.factory.createStringLiteral(value); -} diff --git a/packages/core/src/factories/MetadataCommentTagFactory.ts b/packages/core/src/factories/MetadataCommentTagFactory.ts deleted file mode 100644 index ec763a696af..00000000000 --- a/packages/core/src/factories/MetadataCommentTagFactory.ts +++ /dev/null @@ -1,630 +0,0 @@ -import { IMetadataTypeTag } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { Writable } from "../typings/Writable"; -import { FormatCheatSheet } from "./FormatCheatSheet"; -import { MetadataFactory } from "./MetadataFactory"; -import { MetadataTypeTagFactory } from "./MetadataTypeTagFactory"; - -/** @internal */ -export namespace MetadataCommentTagFactory { - export const analyze = (props: { - errors: MetadataFactory.IError[]; - metadata: MetadataSchema; - tags: ts.JSDocTagInfo[]; - explore: MetadataFactory.IExplore; - }): void => { - // PREPARE MESSAGE CONTAINER - const messages: string[] = []; - const report = (msg: string) => { - messages.push(msg); - return null; - }; - const validateReport = (next: { - property: string | null; - message: string; - }): false => { - messages.push( - `the property ${ - next.property === null - ? `["typia.tag"]` - : `["typia.tag.${next.property}"]` - } ${next.message}.`, - ); - return false; - }; - - // VALIDATE AND CONSTRUCT COMMENT TAGS - for (const tag of props.tags) { - const tagger: TagRecord | null = parse({ - report, - tag, - }); - if (tagger === null) continue; - for (const [key, value] of Object.entries(tagger)) { - const filtered: IMetadataTypeTag[] = value.filter( - (v) => v.validate !== null, - ) as IMetadataTypeTag[]; - if (key === "array") { - if (props.metadata.arrays.length === 0) { - report(`requires array type`); - continue; - } - for (const a of props.metadata.arrays) { - Writable(a).tags = a.tags.filter((x) => - MetadataTypeTagFactory.validate({ - report: validateReport, - type: "array", - tags: [...x, ...filtered], - }), - ); - if (a.tags.length === 0) a.tags.push(filtered); - else for (const tags of a.tags) tags.push(...filtered); - } - } else { - const atomic = props.metadata.atomics.find((a) => a.type == key); - if (atomic === undefined) - if (key === "bigint" || key === "number") { - const opposite = key === "bigint" ? "number" : "bigint"; - if ( - tagger[opposite] !== undefined && - props.metadata.atomics.some((a) => a.type === opposite) - ) - continue; - } else if ( - key === "string" && - value[0]?.kind === "format" && - value[0]?.value === "date-time" - ) - continue; - else report(`requires ${key} type`); - else { - Writable(atomic).tags = atomic.tags.filter((x) => - MetadataTypeTagFactory.validate({ - report: validateReport, - type: key as "string", - tags: [...x, ...filtered], - }), - ); - if (atomic.tags.length === 0) atomic.tags.push(filtered); - else for (const tags of atomic.tags) tags.push(...filtered); - } - } - } - } - - // DO REPORT - if (messages.length !== 0) - props.errors.push({ - name: "comment tag(s)", - explore: props.explore, - messages, - }); - }; - - const parse = (props: { - report: (msg: string) => null; - tag: ts.JSDocTagInfo; - }): TagRecord | null => { - const next = PARSER[props.tag.name]; - if (next === undefined) return {}; - - const value = (props.tag.text || [])[0]?.text; - if (value === undefined && props.tag.name !== "uniqueItems") - return props.report(`no comment tag value`); - return next({ - report: props.report, - value: value!, - }); - }; - - export const get = (props: { - kind: string; - type: "array" | "bigint" | "number" | "string"; - value: string; - }): IMetadataTypeTag[] => { - const output: IMetadataTypeTag[] | undefined = PARSER[props.kind]?.({ - report: () => null, - value: props.value, - })?.[props.type]; - if (output === undefined) - throw new Error( - `no tag found for (kind: ${props.kind}, type: ${props.type}).`, - ); - return output; - }; -} - -/** @internal */ -type TagRecord = { - [P in Target]?: NotDeterminedTypeTag[]; -}; - -/** @internal */ -type Target = "bigint" | "number" | "string" | "array"; - -/** @internal */ -type NotDeterminedTypeTag = Omit & { - validate: string | undefined; - schema: object | undefined; -}; - -/** @internal */ -const PARSER: Record< - string, - (props: { report: (msg: string) => null; value: string }) => { - [P in Target]?: NotDeterminedTypeTag[]; - } -> = { - /* ----------------------------------------------------------- - ARRAY - ----------------------------------------------------------- */ - items: ({ report, value }) => ({ - array: [ - { - name: `MinItems<${value}>`, - target: "array", - kind: "minItems", - value: parse_integer({ - report, - value, - unsigned: true, - }), - validate: `${value} <= $input.length`, - exclusive: true, - schema: { - minItems: parse_integer({ - report, - value, - unsigned: true, - }), - }, - }, - { - name: `MaxItems<${value}>`, - target: "array", - kind: "maxItems", - value: parse_integer({ - report, - value, - unsigned: true, - }), - validate: `$input.length <= ${value}`, - exclusive: true, - schema: { - maxItems: parse_integer({ - report, - unsigned: true, - value, - }), - }, - }, - ], - }), - minItems: ({ report, value }) => ({ - array: [ - { - name: `MinItems<${value}>`, - target: "array", - kind: "minItems", - value: parse_integer({ - report, - value, - unsigned: true, - }), - validate: `${value} <= $input.length`, - exclusive: true, - schema: { - minItems: parse_integer({ - report, - value, - unsigned: true, - }), - }, - }, - ], - }), - maxItems: ({ report, value }) => ({ - array: [ - { - name: `MaxItems<${value}>`, - target: "array", - kind: "maxItems", - value: parse_integer({ - report, - value, - unsigned: true, - }), - validate: `$input.length <= ${value}`, - exclusive: true, - schema: { - maxItems: parse_integer({ - report, - value, - unsigned: true, - }), - }, - }, - ], - }), - uniqueItems: () => ({ - array: [ - { - name: `UniqueItems`, - target: "array", - kind: "uniqueItems", - value: true, - validate: `$input.length <= 1 || (new Set($input).size === $input.length)`, - exclusive: true, - schema: { - uniqueItems: true, - }, - }, - ], - }), - - /* ----------------------------------------------------------- - NUMBER - ----------------------------------------------------------- */ - type: ({ value }) => { - // EMENDATIONS - if (value.startsWith("{") && value.endsWith("}")) - value = value.substring(1, value.length - 1); - if (value === "int") value = "int32"; - else if (value === "uint") value = "uint32"; - - // MUST BE ONE OF THEM - if ( - ["int32", "uint32", "int64", "uint64", "float", "double"].includes( - value, - ) === false - ) - return {}; - return { - number: [ - { - name: `Type<${JSON.stringify(value)}>`, - target: "number", - kind: "type", - value: value, - validate: - value === "int32" - ? `Math.floor($input) === $input && -2147483648 <= $input && $input <= 2147483647` - : value === "uint32" - ? `Math.floor($input) === $input && 0 <= $input && $input <= 4294967295` - : value === "int64" - ? `Math.floor($input) === $input && -9223372036854775808 <= $input && $input <= 9223372036854775807` - : value === "uint64" - ? `Math.floor($input) === $input && 0 <= $input && $input <= 18446744073709551615` - : value === "float" - ? `-1.175494351e38 <= $input && $input <= 3.4028235e38` - : `true`, - exclusive: true, - schema: ["int32", "int64"].includes(value) - ? { type: "integer" } - : ["uint32", "uint64"].includes(value) - ? { type: "integer", minimum: 0 } - : undefined, - }, - ], - bigint: - value === "int64" || "uint64" - ? [ - { - name: `Type<${JSON.stringify(value)}>`, - target: "bigint", - kind: "type", - value: value, - validate: value === "int64" ? "true" : "BigInt(0) <= $input", - exclusive: true, - schema: value === "uint64" ? { minimum: 0 } : undefined, - }, - ] - : [], - }; - }, - minimum: (props) => ({ - number: [ - { - name: `Minimum<${props.value}>`, - target: "number", - kind: "minimum", - value: parse_number(props), - validate: `${props.value} <= $input`, - exclusive: ["minimum", "exclusiveMinimum"], - schema: { - minimum: parse_number(props), - }, - }, - ], - bigint: [ - { - name: `Minimum<${props.value}n>`, - target: "bigint", - kind: "minimum", - value: (() => { - const parsed = parse_integer({ - report: props.report, - value: props.value, - unsigned: false, - }); - return parsed === null ? null : BigInt(parsed); - })(), - validate: `${props.value} <= $input`, - exclusive: ["minimum", "exclusiveMinimum"], - schema: { - minimum: parse_number(props), - }, - }, - ], - }), - maximum: (props) => ({ - number: [ - { - name: `Maximum<${props.value}>`, - target: "number", - kind: "maximum", - value: parse_number(props), - validate: `$input <= ${props.value}`, - exclusive: ["maximum", "exclusiveMaximum"], - schema: { - maximum: parse_number(props), - }, - }, - ], - bigint: [ - { - name: `Maximum<${props.value}n>`, - target: "bigint", - kind: "maximum", - value: (() => { - const parsed = parse_integer({ - report: props.report, - value: props.value, - unsigned: false, - }); - return parsed === null ? null : BigInt(parsed); - })(), - validate: `$input <= ${props.value}`, - exclusive: ["maximum", "exclusiveMaximum"], - schema: { - maximum: parse_number(props), - }, - }, - ], - }), - exclusiveMinimum: (props) => ({ - number: [ - { - name: `ExclusiveMinimum<${props.value}>`, - target: "number", - kind: "exclusiveMinimum", - value: parse_number(props), - validate: `${props.value} < $input`, - exclusive: ["minimum", "exclusiveMinimum"], - schema: { - exclusiveMinimum: parse_number(props), - }, - }, - ], - bigint: [ - { - name: `ExclusiveMinimum<${props.value}n>`, - target: "bigint", - kind: "exclusiveMinimum", - value: (() => { - const parsed = parse_integer({ - report: props.report, - value: props.value, - unsigned: false, - }); - return parsed === null ? null : BigInt(parsed); - })(), - validate: `${props.value} < $input`, - exclusive: ["minimum", "exclusiveMinimum"], - schema: { - exclusiveMinimum: parse_number(props), - }, - }, - ], - }), - exclusiveMaximum: (props) => ({ - number: [ - { - name: `ExclusiveMaximum<${props.value}>`, - target: "number", - kind: "exclusiveMaximum", - value: parse_number(props), - validate: `$input < ${props.value}`, - exclusive: ["maximum", "exclusiveMaximum"], - schema: { - exclusiveMaximum: parse_number(props), - }, - }, - ], - bigint: [ - { - name: `ExclusiveMaximum<${props.value}n>`, - target: "bigint", - kind: "exclusiveMaximum", - value: (() => { - const parsed = parse_integer({ - report: props.report, - value: props.value, - unsigned: false, - }); - return parsed === null ? null : BigInt(parsed); - })(), - validate: `$input < ${props.value}`, - exclusive: ["maximum", "exclusiveMaximum"], - schema: { - exclusiveMaximum: parse_number(props), - }, - }, - ], - }), - multipleOf: (props) => ({ - number: [ - { - name: `MultipleOf<${props.value}>`, - target: "number", - kind: "multipleOf", - value: parse_number(props), - validate: `$input % ${props.value} === 0`, - exclusive: true, - schema: { - multipleOf: parse_number(props), - }, - }, - ], - bigint: [ - { - name: `MultipleOf<${props.value}n>`, - target: "bigint", - kind: "multipleOf", - value: (() => { - const parsed = parse_integer({ - report: props.report, - value: props.value, - unsigned: false, - }); - return parsed === null ? null : BigInt(parsed); - })(), - validate: `$input % ${props.value}n === 0n`, - exclusive: true, - schema: { - multipleOf: parse_number(props), - }, - }, - ], - }), - - /* ----------------------------------------------------------- - STRING - ----------------------------------------------------------- */ - format: ({ value }) => { - const matched = FORMATS.get(value); - if (matched === undefined) return {}; - return { - string: [ - { - name: `Format<${JSON.stringify(matched[0])}>`, - target: "string", - kind: "format", - value: matched[0], - validate: matched[1], - exclusive: true, - schema: { - format: matched[0], - }, - }, - ], - }; - }, - pattern: ({ value }) => ({ - string: [ - { - name: `Pattern<${JSON.stringify(value)}>`, - target: "string", - kind: "pattern", - value: value, - validate: `RegExp(${JSON.stringify(value)}).test($input)`, - exclusive: ["format"], - schema: { - pattern: value, - }, - }, - ], - }), - length: (props) => ({ - string: [ - { - name: `MinLength<${props.value}>`, - target: "string", - kind: "minLength", - value: parse_number(props), - validate: `${props.value} <= $input.length`, - exclusive: true, - schema: { - minLength: parse_number(props), - }, - }, - { - name: `MaxLength<${props.value}>`, - target: "string", - kind: "maxLength", - value: parse_number(props), - validate: `$input.length <= ${props.value}`, - exclusive: true, - schema: { - maxLength: parse_number(props), - }, - }, - ], - }), - minLength: ({ report, value }) => ({ - string: [ - { - name: `MinLength<${value}>`, - target: "string", - kind: "minLength", - value: parse_number({ report, value }), - validate: `${value} <= $input.length`, - exclusive: true, - schema: { - minLength: parse_number({ report, value }), - }, - }, - ], - }), - maxLength: ({ report, value }) => ({ - string: [ - { - name: `MaxLength<${value}>`, - target: "string", - kind: "maxLength", - value: parse_number({ report, value }), - validate: `$input.length <= ${value}`, - exclusive: true, - schema: { - maxLength: parse_number({ report, value }), - }, - }, - ], - }), -}; - -/** @internal */ -const parse_number = (props: { - report: (msg: string) => null; - value: string; -}): number | null => { - const parsed: number = Number(props.value); - if (isNaN(parsed) === true) return props.report(`invalid number`); - return parsed; -}; - -/** @internal */ -const parse_integer = (props: { - report: (msg: string) => null; - unsigned: boolean; - value: string; -}): number | null => { - const parsed: number | null = parse_number(props); - if (parsed === null) return null; - else if (Math.floor(parsed) !== parsed) - return props.report(`invalid integer`); - else if (props.unsigned === true && parsed < 0) - return props.report(`invalid unsigned integer`); - return parsed; -}; - -/** @internal */ -const FORMATS: Map = new Map([ - ...Object.entries(FormatCheatSheet).map( - ([key, value]) => [key, [key, value]] as any, - ), - ["datetime", ["date-time", `!isNaN(new Date($input).getTime())`]], - ["dateTime", ["date-time", `!isNaN(new Date($input).getTime())`]], -]); diff --git a/packages/core/src/factories/MetadataFactory.ts b/packages/core/src/factories/MetadataFactory.ts deleted file mode 100644 index f8e2de2e557..00000000000 --- a/packages/core/src/factories/MetadataFactory.ts +++ /dev/null @@ -1,491 +0,0 @@ -import { ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { MetadataAliasType } from "../schemas/metadata/MetadataAliasType"; -import { MetadataArrayType } from "../schemas/metadata/MetadataArrayType"; -import { MetadataCollection } from "../schemas/metadata/MetadataCollection"; -import { MetadataConstant } from "../schemas/metadata/MetadataConstant"; -import { MetadataFunction } from "../schemas/metadata/MetadataFunction"; -import { MetadataObject } from "../schemas/metadata/MetadataObject"; -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { MetadataTupleType } from "../schemas/metadata/MetadataTupleType"; -import { ExpressionFactory } from "./ExpressionFactory"; -import { explore_metadata } from "./internal/metadata/explore_metadata"; -import { iterate_metadata_collection } from "./internal/metadata/iterate_metadata_collection"; -import { iterate_metadata_sort } from "./internal/metadata/iterate_metadata_sort"; - -/** - * TypeScript type metadata extractor. - * - * Analyzes TypeScript types at compile-time and extracts {@link MetadataSchema} - * containing all type information needed for validation/serialization code - * generation. Handles unions, intersections, generics, type aliases, and - * collects reusable components into {@link MetadataCollection}. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace MetadataFactory { - /** Validation function type for metadata schemas. */ - export type Validator = (props: { - metadata: MetadataSchema; - explore: IExplore; - top: MetadataSchema; - }) => string[]; - - /** Properties for metadata analysis. */ - export interface IProps { - /** TypeScript type checker. */ - checker: ts.TypeChecker; - - /** TypeScript transformation context. */ - transformer: ts.TransformationContext | undefined; - - /** Analysis options. */ - options: IOptions; - - /** Storage for collected metadata components. */ - components: MetadataCollection; - - /** Type to analyze. */ - type: ts.Type | null; - } - - /** Options for metadata analysis. */ - export interface IOptions { - /** Process escaped types. */ - escape: boolean; - - /** Extract constant values. */ - constant: boolean; - - /** Absorb union types. */ - absorb: boolean; - - /** Include function types. */ - functional?: boolean; - - /** Custom validation function. */ - validate?: Validator; - - /** Error callback. */ - onError?: (node: ts.Node | undefined, message: string) => void; - } - - /** Type exploration context during analysis. */ - export interface IExplore { - /** Whether at top-level type. */ - top: boolean; - - /** Current object type being explored. */ - object: MetadataObjectType | null; - - /** Current property key. */ - property: string | object | null; - - /** Nested type context. */ - nested: null | MetadataAliasType | MetadataArrayType | MetadataTupleType; - - /** Function parameter name. */ - parameter: string | null; - - /** Whether exploring return type. */ - output: boolean; - - /** Whether in escaped type. */ - escaped: boolean; - - /** Whether in aliased type. */ - aliased: boolean; - } - - /** Metadata analysis error. */ - export interface IError { - /** Type name where error occurred. */ - name: string; - - /** Exploration context at error. */ - explore: IExplore; - - /** Error messages. */ - messages: string[]; - } - - /** - * Analyze TypeScript type and extract metadata. - * - * @param props Analysis properties - * @returns Metadata schema or validation errors - */ - export const analyze = ( - props: IProps, - ): ValidationPipe => { - const errors: IError[] = []; - const metadata: MetadataSchema = explore_metadata({ - ...props, - errors, - explore: { - top: true, - object: null, - property: null, - parameter: null, - nested: null, - aliased: false, - escaped: false, - output: false, - }, - intersected: false, - }); - iterate_metadata_collection({ - errors, - collection: props.components, - }); - iterate_metadata_sort({ - collection: props.components, - metadata: metadata, - }); - - if (props.options.validate) - errors.push( - ...validate({ - transformer: props.transformer, - options: props.options, - functor: props.options.validate, - metadata, - }), - ); - return errors.length - ? { - success: false, - errors, - } - : { - success: true, - data: metadata, - }; - }; - - /** @internal */ - export const soleLiteral = (value: string): MetadataSchema => { - const meta: MetadataSchema = MetadataSchema.initialize(); - meta.constants.push( - MetadataConstant.from({ - values: [ - { - value, - tags: [], - }, - ], - type: "string", - }), - ); - return meta; - }; - - /** - * Validate metadata schema. - * - * @param props Validation properties - * @returns Array of validation errors - */ - export const validate = (props: { - transformer?: ts.TransformationContext; - options: IOptions; - functor: Validator; - metadata: MetadataSchema; - }): IError[] => { - const visitor: IValidationVisitor = { - functor: props.functor, - errors: [], - objects: new Set(), - arrays: new Set(), - tuples: new Set(), - aliases: new Set(), - functions: new Set(), - }; - validateMeta({ - ...props, - visitor, - explore: { - object: null, - property: null, - parameter: null, - nested: null, - top: true, - aliased: false, - escaped: false, - output: false, - }, - top: props.metadata, - }); - return visitor.errors; - }; - - const validateMeta = (props: { - options: IOptions; - visitor: IValidationVisitor; - metadata: MetadataSchema; - top: MetadataSchema; - explore: IExplore; - }) => { - const result: string[] = []; - for (const atomic of props.metadata.atomics) - for (const row of atomic.tags) - for (const tag of row.filter( - (t) => t.validate !== undefined && t.predicate === undefined, - )) - try { - tag.predicate = ExpressionFactory.transpile({ - script: tag.validate!, - }); - } catch { - result.push( - `Unable to transpile type tag script: ${JSON.stringify( - tag.validate, - )}`, - ); - tag.predicate = () => ts.factory.createTrue(); - } - result.push( - ...props.visitor.functor({ - metadata: props.metadata, - explore: props.explore, - top: props.top, - }), - ); - if (result.length) - props.visitor.errors.push({ - name: props.metadata.getName(), - explore: { ...props.explore }, - messages: [...new Set(result)], - }); - - for (const alias of props.metadata.aliases) - validateAlias({ - ...props, - alias: alias.type, - }); - for (const array of props.metadata.arrays) - validateArray({ - ...props, - array: array.type, - }); - for (const tuple of props.metadata.tuples) - validateTuple({ - ...props, - tuple: tuple.type, - }); - for (const object of props.metadata.objects) - validateObject({ - ...props, - object: object.type, - }); - for (const func of props.metadata.functions) - validateFunction({ - ...props, - function: func, - }); - for (const set of props.metadata.sets) - validateMeta({ - ...props, - metadata: set.value, - }); - for (const map of props.metadata.maps) { - validateMeta({ - ...props, - metadata: map.key, - }); - validateMeta({ - ...props, - metadata: map.value, - }); - } - - if (props.options.escape === true && props.metadata.escaped !== null) - validateMeta({ - ...props, - metadata: props.metadata.escaped.returns, - explore: { - ...props.explore, - escaped: true, - }, - }); - }; - - const validateAlias = (props: { - transformer?: ts.TransformationContext; - options: IOptions; - visitor: IValidationVisitor; - alias: MetadataAliasType; - explore: IExplore; - top: MetadataSchema; - }) => { - if (props.visitor.aliases.has(props.alias)) return; - props.visitor.aliases.add(props.alias); - - validateMeta({ - ...props, - metadata: props.alias.value, - explore: { - ...props.explore, - nested: props.alias, - aliased: true, - }, - }); - }; - - const validateArray = (props: { - transformer?: ts.TransformationContext; - options: IOptions; - visitor: IValidationVisitor; - array: MetadataArrayType; - explore: IExplore; - top: MetadataSchema; - }) => { - if (props.visitor.arrays.has(props.array)) return; - props.visitor.arrays.add(props.array); - - validateMeta({ - ...props, - metadata: props.array.value, - explore: { - ...props.explore, - nested: props.array, - top: false, - }, - }); - }; - - const validateTuple = (props: { - transformer?: ts.TransformationContext; - options: IOptions; - visitor: IValidationVisitor; - tuple: MetadataTupleType; - explore: IExplore; - top: MetadataSchema; - }) => { - if (props.visitor.tuples.has(props.tuple)) return; - props.visitor.tuples.add(props.tuple); - - for (const elem of props.tuple.elements) - validateMeta({ - ...props, - metadata: elem, - explore: { - ...props.explore, - nested: props.tuple, - top: false, - }, - }); - }; - - const validateObject = (props: { - transformer?: ts.TransformationContext; - options: IOptions; - visitor: IValidationVisitor; - object: MetadataObjectType; - top: MetadataSchema; - }) => { - if (props.visitor.objects.has(props.object)) return; - props.visitor.objects.add(props.object); - - if (props.options.validate) { - const explore: IExplore = { - object: props.object, - top: false, - property: null, - parameter: null, - nested: null, - aliased: false, - escaped: false, - output: false, - }; - const errors: string[] = props.options.validate({ - metadata: MetadataSchema.create({ - ...MetadataSchema.initialize(), - objects: [ - MetadataObject.create({ - type: props.object, - tags: [], - }), - ], - }), - top: props.top, - explore, - }); - if (errors.length) - props.visitor.errors.push({ - name: props.object.name, - explore, - messages: [...new Set(errors)], - }); - } - - for (const property of props.object.properties) - validateMeta({ - ...props, - metadata: property.value, - explore: { - object: props.object, - property: property.key.isSoleLiteral() - ? property.key.getSoleLiteral()! - : {}, - parameter: null, - nested: null, - top: false, - aliased: false, - escaped: false, - output: false, - }, - top: props.top, - }); - }; - - const validateFunction = (props: { - transformer?: ts.TransformationContext; - options: IOptions; - visitor: IValidationVisitor; - function: MetadataFunction; - explore: IExplore; - top: MetadataSchema; - }) => { - if (props.visitor.functions.has(props.function)) return; - props.visitor.functions.add(props.function); - - for (const param of props.function.parameters) - validateMeta({ - ...props, - metadata: param.type, - explore: { - ...props.explore, - parameter: param.name, - nested: null, - top: false, - output: false, - }, - top: props.top, - }); - if (props.function.output) - validateMeta({ - ...props, - metadata: props.function.output, - explore: { - ...props.explore, - parameter: null, - nested: null, - top: false, - output: true, - }, - }); - }; - - interface IValidationVisitor { - functor: Validator; - errors: IError[]; - objects: Set; - arrays: Set; - tuples: Set; - aliases: Set; - functions: Set; - } -} diff --git a/packages/core/src/factories/MetadataTypeTagFactory.ts b/packages/core/src/factories/MetadataTypeTagFactory.ts deleted file mode 100644 index 3ea7f93abe2..00000000000 --- a/packages/core/src/factories/MetadataTypeTagFactory.ts +++ /dev/null @@ -1,413 +0,0 @@ -import { IMetadataTypeTag } from "@typia/interface"; - -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { MetadataFactory } from "./MetadataFactory"; -import { MetadataTypeTagSchemaFactory } from "./MetadataTypeTagSchemaFactory"; - -export namespace MetadataTypeTagFactory { - export const is = (obj: MetadataObjectType): boolean => { - if (obj.properties.length !== 1) return false; - - const top: MetadataProperty = obj.properties[0]!; - if (top.key.isSoleLiteral() === false) return false; - else if (top.key.getSoleLiteral() !== "typia.tag") return false; - - const value: MetadataSchema = top.value; - if ( - value.size() !== 1 || - value.objects.length !== 1 || - value.isRequired() === true || - value.nullable === true - ) - return false; - - const tag: MetadataObjectType = top.value.objects[0]!.type; - const statics: string[] = tag.properties - .map((p) => p.key.getSoleLiteral()!) - .filter((str) => str !== null); - if (ESSENTIAL_FIELDS.some((f) => !statics.includes(f))) return false; - return true; - }; - - export const analyze = (props: { - errors: MetadataFactory.IError[]; - type: "boolean" | "bigint" | "number" | "string" | "array" | "object"; - objects: MetadataObjectType[]; - explore: MetadataFactory.IExplore; - }): IMetadataTypeTag[] => { - const messages: string[] = []; - const report = (next: { - property: string | null; - message: string; - }): false => { - messages.push( - `the property ${ - next.property === null - ? `["typia.tag"]` - : `["typia.tag.${next.property}"]` - } ${next.message}.`, - ); - return false; - }; - - //---- - // VALIDATION PROCESS - //---- - const filtered: MetadataObjectType[] = props.objects.filter((obj) => { - // ONLY ONE PROPERTY - if (obj.properties.length !== 1) return false; - - // THE TAG.TYPE PROPERTY MUST BE - const top: MetadataProperty = obj.properties[0]!; - if ( - top.key.getSoleLiteral() !== "typia.tag" || - top.value.size() !== 1 || - top.value.objects.length !== 1 - ) - return false; - else if (top.value.optional === false) - return report({ - property: null, - message: "must be optional object", - }); - - // CHECK LIST OF PROPERTIES - const tag: MetadataObjectType = top.value.objects[0]!.type; - const statistics: string[] = tag.properties - .map((p) => p.key.getSoleLiteral()!) - .filter((str) => str !== null); - if (ESSENTIAL_FIELDS.some((f) => !statistics.includes(f))) - return report({ - property: null, - message: `must have at least three properties - ${ESSENTIAL_FIELDS.map( - (str) => `'${str}'`, - ).join(", ")}`, - }); - - const each: boolean[] = tag.properties.map((p) => { - const key: string | null = p.key.getSoleLiteral(); - if (key === null) return true; - else if (FIELDS.includes(key) === false) return true; - return validate_property({ - report, - key, - value: p.value, - }); - }); - return each.every((v) => v === true); - }); - if (filtered.length === 0) return []; - - //---- - // CONSTRUCT TYPE TAGS - //---- - // CREATE 1ST - const tagList: Array = filtered.map((object) => - create_metadata_type_tag({ - report, - object, - }), - ); - - const output: IMetadataTypeTag[] = []; - for (const tag of tagList) - if (tag !== null) - output.push({ - target: tag.target.some((str) => str === props.type) - ? props.type - : null!, - name: tag.name, - kind: tag.kind, - value: tag.value, - validate: tag.validate[props.type]!, - exclusive: tag.exclusive, - schema: tag.schema, - }); - validate({ - report, - type: props.type, - tags: output, - }); - - if (messages.length > 0) { - props.errors.push({ - name: [props.type, ...props.objects.map((o) => o.name)].join(" & "), - explore: props.explore, - messages, - }); - return []; - } - return output; - }; - - export const validate = (props: { - report: (next: { property: string | null; message: string }) => false; - type: "boolean" | "bigint" | "number" | "string" | "array" | "object"; - tags: IMetadataTypeTag[]; - }): boolean => { - let success: boolean = true; - for (const tag of props.tags) - if (tag.target !== props.type) { - success &&= props.report({ - property: null, - message: `target must contain ${props.type} type`, - }); - } - - props.tags.forEach((tag, i) => { - if (tag.exclusive === false) return; - else if (tag.exclusive === true) { - const some: boolean = props.tags.some( - (opposite, j) => i !== j && opposite.kind === tag.kind, - ); - if (some === true) - success &&= props.report({ - property: null, - message: `kind '${tag.kind}' can't be duplicated`, - }); - } else if (Array.isArray(tag.exclusive)) { - const some: IMetadataTypeTag | undefined = props.tags.find( - (opposite, j) => - i !== j && - opposite.kind === tag.kind && - (tag.exclusive as string[]).includes(opposite.name), - ); - if (some !== undefined) - success ??= props.report({ - property: null, - message: `kind '${tag.kind}' can't be used with '${some.name}'`, - }); - } - }); - return success; - }; - - const validate_property = (props: { - report: (next: { property: string | null; message: string }) => false; - key: string; - value: MetadataSchema; - }): boolean => { - if ( - // TARGET - props.key === "target" && - (props.value.constants.length !== 1 || - props.value.constants[0]!.values.length !== props.value.size() || - props.value.constants[0]!.values.some( - (v) => - v.value !== "boolean" && - v.value !== "bigint" && - v.value !== "number" && - v.value !== "string" && - v.value !== "array" && - v.value !== "object", - )) - ) - return props.report({ - property: props.key, - message: `must be one of 'boolean', 'bigint', 'number', 'string', 'array', 'object'`, - }); - else if ( - // KIND - props.key === "kind" && - (props.value.size() !== 1 || - props.value.constants.length !== 1 || - props.value.constants[0]!.type !== "string" || - props.value.constants[0]!.values.length !== 1) - ) - return props.report({ - property: props.key, - message: "must be a string literal type", - }); - else if ( - // VALUE - props.key === "value" && - !( - (props.value.size() === 0 && props.value.isRequired() === false) || - (props.value.size() === 1 && - (props.value.objects.length === 1 || - props.value.constants.length === 1)) - ) - ) - return props.report({ - property: props.key, - message: "must be a literal type or undefined value", - }); - else if (props.key === "exclusive") return get_exclusive(props) !== null; - else if (props.key === "validate") { - //---- - // VALIDATE - //---- - // UNDEFINED CASE - if ( - props.value.size() === 0 && - props.value.isRequired() === false && - props.value.nullable === false - ) - return true; - - // STRING CASE - if ( - props.value.size() === 1 && - props.value.constants.length === 1 && - props.value.constants[0]!.type === "string" && - (props.value.constants[0]!.values.length === 1) === true - ) - return true; - - // RECORD - const target: string[] | undefined = - props.value.objects[0]?.type.properties - .map((p) => p.key.getSoleLiteral()!) - .filter((str) => str !== null) as string[] | undefined; - if (target === undefined) - return props.report({ - property: "target", - message: `must be one of 'boolean', 'bigint', 'number', 'string', 'array', 'object`, - }); - const variadic: boolean = - props.value.size() === 1 && - props.value.objects.length === 1 && - props.value.objects[0]!.type.properties.every( - (vp) => - vp.value.size() === 1 && - vp.value.isRequired() && - vp.value.nullable === false && - vp.value.constants.length === 1 && - vp.value.constants[0]!.type === "string" && - vp.value.constants[0]!.values.length === 1 && - target.includes(vp.key.getSoleLiteral()!), - ); - if (variadic === false) - return props.report({ - property: props.key, - message: `must be a string literal type or Record type.`, - }); - } - return true; - }; - - const create_metadata_type_tag = (props: { - report: (next: { property: string | null; message: string }) => false; - object: MetadataObjectType; - }): ITypeTag | null => { - const find = (key: string): MetadataProperty | undefined => - props.object.properties[0]?.value.objects[0]?.type.properties.find( - (p) => p.key.getSoleLiteral() === key, - ); - - const target = find("target")!.value.constants[0]!.values.map( - (v) => v.value, - ) as ITypeTag["target"]; - const kind: string = find("kind")!.value.constants[0]!.values[0]! - .value as string; - const value: boolean | bigint | number | string | undefined = - find("value")?.value.constants[0]?.values[0]!.value; - const exclusive: string[] | boolean | null = get_exclusive({ - report: props.report, - key: "exclusive", - value: find("exclusive")?.value, - }); - if (exclusive === null) return null; - - const validate: Record = (() => { - const validate = find("validate")?.value; - if (!validate || validate.size() === 0) return {}; - else if (validate.constants.length) - return Object.fromEntries( - target.map((t) => [ - t, - validate.constants[0]!.values[0]!.value as string, - ]), - ); - return Object.fromEntries( - validate.objects[0]!.type.properties.map((p) => [ - p.key.getSoleLiteral()!, - p.value.constants[0]!.values[0]!.value as string, - ]), - ); - })(); - const schema: object | undefined = (() => { - const p: MetadataSchema | undefined = find("schema")?.value; - if (p === undefined) return undefined; - else if (p.size() === 0 && p.isRequired() === false) return undefined; - else if ( - p.size() === 1 && - p.nullable === false && - p.isRequired() === true && - p.any === false - ) - return MetadataTypeTagSchemaFactory.object({ - report: (message) => - props.report({ - property: "schema", - message, - }), - object: p.objects[0]!.type, - }); - props.report({ - property: "schema", - message: "must be an object type", - }); - return undefined; - })(); - return { - name: props.object.name, - target, - kind, - value, - validate, - exclusive: exclusive ?? false, - schema, - }; - }; - - const get_exclusive = (props: { - report: (next: { property: string | null; message: string }) => false; - key: string; - value: MetadataSchema | undefined; - }): boolean | string[] | null => { - if (props.value === undefined) return false; - else if ( - props.value.size() === 1 && - props.value.constants.length === 1 && - props.value.constants[0]!.type === "boolean" && - props.value.constants[0]!.values.length === 1 - ) - return props.value.constants[0]!.values[0]!.value as boolean; - else if ( - props.value.size() === 1 && - props.value.tuples.length === 1 && - props.value.tuples[0]!.type.elements.every( - (elem) => - elem.size() === 1 && - elem.constants.length === 1 && - elem.constants[0]!.type === "string" && - elem.constants[0]!.values.length === 1, - ) - ) - return props.value.tuples[0]!.type.elements.map( - (elem) => elem.constants[0]!.values[0]!.value as string, - ); - props.report({ - property: props.key, - message: - "must a boolean literal type or a tuple of string literal types.", - }); - return null; - }; -} - -interface ITypeTag { - name: string; - target: Array<"bigint" | "number" | "string" | "array" | "object">; - kind: string; - value?: undefined | boolean | bigint | number | string; - validate: Record; - exclusive: boolean | string[]; - schema?: undefined | any; -} - -const ESSENTIAL_FIELDS = ["target", "kind", "value"]; -const FIELDS = [...ESSENTIAL_FIELDS, "validate", "exclusive"]; diff --git a/packages/core/src/factories/MetadataTypeTagSchemaFactory.ts b/packages/core/src/factories/MetadataTypeTagSchemaFactory.ts deleted file mode 100644 index 6328a822582..00000000000 --- a/packages/core/src/factories/MetadataTypeTagSchemaFactory.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; - -export namespace MetadataTypeTagSchemaFactory { - export const object = (props: { - report: (msg: string) => false; - object: MetadataObjectType; - }): object | undefined => { - if (props.object.recursive) { - props.report(`${props.object.name} has recursive type`); - return undefined; - } - const output: any = {}; - for (const p of props.object.properties) { - const key: string | null = p.key.getSoleLiteral()!; - if (key === null) { - props.report( - `${props.object.name} has non-literal key type: ${p.key.getName()}`, - ); - continue; - } - output[key] = iterate({ - report: props.report, - object: props.object, - key, - metadata: p.value, - }); - } - return output; - }; - - const iterate = (props: { - report: (message: string) => false; - object: MetadataObjectType; - key: string; - metadata: MetadataSchema; - }): any => { - if ( - props.metadata.any || - props.metadata.atomics.length || - props.metadata.arrays.length || - props.metadata.natives.length || - props.metadata.functions.length - ) - props.report(`${props.object.name}.${props.key} has non-literal type`); - else if (props.metadata.size() > 1) - props.report(`${props.object.name}.${props.key} has union type`); - else if (props.metadata.size() === 0) - if (props.metadata.nullable) return null; - else if (props.metadata.isRequired() === true) - props.report(`${props.object.name}.${props.key} has non-literal type`); - else return undefined; - else if (props.metadata.constants.length) - return props.metadata.constants[0]!.values[0]!.value; - else if (props.metadata.tuples.length) { - const tuple = props.metadata.tuples[0]!; - if (tuple.type.isRest()) - props.report(`${props.object.name}.${props.key} has rest tuple type`); - else if (tuple.type.recursive) - props.report( - `${props.object.name}.${props.key} has recursive tuple type`, - ); - else if (tuple.type.elements.some((e) => e.required === false)) - props.report( - `${props.object.name}.${props.key} has optional tuple type`, - ); - return tuple.type.elements.map((elem) => - iterate({ - report: props.report, - object: props.object, - key: props.key, - metadata: elem, - }), - ); - } else if (props.metadata.objects.length) - return object({ - report: props.report, - object: props.metadata.objects[0]!.type, - }); - else props.report(`${props.object.name}.${props.key} has non-literal type`); - }; -} diff --git a/packages/core/src/factories/NumericRangeFactory.ts b/packages/core/src/factories/NumericRangeFactory.ts deleted file mode 100644 index 6bb685a6013..00000000000 --- a/packages/core/src/factories/NumericRangeFactory.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ProtobufAtomic } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ExpressionFactory } from "./ExpressionFactory"; - -export namespace NumericRangeFactory { - export const number = ( - type: ProtobufAtomic.Numeric, - input: ts.Expression, - ): ts.Expression => NumberPredicator[type](input); - - export const bigint = ( - type: ProtobufAtomic.BigNumeric, - input: ts.Expression, - ): ts.Expression => BigIntPredicator[type](input); -} - -namespace NumberPredicator { - export const int32 = (input: ts.Expression) => - ts.factory.createLogicalAnd( - integer(input), - between("-2147483648", "2147483647")(input), - ); - export const uint32 = (input: ts.Expression) => - ts.factory.createLogicalAnd( - integer(input), - between("0", "4294967295")(input), - ); - export const int64 = (input: ts.Expression) => - ts.factory.createLogicalAnd( - integer(input), - between("-9223372036854775808", "9223372036854775807")(input), - ); - export const uint64 = (input: ts.Expression) => - ts.factory.createLogicalAnd( - integer(input), - between("0", "18446744073709551615")(input), - ); - export const float = (input: ts.Expression) => - between("-1.175494351e38", "3.4028235e38")(input); - export const double = () => ts.factory.createTrue(); -} - -namespace BigIntPredicator { - export const int64 = () => ts.factory.createTrue(); - export const uint64 = (input: ts.Expression) => - ts.factory.createLessThanEquals( - ts.factory.createCallExpression( - ts.factory.createIdentifier("BigInt"), - undefined, - [ExpressionFactory.number(0)], - ), - input, - ); -} - -const integer = (input: ts.Expression) => - ts.factory.createStrictEquality( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Math.floor"), - undefined, - [input], - ), - input, - ); - -const between = (x: string, y: string) => (input: ts.Expression) => - ts.factory.createLogicalAnd( - ts.factory.createLessThanEquals(ts.factory.createIdentifier(x), input), - ts.factory.createLessThanEquals(input, ts.factory.createIdentifier(y)), - ); diff --git a/packages/core/src/factories/ProtobufFactory.ts b/packages/core/src/factories/ProtobufFactory.ts deleted file mode 100644 index 9bc7cdcdbd6..00000000000 --- a/packages/core/src/factories/ProtobufFactory.ts +++ /dev/null @@ -1,917 +0,0 @@ -import { - IMetadataTypeTag, - ProtobufAtomic, - ValidationPipe, -} from "@typia/interface"; -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { TransformerError } from "../context/TransformerError"; -import { ProtobufUtil } from "../programmers/helpers/ProtobufUtil"; -import { MetadataCollection } from "../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { IProtobufProperty } from "../schemas/protobuf/IProtobufProperty"; -import { IProtobufPropertyType } from "../schemas/protobuf/IProtobufPropertyType"; -import { IProtobufSchema } from "../schemas/protobuf/IProtobufSchema"; -import { MetadataFactory } from "./MetadataFactory"; - -export namespace ProtobufFactory { - export interface IProps { - method: string; - checker: ts.TypeChecker; - transformer?: ts.TransformationContext; - components: MetadataCollection; - type: ts.Type; - } - - /* ----------------------------------------------------------- - METADATA COMPOSER - ----------------------------------------------------------- */ - export const metadata = (props: IProps): MetadataSchema => { - // COMPOSE METADATA WITH INDIVIDUAL VALIDATIONS - const result: ValidationPipe = - MetadataFactory.analyze({ - ...props, - transformer: props.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate: validate(), - }, - }); - if (result.success === false) - throw TransformerError.from({ - code: `typia.protobuf.${props.method}`, - errors: result.errors, - }); - return result.data; - }; - - /** @internal */ - export const emplaceObject = (object: MetadataObjectType): void => { - for (const p of object.properties) emplaceProperty(p); - const properties: IProtobufProperty[] = object.properties - .map((p) => p.of_protobuf_) - .filter((p) => p !== undefined); - const unique: Set = new Set( - properties - .filter((p) => p !== undefined) - .filter((p) => p.fixed === true) - .map((p) => p.union.map((u) => u.index)) - .flat(), - ); - let index: number = 1; - properties.forEach((schema) => { - if (schema.fixed === true) - index = Math.max( - index, - Math.max(...schema.union.map((u) => u.index)) + 1, - ); - else { - for (const u of schema.union) { - while (unique.has(index) === true) ++index; - u.index = index; - unique.add(index); - } - ++index; - } - }); - }; - - const emplaceProperty = (prop: MetadataProperty): void => { - const union: IProtobufPropertyType[] = []; - for (const native of prop.value.natives) - if (native.name === "Uint8Array") - union.push({ - type: "bytes", - index: ProtobufUtil.getSequence(native.tags[0] ?? [])!, - }); - union.push(...emplaceAtomic(prop.value).values()); - for (const array of prop.value.arrays) - union.push({ - type: "array", - array: array.type, - value: emplaceSchema( - array.type.value, - ) as IProtobufSchema.IArray["value"], - index: ProtobufUtil.getSequence(array.tags[0] ?? [])!, - }); - for (const obj of prop.value.objects) - if (isDynamicObject(obj.type)) - union.push({ - type: "map", - map: obj.type, - key: emplaceSchema( - obj.type.properties[0]!.key, - ) as IProtobufSchema.IMap["key"], - value: emplaceSchema( - obj.type.properties[0]!.value, - ) as IProtobufSchema.IMap["value"], - index: ProtobufUtil.getSequence(obj.tags[0] ?? [])!, - }); - else - union.push({ - type: "object", - object: obj.type, - index: ProtobufUtil.getSequence(obj.tags[0] ?? [])!, - }); - for (const map of prop.value.maps) - union.push({ - type: "map", - map, - key: emplaceSchema(map.key) as IProtobufSchema.IMap["key"], - value: emplaceSchema(map.value) as IProtobufSchema.IMap["value"], - index: ProtobufUtil.getSequence(map.tags[0] ?? [])!, - }); - prop.of_protobuf_ = { - union, - fixed: union.every((p) => p.index !== null), - }; - }; - - const emplaceSchema = (metadata: MetadataSchema): IProtobufSchema => { - for (const native of metadata.natives) - if (native.name === "Uint8Array") - return { - type: "bytes", - }; - const atomic = emplaceAtomic(metadata); - if (atomic.size) return atomic.values().next().value!; - for (const array of metadata.arrays) - return { - type: "array", - array: array.type, - value: emplaceSchema( - array.type.value, - ) as IProtobufSchema.IArray["value"], - }; - for (const obj of metadata.objects) - if (isDynamicObject(obj.type)) - return { - type: "map", - map: obj.type, - key: emplaceSchema( - obj.type.properties[0]!.key, - ) as IProtobufSchema.IMap["key"], - value: emplaceSchema( - obj.type.properties[0]!.value, - ) as IProtobufSchema.IMap["value"], - }; - else - return { - type: "object", - object: obj.type, - }; - for (const map of metadata.maps) - return { - type: "map", - map, - key: emplaceSchema(map.key) as IProtobufSchema.IMap["key"], - value: emplaceSchema(map.value) as IProtobufSchema.IMap["value"], - }; - throw new Error( - "Error on ProtobufFactory.emplaceSchema(): any type detected.", - ); - }; - - const emplaceAtomic = ( - meta: MetadataSchema, - ): Map => { - const map: Map = new Map(); - - // CONSTANTS - for (const c of meta.constants) - if (c.type === "boolean") - map.set("bool", { - type: "bool", - index: getSequence(c.values[0]?.tags[0] ?? [])!, - }); - else if (c.type === "bigint") { - const init: ProtobufAtomic.BigNumeric = getBigintType( - c.values.map((v) => BigInt(v.value)), - ); - for (const value of c.values) - emplaceBigint({ - map, - tags: value.tags, - init, - }); - } else if (c.type === "number") { - const init: ProtobufAtomic.Numeric = getNumberType( - c.values.map((v) => v.value) as number[], - ); - for (const value of c.values) - emplaceNumber({ - map, - tags: value.tags, - init, - }); - } else if (c.type === "string") - map.set("string", { - type: "string", - index: getSequence(c.values[0]?.tags[0] ?? [])!, - }); - - // TEMPLATE - if (meta.templates.length) - map.set("string", { - type: "string", - index: getSequence(meta.templates[0]?.tags[0] ?? [])!, - }); - - // ATOMICS - for (const atomic of meta.atomics) - if (atomic.type === "boolean") - map.set("bool", { - type: "bool", - index: getSequence(atomic.tags[0] ?? [])!, - }); - else if (atomic.type === "bigint") - emplaceBigint({ - map, - tags: atomic.tags, - init: "int64", - }); - else if (atomic.type === "number") - emplaceNumber({ - map, - tags: atomic.tags, - init: "double", - }); - else if (atomic.type === "string") - map.set("string", { - type: "string", - index: getSequence(atomic.tags[0] ?? [])!, - }); - - // SORTING FOR VALIDATION REASON - return new Map( - Array.from(map).sort((x, y) => ProtobufUtil.compare(x[0], y[0])), - ); - }; - - const emplaceBigint = (next: { - map: Map; - tags: IMetadataTypeTag[][]; - init: ProtobufAtomic.BigNumeric; - }): void => { - if (next.tags.length === 0) { - next.map.set(next.init, { - type: "bigint", - name: next.init, - index: null!, - }); - return; - } - for (const row of next.tags) { - const value: ProtobufAtomic.BigNumeric = - row.find( - (tag) => - tag.kind === "type" && - (tag.value === "int64" || tag.value === "uint64"), - )?.value ?? next.init; - next.map.set(next.init, { - type: "bigint", - name: value, - index: ProtobufUtil.getSequence(row)!, - }); - } - }; - - const emplaceNumber = (next: { - map: Map; - tags: IMetadataTypeTag[][]; - init: ProtobufAtomic.Numeric; - }): void => { - if (next.tags.length === 0) { - next.map.set(next.init, { - type: "number", - name: next.init, - index: null!, - }); - return; - } - for (const row of next.tags) { - const value: ProtobufAtomic.Numeric = - row.find( - (tag) => - tag.kind === "type" && - (tag.value === "int32" || - tag.value === "uint32" || - tag.value === "int64" || - tag.value === "uint64" || - tag.value === "float" || - tag.value === "double"), - )?.value ?? next.init; - next.map.set(value, { - type: "number", - name: value, - index: ProtobufUtil.getSequence(row)!, - }); - } - }; - - const getBigintType = (values: bigint[]): ProtobufAtomic.BigNumeric => - values.some((v) => v < 0) ? "int64" : "uint64"; - - const getNumberType = (values: number[]): ProtobufAtomic.Numeric => - values.every((v) => Math.floor(v) === v) - ? values.every((v) => -2147483648 <= v && v <= 2147483647) - ? "int32" - : "int64" - : "double"; - - const getSequence = (tags: IMetadataTypeTag[]): number | null => { - const sequence = tags.find( - (t) => - t.kind === "sequence" && - typeof (t.schema as any)?.["x-protobuf-sequence"] === "number", - ); - if (sequence === undefined) return null; - const value: number = Number( - (sequence.schema as any)["x-protobuf-sequence"], - ); - return Number.isNaN(value) ? null : value; - }; - - /* ----------------------------------------------------------- - VALIDATORS - ----------------------------------------------------------- */ - const validate = () => { - const visited: WeakSet = new WeakSet(); - return (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const errors: string[] = []; - const insert = (msg: string) => errors.push(msg); - - if (props.explore.top === true) { - const onlyObject: boolean = - props.metadata.size() === 1 && - props.metadata.objects.length === 1 && - props.metadata.objects[0]!.type.properties.every((p) => - p.key.isSoleLiteral(), - ) && - props.metadata.isRequired() === true && - props.metadata.nullable === false; - if (onlyObject === false) - insert("target type must be a sole and static object type"); - } - for (const obj of props.metadata.objects) { - if (visited.has(obj.type)) continue; - visited.add(obj.type); - validateObject({ - object: obj.type, - errors, - }); - try { - emplaceObject(obj.type); - } catch {} - } - - //---- - // NOT SUPPORTED TYPES - //---- - const noSupport = (msg: string) => insert(`does not support ${msg}`); - - // PROHIBIT ANY TYPE - if (props.metadata.any) noSupport("any type"); - // PROHIBIT FUNCTIONAL TYPE - if (props.metadata.functions.length) noSupport("functional type"); - // PROHIBIT TUPLE TYPE - if (props.metadata.tuples.length) noSupport("tuple type"); - // PROHIBIT SET TYPE - if (props.metadata.sets.length) noSupport("Set type"); - // NATIVE TYPE, BUT NOT Uint8Array - if (props.metadata.natives.length) - for (const native of props.metadata.natives) { - if (native.name === "Uint8Array") continue; - - const instead = BANNED_NATIVE_TYPES.get(native.name); - if (instead === undefined) noSupport(`${native.name} type`); - else noSupport(`${native.name} type. Use ${instead} type instead.`); - } - //---- - // ATOMIC CASES - //---- - if (props.metadata.atomics.length) { - const numbers = ProtobufUtil.getNumbers(props.metadata); - const bigints = ProtobufUtil.getBigints(props.metadata); - - for (const type of ["int64", "uint64"]) - if (numbers.has(type) && bigints.has(type)) - insert( - `tags.Type<"${type}"> cannot be used in both number and bigint types. Recommend to remove from number type`, - ); - } - //---- - // ARRAY CASES - //---- - // DO NOT ALLOW MULTI-DIMENSIONAL ARRAY - if ( - props.metadata.arrays.length && - props.metadata.arrays.some((array) => !!array.type.value.arrays.length) - ) - noSupport("over two dimensional array type"); - // CHILD OF ARRAY TYPE MUST BE REQUIRED - if ( - props.metadata.arrays.length && - props.metadata.arrays.some( - (array) => - array.type.value.isRequired() === false || - array.type.value.nullable === true, - ) - ) - noSupport("optional type in array"); - // UNION IN ARRAY - if ( - props.metadata.arrays.length && - props.metadata.arrays.some( - (a) => - a.type.value.size() > 1 && - a.type.value.constants.length !== 1 && - a.type.value.constants[0]?.values.length !== a.type.value.size(), - ) - ) - noSupport("union type in array"); - // DO DYNAMIC OBJECT IN ARRAY - if ( - props.metadata.arrays.length && - props.metadata.arrays.some( - (a) => - a.type.value.maps.length || - (a.type.value.objects.length && - a.type.value.objects.some( - (o) => ProtobufUtil.isStaticObject(o.type) === false, - )), - ) - ) - noSupport("dynamic object in array"); - // UNION WITH ARRAY - if (props.metadata.size() > 1 && props.metadata.arrays.length) - noSupport("union type with array type"); - //---- - // OBJECT CASES - //---- - // EMPTY PROPERTY - if ( - props.metadata.objects.length && - props.metadata.objects.some((obj) => obj.type.properties.length === 0) - ) - noSupport("empty object type"); - // STATIC PROPERTY NAMES MUST BE VALID PROTO FIELD IDENTIFIERS - if ( - props.metadata.objects.length && - props.metadata.objects.some((obj) => - obj.type.properties.some( - (property) => - property.key.isSoleLiteral() === true && - NamingConvention.variable(property.key.getSoleLiteral()!) === - false, - ), - ) - ) - noSupport("object type with non-variable property name"); - // BOXED NATIVE OBJECTS - if ( - props.metadata.objects.length && - props.metadata.objects.some((obj) => isBoxedNativeObject(obj.type)) - ) { - const boxed = props.metadata.objects - .map((obj) => obj.type) - .find((obj) => isBoxedNativeObject(obj)); - const instead = boxed - ? BANNED_NATIVE_TYPES.get(boxed.name) - : undefined; - if (boxed && instead !== undefined) - noSupport(`${boxed.name} type. Use ${instead} type instead.`); - } - // MULTIPLE DYNAMIC KEY TYPED PROPERTIES - if ( - props.metadata.objects.length && - props.metadata.objects.some( - (obj) => - obj.type.properties.filter((p) => !p.key.isSoleLiteral()).length > - 1, - ) - ) - noSupport( - "object type with multiple dynamic key typed properties. Keep only one.", - ); - // STATIC AND DYNAMIC PROPERTIES ARE COMPATIBLE - if ( - props.metadata.objects.length && - props.metadata.objects.some( - (obj) => - obj.type.properties.some((p) => p.key.isSoleLiteral()) && - obj.type.properties.some((p) => !p.key.isSoleLiteral()), - ) - ) - noSupport( - "object type with mixed static and dynamic key typed properties. Keep statics or dynamic only.", - ); - // DYNAMIC OBJECT, BUT PROPERTY VALUE TYPE IS ARRAY - if ( - props.metadata.objects.length && - isDynamicObject(props.metadata.objects[0]!.type) && - props.metadata.objects[0]!.type.properties.some( - (p) => !!p.value.arrays.length, - ) - ) - noSupport("dynamic object with array value type"); - // UNION WITH DYNAMIC OBJECTTa - if ( - props.metadata.size() > 1 && - props.metadata.objects.length && - isDynamicObject(props.metadata.objects[0]!.type) - ) - noSupport("union type with dynamic object type"); - // UNION IN DYNAMIC PROPERTY VALUE - if ( - props.metadata.objects.length && - props.metadata.objects.some( - (obj) => - isDynamicObject(obj.type) && - obj.type.properties.some((p) => ProtobufUtil.isUnion(p.value)), - ) - ) - noSupport("union type in dynamic property"); - //---- - // MAP CASES - //---- - // KEY TYPE IS UNION - if ( - props.metadata.maps.length && - props.metadata.maps.some((m) => ProtobufUtil.isUnion(m.key)) - ) - noSupport("union key typed map"); - // KEY TYPE IS NOT ATOMIC - if ( - props.metadata.maps.length && - props.metadata.maps.some( - (m) => ProtobufUtil.getAtomics(m.key).size !== 1, - ) - ) - noSupport("non-atomic key typed map"); - // MAP TYPE, BUT PROPERTY KEY TYPE IS OPTIONAL - if ( - props.metadata.maps.length && - props.metadata.maps.some( - (m) => m.key.isRequired() === false || m.key.nullable, - ) - ) - noSupport("optional key typed map"); - // MAP TYPE, BUT VALUE TYPE IS ARRAY - if ( - props.metadata.maps.length && - props.metadata.maps.some((m) => !!m.value.arrays.length) - ) - noSupport("map type with array value type"); - // UNION WITH MAP - if (props.metadata.size() > 1 && props.metadata.maps.length) - noSupport("union type with map type"); - // UNION IN MAP - if ( - props.metadata.maps.length && - props.metadata.maps.some((m) => ProtobufUtil.isUnion(m.value)) - ) - noSupport("union type in map value type"); - return errors; - }; - }; - - /* ----------------------------------------------------------- - SEQUENCE VALIDATOR - ----------------------------------------------------------- */ - const validateObject = (next: { - object: MetadataObjectType; - errors: string[]; - }): void => { - for (const property of next.object.properties) - validateProperty({ - metadata: property.value, - errors: next.errors, - }); - - const entire: Map = new Map(); - const visitProperty = (p: MetadataProperty) => { - const local: Set = new Set(); - const tagger = (matrix: IMetadataTypeTag[][]): void => { - matrix.forEach((tags) => { - const value: number | null = ProtobufUtil.getSequence(tags); - if (value !== null) local.add(value); - }); - }; - for (const c of p.value.constants) - for (const v of c.values) tagger(v.tags); - for (const a of p.value.atomics) tagger(a.tags); - for (const t of p.value.templates) tagger(t.tags); - for (const o of p.value.objects) tagger(o.tags); - for (const a of p.value.arrays) tagger(a.tags); - for (const s of local) - if (entire.has(s)) - next.errors.push( - `The Sequence<${s}> tag is duplicated in two properties (${JSON.stringify(entire.get(s))} and ${JSON.stringify(p.key.getSoleLiteral())})`, - ); - else entire.set(s, p.key.getSoleLiteral()!); - }; - for (const p of next.object.properties) visitProperty(p); - }; - - const validateProperty = (next: { - metadata: MetadataSchema; - errors: string[]; - }): void => { - let expected: number = 0; - const sequences: Set = new Set(); - const add = (value: number): boolean => { - if (sequences.has(value)) return false; - sequences.add(value); - ++expected; - return true; - }; - - for (const validator of [ - validateBooleanSequence, - validateNumericSequences({ - type: "bigint", - default: "int64", - categories: BIGINT_TYPES, - }), - validateNumericSequences({ - type: "number", - default: "double", - categories: NUMBER_TYPES, - }), - validateStringSequence, - ]) - validator({ metadata: next.metadata, errors: next.errors, add }); - for (const array of next.metadata.arrays) - validateInstanceSequence({ - type: "array", - tags: array.tags, - errors: next.errors, - add, - }); - for (const object of next.metadata.objects) - validateInstanceSequence({ - type: "object", - tags: object.tags, - errors: next.errors, - add, - }); - for (const map of next.metadata.maps) - validateInstanceSequence({ - type: "map", - tags: map.tags, - errors: next.errors, - add, - }); - for (const native of next.metadata.natives) - if (native.name === "Uint8Array") - validateInstanceSequence({ - type: "Uint8Array", - tags: native.tags, - errors: next.errors, - add, - }); - }; - - const validateBooleanSequence = (next: { - metadata: MetadataSchema; - errors: string[]; - add: (value: number) => boolean; - }): void => { - // PREPARE EMPLACER - const unique: Set = new Set(); - let expected: number = 0; - let actual: number = 0; - const emplace = (matrix: IMetadataTypeTag[][]): void => { - for (const tags of matrix) - for (const tag of tags) { - const sequence = ProtobufUtil.getSequence([tag]); - if (sequence !== null) { - unique.add(sequence); - ++actual; - } - ++expected; - } - }; - - // GATHER SEQUENCE TAGS - for (const atomic of next.metadata.atomics) - if (atomic.type === "boolean") emplace(atomic.tags); - for (const constant of next.metadata.constants) - if (constant.type === "boolean") - for (const value of constant.values) emplace(value.tags); - - // PREDICATE - if (unique.size && actual !== expected) - next.errors.push( - `The sequence tag must be declared in every union type members`, - ); - else if (unique.size > 1) - next.errors.push( - `The sequence tag value must be the same in boolean type (including literal types)`, - ); - else if (unique.size === 1) { - const value: number = unique.values().next().value!; - if (next.add(value) === false) - next.errors.push( - `The sequence tag value ${value} in boolean type is duplicated with other types`, - ); - } - }; - - const validateNumericSequences = - (config: { - type: "number" | "bigint"; - default: string; - categories: Set; - }) => - (next: { - metadata: MetadataSchema; - errors: string[]; - add: (value: number) => boolean; - }): void => { - // FIND TYPE CATEGORIES - const categories: Set = new Set(); - const getType = (tags: IMetadataTypeTag[]): string => { - const found: IMetadataTypeTag | undefined = tags.find( - (t) => t.kind === "type" && config.categories.has(t.value), - ); - return found?.value ?? config.default; - }; - const exploreCategory = (matrix: IMetadataTypeTag[][]): void => { - for (const tags of matrix) categories.add(getType(tags)); - }; - for (const atomic of next.metadata.atomics) - if (atomic.type === config.type) exploreCategory(atomic.tags); - for (const constant of next.metadata.constants) - if (constant.type === config.type) - for (const value of constant.values) exploreCategory(value.tags); - - // ITERATE TYPE CATEGORIES - for (const category of categories) { - const unique: Set = new Set(); - let expected: number = 0; - let actual: number = 0; - const emplace = (tags: IMetadataTypeTag[]): void => { - const sequence: number | null = ProtobufUtil.getSequence(tags); - if (sequence !== null) { - unique.add(sequence); - ++actual; - } - ++expected; - }; - - for (const atomic of next.metadata.atomics) - if (atomic.type === config.type) - for (const tags of atomic.tags) - if (getType(tags) === category) emplace(tags); - for (const constant of next.metadata.constants) - if (constant.type === config.type) - for (const value of constant.values) - for (const tags of value.tags) - if (getType(tags) === category) emplace(tags); - - if (unique.size && actual !== expected) { - next.errors.push( - `The sequence tag must be declared in every union type members`, - ); - } else if (unique.size > 1) - next.errors.push( - `The sequence tag value must be the same in ${config.type} type (including literal types)`, - ); - else if (unique.size === 1) { - const value: number = unique.values().next().value!; - if (next.add(value) === false) - next.errors.push( - `The sequence tag value ${value} in ${config.type} type is duplicated with other types`, - ); - } - } - }; - - const validateStringSequence = (next: { - metadata: MetadataSchema; - errors: string[]; - add: (value: number) => boolean; - }): void => { - const unique: Set = new Set(); - let expected: number = 0; - let actual: number = 0; - const emplace = (matrix: IMetadataTypeTag[][]): void => { - for (const tags of matrix) - for (const tag of tags) { - const sequence = ProtobufUtil.getSequence([tag]); - if (sequence !== null) { - unique.add(sequence); - ++actual; - } - ++expected; - } - }; - for (const atomic of next.metadata.atomics) - if (atomic.type === "string") emplace(atomic.tags); - for (const constant of next.metadata.constants) - if (constant.type === "string") - for (const value of constant.values) emplace(value.tags); - for (const template of next.metadata.templates) emplace(template.tags); - - if (unique.size && actual !== expected) - next.errors.push( - `The sequence tag must be declared in every union type members`, - ); - else if (unique.size > 1) - next.errors.push( - `The sequence tag value must be the same in string types including literal and template types`, - ); - else if (unique.size === 1) { - const value: number = unique.values().next().value!; - if (next.add(value) === false) - next.errors.push( - `The sequence tag value ${value} in string type is duplicated with other types`, - ); - } - }; - - const validateInstanceSequence = (next: { - type: "array" | "object" | "map" | "Uint8Array"; - tags: IMetadataTypeTag[][]; - errors: string[]; - add: (value: number) => boolean; - }): void => { - const unique: Set = new Set(); - let count: number = 0; - for (const tags of next.tags) { - const value: number | null = ProtobufUtil.getSequence(tags); - if (value === null) continue; - unique.add(value); - ++count; - } - if (unique.size && count !== next.tags.length) - next.errors.push( - `The sequence tag must be declared in every union type members`, - ); - else if (unique.size > 1) - next.errors.push( - `The sequence tag value must be the same in ${next.type === "array" ? "an array" : "object"} type.`, - ); - else if (unique.size === 1) { - const value: number = unique.values().next().value!; - if (next.add(value) === false) - next.errors.push( - `The sequence tag value ${value} in ${next.type} type is duplicated with other types`, - ); - } - }; -} - -const isDynamicObject = (obj: MetadataObjectType): boolean => - obj.properties[0]!.key.isSoleLiteral() === false; - -const isBoxedNativeObject = (obj: MetadataObjectType): boolean => - BANNED_NATIVE_TYPES.has(obj.name) && - obj.properties.length === 1 && - obj.properties[0]!.key.isSoleLiteral() === true && - obj.properties[0]!.key.getSoleLiteral()!.startsWith("__@toStringTag@"); - -const BANNED_NATIVE_TYPES: Map = new Map([ - ["Date", "string"], - ["Boolean", "boolean"], - ["BigInt", "bigint"], - ["Number", "number"], - ["String", "string"], - ...[ - "Buffer", - "Uint8ClampedArray", - "Uint16Array", - "Uint32Array", - "BigUint64Array", - "Int8Array", - "Int16Array", - "Int32Array", - "BigInt64Array", - "Float32Array", - "Float64Array", - "DataView", - "ArrayBuffer", - "SharedArrayBuffer", - ].map((name) => [name, "Uint8Array"] as const), - ["WeakSet", "Array"], - ["WeakMap", "Map"], -]); -const NUMBER_TYPES: Set = new Set([ - "int32", - "uint32", - "int64", - "uint64", - "float", - "double", -]); -const BIGINT_TYPES = new Set(["int64", "uint64"]); diff --git a/packages/core/src/factories/StatementFactory.ts b/packages/core/src/factories/StatementFactory.ts deleted file mode 100644 index 86ac04cbcd5..00000000000 --- a/packages/core/src/factories/StatementFactory.ts +++ /dev/null @@ -1,90 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { TypeFactory } from "./TypeFactory"; - -export namespace StatementFactory { - export const mut = (props: { - name: string; - type?: ts.TypeNode | undefined; - initializer?: ts.Expression | undefined; - }) => - ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - props.name, - undefined, - props.type !== undefined - ? props.type - : props.initializer === undefined - ? TypeFactory.keyword("any") - : undefined, - props.initializer, - ), - ], - ts.NodeFlags.Let, - ), - ); - - export const constant = (props: { - name: string; - type?: ts.TypeNode | undefined; - value?: ts.Expression | undefined; - }) => - ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - props.name, - undefined, - props.type !== undefined - ? props.type - : props.value === undefined - ? TypeFactory.keyword("any") - : undefined, - props.value, - ), - ], - ts.NodeFlags.Const, - ), - ); - - export const entry = (props: { key: string; value: string }) => - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createArrayBindingPattern([ - ts.factory.createBindingElement( - undefined, - undefined, - ts.factory.createIdentifier(props.key), - undefined, - ), - ts.factory.createBindingElement( - undefined, - undefined, - ts.factory.createIdentifier(props.value), - undefined, - ), - ]), - undefined, - undefined, - undefined, - ), - ], - ts.NodeFlags.Const, - ); - - export const transpile = (script: string) => - ts.factory.createExpressionStatement( - ts.factory.createIdentifier(ts.transpile(script)), - ); - - export const block = (expression: ts.Expression) => - ts.factory.createBlock( - [ts.factory.createExpressionStatement(expression)], - true, - ); -} diff --git a/packages/core/src/factories/TemplateFactory.ts b/packages/core/src/factories/TemplateFactory.ts deleted file mode 100644 index ccd0cd7e361..00000000000 --- a/packages/core/src/factories/TemplateFactory.ts +++ /dev/null @@ -1,64 +0,0 @@ -import ts from "@typescript/native-preview"; - -export namespace TemplateFactory { - export const generate = (expressions: ts.Expression[]): ts.Expression => { - if (expressions.every((exp) => ts.isStringLiteral(exp))) - return ts.factory.createStringLiteral( - (expressions as ts.StringLiteral[]).map((str) => str.text).join(""), - ); - - const iterator: IIterator = { - value: "", - index: 0, - }; - gather({ - expressions, - iterator, - }); - - const head: ts.TemplateHead = ts.factory.createTemplateHead(iterator.value); - const spans: ts.TemplateSpan[] = []; - - while (true) { - const elem: ts.Expression = expressions[iterator.index++]!; - gather({ - expressions, - iterator, - }); - - const broken: boolean = iterator.index === expressions.length; - spans.push( - ts.factory.createTemplateSpan( - elem, - broken - ? ts.factory.createTemplateTail(iterator.value) - : ts.factory.createTemplateMiddle(iterator.value), - ), - ); - if (broken === true) break; - } - return ts.factory.createTemplateExpression(head, spans); - }; - - const gather = (props: { - expressions: ts.Expression[]; - iterator: IIterator; - }): void => { - const found: number = props.expressions.findIndex( - (elem, index) => - index >= props.iterator.index && !ts.isStringLiteral(elem), - ); - - const last: number = found !== -1 ? found : props.expressions.length; - props.iterator.value = props.expressions - .slice(props.iterator.index, last) - .map((elem) => (elem as ts.StringLiteral).text) - .reduce((x, y) => x + y, ""); - props.iterator.index = last; - }; - - interface IIterator { - value: string; - index: number; - } -} diff --git a/packages/core/src/factories/TypeFactory.ts b/packages/core/src/factories/TypeFactory.ts deleted file mode 100644 index 40f064b2d56..00000000000 --- a/packages/core/src/factories/TypeFactory.ts +++ /dev/null @@ -1,142 +0,0 @@ -import ts from "@typescript/native-preview"; - -export namespace TypeFactory { - export const isFunction = (type: ts.Type): boolean => - getFunction(type) !== null; - - export const getFunction = (type: ts.Type) => { - const node = type.symbol?.declarations?.[0]; - if (node === undefined) return null; - - return ts.isFunctionLike(node) - ? node - : ts.isPropertyAssignment(node) || ts.isPropertyDeclaration(node) - ? ts.isFunctionLike(node.initializer) - ? node.initializer - : null - : null; - }; - - export const getReturnTypeOfClassMethod = (props: { - checker: ts.TypeChecker; - class: ts.Type; - function: string; - }): ts.Type | null => { - // FIND TO-JSON METHOD - const symbol: ts.Symbol | undefined = props.class.getProperty( - props.function, - ); - if (!symbol) return null; - else if (!symbol.valueDeclaration) return null; - - // GET FUNCTION DECLARATION - const functor: ts.Type = props.checker.getTypeOfSymbolAtLocation( - symbol, - symbol.valueDeclaration, - ); - - // RETURNS THE RETURN-TYPE - const signature: ts.Signature | undefined = - props.checker.getSignaturesOfType(functor, ts.SignatureKind.Call)[0]; - return signature ? signature.getReturnType() : null; - }; - - export const getFullName = (props: { - checker: ts.TypeChecker; - type: ts.Type; - symbol?: ts.Symbol; - aliasTypeArguments?: boolean; // default: true - }): string => { - // PRIMITIVE - const symbol = - props.symbol ?? props.type.aliasSymbol ?? props.type.getSymbol(); - if (symbol === undefined) return props.checker.typeToString(props.type); - - // UNION OR INTERSECT - if ( - props.type.aliasSymbol === undefined && - props.type.isUnionOrIntersection() - ) { - const joiner: string = props.type.isIntersection() ? " & " : " | "; - return props.type.types - .map((child) => - getFullName({ - checker: props.checker, - type: child, - }), - ) - .join(joiner); - } - - //---- - // SPECIALIZATION - //---- - const name: string = get_name(symbol); - - // CHECK GENERIC - const generic: readonly ts.Type[] = - props.type.aliasSymbol && props.aliasTypeArguments !== false - ? (props.type.aliasTypeArguments ?? []) - : props.checker.getTypeArguments(props.type as ts.TypeReference); - return generic.length - ? name === "Promise" - ? getFullName({ - checker: props.checker, - type: generic[0]!, - }) - : `${name}<${generic - .map((child) => - getFullName({ - checker: props.checker, - type: child, - }), - ) - .join(", ")}>` - : name; - }; - - const explore_name = (props: { node: ts.Node; name: string }): string => - ts.isModuleBlock(props.node) - ? explore_name({ - node: props.node.parent.parent, - name: `${props.node.parent.name.getFullText().trim()}.${props.name}`, - }) - : props.name; - - const get_name = (symbol: ts.Symbol): string => { - const parent = symbol.getDeclarations()?.[0]?.parent; - return parent - ? explore_name({ - node: parent, - name: symbol.escapedName.toString(), - }) - : "__type"; - }; - - export const keyword = ( - type: - | "void" - | "any" - | "unknown" - | "boolean" - | "number" - | "bigint" - | "string", - ) => { - return ts.factory.createKeywordTypeNode( - type === "void" - ? ts.SyntaxKind.VoidKeyword - : type === "any" - ? ts.SyntaxKind.AnyKeyword - : type === "unknown" - ? ts.SyntaxKind.UnknownKeyword - : type === "boolean" - ? ts.SyntaxKind.BooleanKeyword - : type === "number" - ? ts.SyntaxKind.NumberKeyword - : type === "bigint" - ? ts.SyntaxKind.BigIntKeyword - : ts.SyntaxKind.StringKeyword, - ); - }; -} diff --git a/packages/core/src/factories/ValueFactory.ts b/packages/core/src/factories/ValueFactory.ts deleted file mode 100644 index 45314f939fa..00000000000 --- a/packages/core/src/factories/ValueFactory.ts +++ /dev/null @@ -1,12 +0,0 @@ -import ts from "@typescript/native-preview"; - -export namespace ValueFactory { - export const NULL = () => ts.factory.createNull(); - export const UNDEFINED = () => ts.factory.createIdentifier("undefined"); - export const BOOLEAN = (value: boolean) => - value ? ts.factory.createTrue() : ts.factory.createFalse(); - export const INPUT = (str: string = "input") => - ts.factory.createIdentifier(str); - export const TYPEOF = (input: ts.Expression) => - ts.factory.createTypeOfExpression(input); -} diff --git a/packages/core/src/factories/index.ts b/packages/core/src/factories/index.ts deleted file mode 100644 index a1b3ced1b1c..00000000000 --- a/packages/core/src/factories/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * from "./CommentFactory"; -export * from "./ExpressionFactory"; -export * from "./FormatCheatSheet"; -export * from "./IdentifierFactory"; -export * from "./JsonMetadataFactory"; -export * from "./LiteralFactory"; -export * from "./MetadataCommentTagFactory"; -export * from "./MetadataFactory"; -export * from "./MetadataTypeTagFactory"; -export * from "./MetadataTypeTagSchemaFactory"; -export * from "./NumericRangeFactory"; -export * from "./ProtobufFactory"; -export * from "./StatementFactory"; -export * from "./TemplateFactory"; -export * from "./TypeFactory"; -export * from "./ValueFactory"; diff --git a/packages/core/src/factories/internal/metadata/IMetadataIteratorProps.ts b/packages/core/src/factories/internal/metadata/IMetadataIteratorProps.ts deleted file mode 100644 index 606a6b86b52..00000000000 --- a/packages/core/src/factories/internal/metadata/IMetadataIteratorProps.ts +++ /dev/null @@ -1,16 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataCollection } from "../../../schemas/metadata/MetadataCollection"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { MetadataFactory } from "../../MetadataFactory"; - -export interface IMetadataIteratorProps { - options: MetadataFactory.IOptions; - checker: ts.TypeChecker; - components: MetadataCollection; - errors: MetadataFactory.IError[]; - metadata: MetadataSchema; - type: Type; - explore: MetadataFactory.IExplore; - intersected?: boolean; -} diff --git a/packages/core/src/factories/internal/metadata/MetadataHelper.ts b/packages/core/src/factories/internal/metadata/MetadataHelper.ts deleted file mode 100644 index 31e5d87267a..00000000000 --- a/packages/core/src/factories/internal/metadata/MetadataHelper.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { MetadataConstant } from "../../../schemas/metadata/MetadataConstant"; -import { MetadataConstantValue } from "../../../schemas/metadata/MetadataConstantValue"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; - -export namespace MetadataHelper { - export const literal_to_metadata = (key: string): MetadataSchema => { - const metadata: MetadataSchema = MetadataSchema.initialize(); - metadata.constants.push( - MetadataConstant.create({ - type: "string", - values: [ - MetadataConstantValue.create({ - value: key, - tags: [], - }), - ], - }), - ); - return metadata; - }; -} diff --git a/packages/core/src/factories/internal/metadata/emend_metadata_atomics.ts b/packages/core/src/factories/internal/metadata/emend_metadata_atomics.ts deleted file mode 100644 index 1dcdb68958d..00000000000 --- a/packages/core/src/factories/internal/metadata/emend_metadata_atomics.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; - -import { MetadataAtomic } from "../../../schemas/metadata/MetadataAtomic"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; - -export const emend_metadata_atomics = (meta: MetadataSchema) => { - // ATOMICS - for (const a of meta.atomics) { - if (is_not_pure(a)) continue; - const index: number = meta.constants.findIndex((c) => c.type === a.type); - if (index !== -1) meta.constants.splice(index, 1); - } - - // BOOLEAN - { - const index: number = meta.constants.findIndex((c) => c.type === "boolean"); - if (index !== -1 && meta.constants[index]!.values.length === 2) { - const temp = meta.constants.splice(index, 1)[0]!; - ArrayUtil.take( - meta.atomics, - (a) => a.type === "boolean", - () => - MetadataAtomic.create({ - type: "boolean" as const, - tags: temp.values[0]!.tags ?? [], - }), - ); - } - } - - // TEMPLATE - if (meta.templates.length) { - const atomic: MetadataAtomic | undefined = meta.atomics.find( - (a) => a.type === "string", - ); - if (atomic !== undefined && false === is_not_pure(atomic)) - meta.templates.splice(0, meta.templates.length); - } -}; - -const is_not_pure = (atomic: MetadataAtomic): boolean => - atomic.tags.length !== 0 && - atomic.tags.every( - (row) => row.length !== 0 && row.every((c) => !!c.validate?.length), - ); diff --git a/packages/core/src/factories/internal/metadata/emplace_metadata_alias.ts b/packages/core/src/factories/internal/metadata/emplace_metadata_alias.ts deleted file mode 100644 index b9f3e2cef77..00000000000 --- a/packages/core/src/factories/internal/metadata/emplace_metadata_alias.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; - -import { MetadataAliasType } from "../../../schemas/metadata/MetadataAliasType"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; - -export const emplace_metadata_alias = ( - props: IMetadataIteratorProps, -): MetadataAliasType => { - // CHECK EXISTENCE - const [alias, newbie, closure] = props.components.emplaceAlias( - props.checker, - props.type, - props.type.aliasSymbol!, - ); - ArrayUtil.add(alias.nullables, props.metadata.nullable); - if (newbie === false) return alias; - - // CONSTRUCT VALUE TYPE - const value: MetadataSchema = explore_metadata({ - ...props, - explore: { - ...props.explore, - escaped: false, - aliased: true, - }, - intersected: false, - }); - closure(value); - return alias; -}; diff --git a/packages/core/src/factories/internal/metadata/emplace_metadata_array_type.ts b/packages/core/src/factories/internal/metadata/emplace_metadata_array_type.ts deleted file mode 100644 index 08718de706c..00000000000 --- a/packages/core/src/factories/internal/metadata/emplace_metadata_array_type.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataArrayType } from "../../../schemas/metadata/MetadataArrayType"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; - -interface IProps extends IMetadataIteratorProps { - array: ts.Type; -} - -export const emplace_metadata_array_type = ( - props: IProps, -): MetadataArrayType => { - // CHECK EXISTENCE - const [array, newbie, setValue] = props.components.emplaceArray( - props.checker, - props.type, - ); - ArrayUtil.add(array.nullables, props.metadata.nullable); - if (newbie === false) return array; - - // CONSTRUCT VALUE TYPE - const value: MetadataSchema = explore_metadata({ - ...props, - type: props.array.getNumberIndexType()!, - explore: { - ...props.explore, - escaped: false, - aliased: false, - }, - intersected: false, - }); - setValue(value); - return array; -}; diff --git a/packages/core/src/factories/internal/metadata/emplace_metadata_object.ts b/packages/core/src/factories/internal/metadata/emplace_metadata_object.ts deleted file mode 100644 index 5242c78b840..00000000000 --- a/packages/core/src/factories/internal/metadata/emplace_metadata_object.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { Writable } from "../../../typings/Writable"; -import { CommentFactory } from "../../CommentFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { MetadataHelper } from "./MetadataHelper"; -import { explore_metadata } from "./explore_metadata"; -import { iterate_metadata_coalesce } from "./iterate_metadata_coalesce"; - -export const emplace_metadata_object = ( - props: IMetadataIteratorProps, -): MetadataObjectType => { - // EMPLACE OBJECT - const [obj, newbie] = props.components.emplace(props.checker, props.type); - ArrayUtil.add( - obj.nullables, - props.metadata.nullable, - (elem) => elem === props.metadata.nullable, - ); - - if (newbie === false) return obj; - - // PREPARE ASSETS - const isClass: boolean = props.type.isClass(); - const isProperty = significant(!!props.options.functional); - const pred: (node: ts.Declaration) => boolean = isClass - ? (node) => { - const capsuled: boolean = node - .getChildren() - .some((c) => - c - .getChildren() - .some( - (n) => - n.kind === ts.SyntaxKind.PrivateKeyword || - n.kind === ts.SyntaxKind.ProtectedKeyword, - ), - ); - return capsuled === false && isProperty(node); - } - : (node) => isProperty(node); - - const insert = (props: { - key: MetadataSchema; - value: MetadataSchema; - symbol: ts.Symbol | undefined; - node?: ts.Declaration; - filter?: (doc: ts.JSDocTagInfo) => boolean; - }): MetadataProperty => { - // COMMENTS AND TAGS - const description: string | null = props.symbol - ? (CommentFactory.description(props.symbol) ?? null) - : null; - const jsDocTags: ts.JSDocTagInfo[] = ( - props.symbol?.getJsDocTags() ?? [] - ).filter(props.filter ?? (() => true)); - - // CHECK MUTABILITY - const mutability: "readonly" | null | undefined = - props.node && - ts.canHaveModifiers(props.node) && - ts - .getModifiers(props.node) - ?.some( - (modifier) => modifier.kind === ts.SyntaxKind.ReadonlyKeyword, - ) === true - ? "readonly" - : null; - - // THE PROPERTY - const property: MetadataProperty = MetadataProperty.create({ - key: props.key, - value: props.value, - description, - jsDocTags, - mutability, - }); - obj.properties.push(property); - return property; - }; - - //---- - // REGULAR PROPERTIES - //---- - for (const symbol of props.type.getApparentProperties()) { - // CHECK INTERNAL TAG - if ( - (symbol.getJsDocTags(props.checker) ?? []).find( - (tag) => tag.name === "internal", - ) !== undefined - ) - continue; - - // CHECK NODE IS A FORMAL PROPERTY - const [node, type] = (() => { - const node = symbol.getDeclarations()?.[0] as - | ts.PropertyDeclaration - | undefined; - const type: ts.Type | undefined = node - ? props.checker.getTypeOfSymbolAtLocation(symbol, node) - : props.checker.getTypeOfPropertyOfType(props.type, symbol.name); - return [node, type]; - })(); - if ((node && pred(node) === false) || type === undefined) continue; - - // GET EXACT TYPE - const key: MetadataSchema = MetadataHelper.literal_to_metadata(symbol.name); - const value: MetadataSchema = explore_metadata({ - ...props, - type, - explore: { - top: false, - object: obj, - property: symbol.name, - parameter: null, - nested: null, - aliased: false, - escaped: false, - output: false, - }, - intersected: false, - }); - Writable(value).optional = (symbol.flags & ts.SymbolFlags.Optional) !== 0; - insert({ - key, - value, - symbol, - node, - }); - } - - //---- - // DYNAMIC PROPERTIES - //---- - for (const index of props.checker.getIndexInfosOfType(props.type)) { - // GET EXACT TYPE - const analyzer = (type: ts.Type) => (property: {} | null) => - explore_metadata({ - ...props, - type, - explore: { - top: false, - object: obj, - property, - parameter: null, - nested: null, - aliased: false, - escaped: false, - output: false, - }, - intersected: false, - }); - const key: MetadataSchema = analyzer(index.keyType)(null); - const value: MetadataSchema = analyzer(index.type)({}); - - if ( - key.atomics.length + - key.constants.map((c) => c.values.length).reduce((a, b) => a + b, 0) + - key.templates.length + - key.natives.filter( - (native) => - native.name === "Boolean" || - native.name === "BigInt" || - native.name === "Number" || - native.name === "String", - ).length !== - key.size() - ) - props.errors.push({ - name: key.getName(), - explore: { - top: false, - object: obj, - property: "[key]", - parameter: null, - nested: null, - aliased: false, - escaped: false, - output: false, - }, - messages: [], - }); - - // INSERT WITH REQUIRED CONFIGURATION - insert({ - key, - value, - symbol: index.declaration?.parent - ? props.checker.getSymbolAtLocation(index.declaration.parent) - : undefined, - node: index.declaration, - filter: (doc) => doc.name !== "default", - }); - } - return obj; -}; - -const significant = (functional: boolean) => - functional - ? (node: ts.Declaration) => !ts.isAccessor(node) - : (node: ts.Declaration) => - ts.isParameter(node) || - ts.isPropertyDeclaration(node) || - ts.isPropertyAssignment(node) || - ts.isPropertySignature(node) || - ts.isTypeLiteralNode(node) || - ts.isShorthandPropertyAssignment(node); - -const iterate_optional_coalesce = (props: { - metadata: MetadataSchema; - type: ts.Type; -}): void => { - if (props.type.isUnionOrIntersection()) - props.type.types.forEach((child) => - iterate_optional_coalesce({ - metadata: props.metadata, - type: child, - }), - ); - else iterate_metadata_coalesce(props); -}; diff --git a/packages/core/src/factories/internal/metadata/emplace_metadata_tuple.ts b/packages/core/src/factories/internal/metadata/emplace_metadata_tuple.ts deleted file mode 100644 index 505f3795015..00000000000 --- a/packages/core/src/factories/internal/metadata/emplace_metadata_tuple.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { MetadataTupleType } from "../../../schemas/metadata/MetadataTupleType"; -import { Writable } from "../../../typings/Writable"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; - -export const emplace_metadata_tuple = ( - props: IMetadataIteratorProps, -): MetadataTupleType => { - // CHECK EXISTENCE - const [tuple, newbie, closure] = props.components.emplaceTuple( - props.checker, - props.type, - ); - ArrayUtil.add(tuple.nullables, props.metadata.nullable); - if (newbie === false) return tuple; - - // CONSTRUCT ELEMENT TYPES - const flagList: readonly ts.ElementFlags[] = - props.type.elementFlags ?? - (props.type.target as ts.TupleType)?.elementFlags ?? - []; - const elements: MetadataSchema[] = props.checker - .getTypeArguments(props.type as ts.TypeReference) - .map((elem, i) => { - const child: MetadataSchema = explore_metadata({ - ...props, - type: elem, - explore: { - ...props.explore, - nested: tuple, - aliased: false, - escaped: false, - }, - intersected: false, - }); - - // CHECK OPTIONAL - const flag: ts.ElementFlags | undefined = flagList[i]; - if (flag === ts.ElementFlags.Optional) Writable(child).optional = true; - - // REST TYPE - if (flag !== ts.ElementFlags.Rest) return child; - const wrapper: MetadataSchema = MetadataSchema.initialize(); - Writable(wrapper).rest = child; - return wrapper; - }); - closure(elements); - - return tuple; -}; diff --git a/packages/core/src/factories/internal/metadata/explore_metadata.ts b/packages/core/src/factories/internal/metadata/explore_metadata.ts deleted file mode 100644 index cee508db9a7..00000000000 --- a/packages/core/src/factories/internal/metadata/explore_metadata.ts +++ /dev/null @@ -1,32 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { emend_metadata_atomics } from "./emend_metadata_atomics"; -import { iterate_metadata } from "./iterate_metadata"; - -export const explore_metadata = (props: Required): MetadataSchema => { - // CONSTRUCT METADATA - const metadata: MetadataSchema = MetadataSchema.initialize( - props.explore.escaped, - ); - if (props.type === null) return metadata; - - // ITERATE TYPESCRIPT TYPES - props.intersected ??= false; - iterate_metadata({ - ...props, - metadata, - type: props.type, - }); - emend_metadata_atomics(metadata); - if (metadata.escaped) { - emend_metadata_atomics(metadata.escaped.original); - emend_metadata_atomics(metadata.escaped.returns); - } - return metadata; -}; - -interface IProps extends Omit { - type: ts.Type | null; -} diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata.ts b/packages/core/src/factories/internal/metadata/iterate_metadata.ts deleted file mode 100644 index a8474a9ec81..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata.ts +++ /dev/null @@ -1,54 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { TypeFactory } from "../../TypeFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { iterate_metadata_alias } from "./iterate_metadata_alias"; -import { iterate_metadata_array } from "./iterate_metadata_array"; -import { iterate_metadata_atomic } from "./iterate_metadata_atomic"; -import { iterate_metadata_coalesce } from "./iterate_metadata_coalesce"; -import { iterate_metadata_constant } from "./iterate_metadata_constant"; -import { iterate_metadata_escape } from "./iterate_metadata_escape"; -import { iterate_metadata_function } from "./iterate_metadata_function"; -import { iterate_metadata_intersection } from "./iterate_metadata_intersection"; -import { iterate_metadata_map } from "./iterate_metadata_map"; -import { iterate_metadata_native } from "./iterate_metadata_native"; -import { iterate_metadata_object } from "./iterate_metadata_object"; -import { iterate_metadata_set } from "./iterate_metadata_set"; -import { iterate_metadata_template } from "./iterate_metadata_template"; -import { iterate_metadata_tuple } from "./iterate_metadata_tuple"; -import { iterate_metadata_union } from "./iterate_metadata_union"; - -export const iterate_metadata = (props: IMetadataIteratorProps): void => { - if (props.type.isTypeParameter() === true) { - props.errors.push({ - name: TypeFactory.getFullName({ - checker: props.checker, - type: props.type, - }), - explore: { ...props.explore }, - messages: ["non-specified generic argument found."], - }); - return; - } - // CHECK SPECIAL CASES - if ( - (props.explore.aliased !== true && iterate_metadata_alias(props)) || - iterate_metadata_intersection(props) || - iterate_metadata_union(props) || - iterate_metadata_escape(props) - ) - return; - - // ITERATE CASES - iterate_metadata_coalesce(props) || - iterate_metadata_function(props) || - iterate_metadata_constant(props) || - iterate_metadata_template(props) || - iterate_metadata_atomic(props) || - iterate_metadata_tuple(props as IMetadataIteratorProps) || - iterate_metadata_array(props) || - iterate_metadata_native(props) || - iterate_metadata_map(props) || - iterate_metadata_set(props) || - iterate_metadata_object(props); -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_alias.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_alias.ts deleted file mode 100644 index 97ad91bf9a6..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_alias.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataAlias } from "../../../schemas/metadata/MetadataAlias"; -import { MetadataAliasType } from "../../../schemas/metadata/MetadataAliasType"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { emplace_metadata_alias } from "./emplace_metadata_alias"; - -export const iterate_metadata_alias = ( - props: IMetadataIteratorProps, -): boolean => { - if (props.options.absorb !== false || props.type.aliasSymbol === undefined) - return false; - - const node: ts.Declaration | undefined = - props.type.aliasSymbol.declarations?.[0]; - if (node === undefined) return false; - - // CONSTRUCT DEFINITION - const type: MetadataAliasType = emplace_metadata_alias(props); - ArrayUtil.take( - props.metadata.aliases, - (elem) => elem.type.name === type.name, - () => - MetadataAlias.create({ - type, - tags: [], - }), - ); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_array.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_array.ts deleted file mode 100644 index cede5c6d296..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_array.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataArray } from "../../../schemas/metadata/MetadataArray"; -import { MetadataArrayType } from "../../../schemas/metadata/MetadataArrayType"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { emplace_metadata_array_type } from "./emplace_metadata_array_type"; - -export const iterate_metadata_array = ( - props: IMetadataIteratorProps, -): boolean => { - const array: ts.Type | null = - props.checker.isArrayType(props.type) === false - ? find_array_extended({ - checker: props.checker, - memory: new Map(), - type: props.type, - }) - : props.type; - if (array === null) return false; - - const arrayType: MetadataArrayType = emplace_metadata_array_type({ - ...props, - array, - }); - ArrayUtil.add( - props.metadata.arrays, - MetadataArray.create({ - type: arrayType, - tags: [], - }), - (elem) => elem.type.name === arrayType.name, - ); - return true; -}; - -const find_array_extended = (props: { - checker: ts.TypeChecker; - memory: Map; - type: ts.Type; -}): ts.Type | null => { - const cached = props.memory.get(props.type); - if (cached !== undefined) return null; - - props.memory.set(props.type, null); - const res: ts.Type | null = (() => { - if (props.type.isClassOrInterface() === false) return null; - for (const t of props.type.resolvedBaseTypes ?? []) - if (props.checker.isArrayType(t)) return t; - else { - const res: ts.Type | null = find_array_extended({ - ...props, - type: t, - }); - if (res !== null) return res; - } - return null; - })(); - props.memory.set(props.type, res); - return res; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_atomic.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_atomic.ts deleted file mode 100644 index ef702830d85..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_atomic.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataAtomic } from "../../../schemas/metadata/MetadataAtomic"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; - -const same = (type: ts.Type | null) => { - if (type === null) return () => false; - return (flag: ts.TypeFlags) => (type.getFlags() & flag) !== 0; -}; - -export const iterate_metadata_atomic = (props: { - metadata: MetadataSchema; - type: ts.Type; -}): boolean => { - // PREPARE INTERNAL FUNCTIONS - const filter = same(props.type); - const check = (info: IAtomicInfo) => { - if (filter(info.atomic) || filter(info.literal)) { - ArrayUtil.add( - props.metadata.atomics, - MetadataAtomic.create({ type: info.name, tags: [] }), - (x, y) => x.type === y.type, - ); - return true; - } - return false; - }; - - // CHECK EACH TYPES - return ATOMICS.some((info) => check(info)); -}; - -const ATOMICS: IAtomicInfo[] = [ - { - name: "boolean", - atomic: ts.TypeFlags.BooleanLike, - literal: ts.TypeFlags.BooleanLiteral, - }, - { - name: "number", - atomic: ts.TypeFlags.NumberLike, - literal: ts.TypeFlags.NumberLiteral, - }, - { - name: "bigint", - atomic: ts.TypeFlags.BigInt, - literal: ts.TypeFlags.BigIntLiteral, - }, - { - name: "string", - atomic: ts.TypeFlags.StringLike, - literal: ts.TypeFlags.StringLiteral, - }, -]; - -interface IAtomicInfo { - name: "boolean" | "number" | "bigint" | "string"; - atomic: ts.TypeFlags; - literal: ts.TypeFlags; -} diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_coalesce.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_coalesce.ts deleted file mode 100644 index 66d1261476b..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_coalesce.ts +++ /dev/null @@ -1,27 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { Writable } from "../../../typings/Writable"; - -export const iterate_metadata_coalesce = (props: { - metadata: MetadataSchema; - type: ts.Type; -}): boolean => { - const filter = (flag: ts.TypeFlags) => (props.type.getFlags() & flag) !== 0; - if (filter(ts.TypeFlags.Unknown) || filter(ts.TypeFlags.Any)) { - Writable(props.metadata).any = true; - return true; - } else if (filter(ts.TypeFlags.Null)) { - Writable(props.metadata).nullable = true; - return true; - } else if ( - filter(ts.TypeFlags.Undefined) || - filter(ts.TypeFlags.Never) || - filter(ts.TypeFlags.Void) || - filter(ts.TypeFlags.VoidLike) - ) { - Writable(props.metadata).required = false; - return true; - } - return false; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_collection.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_collection.ts deleted file mode 100644 index e851d627dfc..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_collection.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { MetadataArrayType } from "../../../schemas/metadata/MetadataArrayType"; -import { MetadataCollection } from "../../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { MetadataTupleType } from "../../../schemas/metadata/MetadataTupleType"; -import { MetadataFactory } from "../../MetadataFactory"; -import { iterate_metadata_comment_tags } from "./iterate_metadata_comment_tags"; - -export const iterate_metadata_collection = (props: { - errors: MetadataFactory.IError[]; - collection: MetadataCollection; -}): void => { - for (const array of props.collection.arrays()) - if (array.recursive === null) - props.collection.setArrayRecursive( - array, - isArrayRecursive({ - visited: new Set(), - array, - metadata: array.value, - }), - ); - for (const tuple of props.collection.tuples()) - if (tuple.recursive === null) { - const visited: Set = new Set(); - props.collection.setTupleRecursive( - tuple, - tuple.elements.some((e) => - isTupleRecursive({ - visited, - tuple, - metadata: e, - }), - ), - ); - } - for (const object of props.collection.objects()) { - iterate_metadata_comment_tags({ - errors: props.errors, - object, - }); - if (object.recursive === null) { - const visited: Set = new Set(); - props.collection.setObjectRecursive( - object, - object.properties.some((p) => - isObjectRecursive({ - visited, - object, - metadata: p.value, - }), - ), - ); - } - } -}; - -const isArrayRecursive = (props: { - visited: Set; - array: MetadataArrayType; - metadata: MetadataSchema; -}): boolean => { - if (props.visited.has(props.metadata)) return false; - props.visited.add(props.metadata); - - const next = (metadata: MetadataSchema): boolean => - isArrayRecursive({ - ...props, - metadata, - }); - return ( - props.metadata.arrays.some( - (a) => a.type === props.array || next(a.type.value), - ) || - props.metadata.aliases.some((alias) => next(alias.type.value)) || - props.metadata.tuples.some( - (t) => !t.type.recursive && t.type.elements.some(next), - ) || - props.metadata.maps.some((m) => next(m.value)) || - props.metadata.sets.some((s) => next(s.value)) || - (props.metadata.escaped !== null && next(props.metadata.escaped.returns)) || - (props.metadata.rest !== null && next(props.metadata.rest)) - ); -}; - -const isTupleRecursive = (props: { - visited: Set; - tuple: MetadataTupleType; - metadata: MetadataSchema; -}): boolean => { - if (props.visited.has(props.metadata)) return false; - props.visited.add(props.metadata); - - const next = (metadata: MetadataSchema): boolean => - isTupleRecursive({ - ...props, - metadata, - }); - return ( - props.metadata.tuples.some( - (t) => t.type === props.tuple || t.type.elements.some(next), - ) || - props.metadata.arrays.some( - (a) => !a.type.recursive && next(a.type.value), - ) || - props.metadata.maps.some((m) => next(m.value)) || - props.metadata.sets.some((s) => next(s.value)) || - props.metadata.aliases.some((alias) => next(alias.type.value)) || - (props.metadata.escaped !== null && next(props.metadata.escaped.returns)) || - (props.metadata.rest !== null && next(props.metadata.rest)) - ); -}; - -const isObjectRecursive = (props: { - visited: Set; - object: MetadataObjectType; - metadata: MetadataSchema; -}): boolean => { - if (props.visited.has(props.metadata)) return false; - props.visited.add(props.metadata); - - const next = (metadata: MetadataSchema): boolean => - isObjectRecursive({ - ...props, - metadata, - }); - return ( - props.metadata.objects.some( - (o) => - props.object === o.type || - o.type.properties.some((prop) => next(prop.value)), - ) || - props.metadata.aliases.some((alias) => next(alias.type.value)) || - props.metadata.arrays.some( - (array) => !array.type.recursive && next(array.type.value), - ) || - props.metadata.tuples.some( - (tuple) => !tuple.type.recursive && tuple.type.elements.some(next), - ) || - props.metadata.maps.some((map) => next(map.value)) || - props.metadata.sets.some((set) => next(set.value)) || - (props.metadata.escaped !== null && next(props.metadata.escaped.returns)) || - (props.metadata.rest !== null && next(props.metadata.rest)) - ); -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_comment_tags.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_comment_tags.ts deleted file mode 100644 index 72651467e87..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_comment_tags.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; -import { MetadataCommentTagFactory } from "../../MetadataCommentTagFactory"; -import { MetadataFactory } from "../../MetadataFactory"; - -export const iterate_metadata_comment_tags = (props: { - errors: MetadataFactory.IError[]; - object: MetadataObjectType; -}) => { - if (props.object.tagged_ === true) return; - props.object.tagged_ = true; - - for (const property of props.object.properties) { - MetadataCommentTagFactory.analyze({ - errors: props.errors, - metadata: property.value, - tags: property.jsDocTags, - explore: { - top: false, - object: props.object, - property: property.key.isSoleLiteral() - ? property.key.getSoleLiteral()! - : {}, - parameter: null, - nested: null, - aliased: false, - escaped: false, - output: false, - }, - }); - } -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_constant.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_constant.ts deleted file mode 100644 index 695f7f5adcf..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_constant.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataConstant } from "../../../schemas/metadata/MetadataConstant"; -import { MetadataConstantValue } from "../../../schemas/metadata/MetadataConstantValue"; -import { CommentFactory } from "../../CommentFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; - -export const iterate_metadata_constant = ( - props: IMetadataIteratorProps, -): boolean => { - if (!props.options.constant) return false; - - const filter = (flag: ts.TypeFlags) => (props.type.getFlags() & flag) !== 0; - const comment = () => { - if (!filter(ts.TypeFlags.EnumLiteral)) return {}; - return { - jsDocTags: props.type.symbol?.getJsDocTags(), - description: props.type.symbol - ? (CommentFactory.description(props.type.symbol) ?? null) - : undefined, - }; - }; - if (props.type.isLiteral()) { - const value: string | number | bigint = - typeof props.type.value === "object" - ? BigInt( - `${props.type.value.negative ? "-" : ""}${props.type.value.base10Value}`, - ) - : props.type.value; - const constant: MetadataConstant = ArrayUtil.take( - props.metadata.constants, - (elem) => elem.type === typeof value, - () => - MetadataConstant.create({ - type: typeof value as "number", - values: [], - }), - ); - ArrayUtil.add( - constant.values, - MetadataConstantValue.create({ - value, - tags: [], - ...comment(), - }), - (a, b) => a.value === b.value, - ); - return true; - } else if (filter(ts.TypeFlags.BooleanLiteral)) { - const value: boolean = props.checker.typeToString(props.type) === "true"; - const constant: MetadataConstant = ArrayUtil.take( - props.metadata.constants, - (elem) => elem.type === "boolean", - () => - MetadataConstant.create({ - type: "boolean", - values: [], - }), - ); - ArrayUtil.add( - constant.values, - MetadataConstantValue.create({ - value, - tags: [], - ...comment(), - }), - (a, b) => a.value === b.value, - ); - return true; - } - return false; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_escape.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_escape.ts deleted file mode 100644 index 118ccf056a9..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_escape.ts +++ /dev/null @@ -1,47 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataEscaped } from "../../../schemas/metadata/MetadataEscaped"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { Writable } from "../../../typings/Writable"; -import { TypeFactory } from "../../TypeFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { iterate_metadata } from "./iterate_metadata"; - -export const iterate_metadata_escape = ( - props: IMetadataIteratorProps, -): boolean => { - if (props.options.escape === false || props.explore.escaped === true) - return false; - - const escaped: ts.Type | null = TypeFactory.getReturnTypeOfClassMethod({ - checker: props.checker, - class: props.type, - function: "toJSON", - }); - if (escaped === null) return false; - - if (props.metadata.escaped === null) { - Writable(props.metadata).escaped = MetadataEscaped.create({ - original: MetadataSchema.initialize(), - returns: MetadataSchema.initialize(), - }); - } - iterate_metadata({ - ...props, - metadata: props.metadata.escaped!.original, - explore: { - ...props.explore, - escaped: true, - }, - }); - iterate_metadata({ - ...props, - metadata: props.metadata.escaped!.returns, - type: escaped, - explore: { - ...props.explore, - escaped: true, - }, - }); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_function.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_function.ts deleted file mode 100644 index 720e5427e62..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_function.ts +++ /dev/null @@ -1,89 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { FunctionalGeneralProgrammer } from "../../../programmers/functional/internal/FunctionalGeneralProgrammer"; -import { MetadataFunction } from "../../../schemas/metadata/MetadataFunction"; -import { MetadataParameter } from "../../../schemas/metadata/MetadataParameter"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { CommentFactory } from "../../CommentFactory"; -import { TypeFactory } from "../../TypeFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; - -export const iterate_metadata_function = ( - props: IMetadataIteratorProps, -): boolean => { - const declaration: ts.SignatureDeclaration | null = TypeFactory.getFunction( - props.type, - ); - if (declaration === null) return false; - else if (!props.options.functional) { - if (props.metadata.functions.length === 0) - props.metadata.functions.push( - MetadataFunction.create({ - parameters: [], - output: MetadataSchema.initialize(), - async: false, - }), - ); - } else { - const [signature] = props.type.getCallSignatures(); - if (signature === undefined || signature.declaration === undefined) - props.metadata.functions.push( - MetadataFunction.create({ - parameters: [], - output: MetadataSchema.initialize(), - async: false, - }), - ); - else { - const { async }: FunctionalGeneralProgrammer.IOutput = - FunctionalGeneralProgrammer.getReturnType({ - checker: props.checker, - declaration, - }); - props.metadata.functions.push( - MetadataFunction.create({ - parameters: signature.parameters.map((p) => - MetadataParameter.create({ - name: p.name, - type: explore_metadata({ - ...props, - type: props.checker.getTypeOfSymbol(p), - explore: { - ...props.explore, - top: false, - parameter: p.name, - }, - intersected: false, - }), - tsType: props.checker.getTypeOfSymbol(p), - description: CommentFactory.description(p) ?? null, - jsDocTags: p?.getJsDocTags() ?? [], - }), - ), - async, - output: explore_metadata({ - ...props, - options: { - ...props.options, - functional: false, - }, - type: async - ? (props.checker.getTypeArguments( - signature.getReturnType() as ts.TypeReference, - )?.[0] ?? - props.checker.getTypeFromTypeNode(TypeFactory.keyword("any"))) - : signature.getReturnType(), - explore: { - ...props.explore, - top: false, - output: true, - }, - intersected: false, - }), - }), - ); - } - } - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_intersection.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_intersection.ts deleted file mode 100644 index ae747076e3a..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_intersection.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { IMetadataTypeTag } from "@typia/interface"; - -import { MetadataCollection } from "../../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { MetadataFactory } from "../../MetadataFactory"; -import { MetadataTypeTagFactory } from "../../MetadataTypeTagFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; -import { iterate_metadata } from "./iterate_metadata"; - -export const iterate_metadata_intersection = ( - props: IMetadataIteratorProps, -): boolean => { - if (props.intersected === true) return false; - else if (props.type.isIntersection() === false) return false; - - // CONSTRUCT FAKE METADATA LIST - const commit: MetadataCollection = props.components.clone(); - props.components["options"] = undefined; - - const fakeErrors: MetadataFactory.IError[] = []; - const children: MetadataSchema[] = props.type.types.map((t) => - explore_metadata({ - ...props, - options: { - ...props.options, - absorb: true, - functional: false, - }, - components: props.components, - errors: fakeErrors, - type: t, - explore: { - ...props.explore, - aliased: false, - }, - intersected: false, - }), - ); - - // ERROR OR ANY TYPE CASE - const escape = (out: boolean) => { - Object.assign(props.components, commit); - return out; - }; - if (fakeErrors.length) { - props.errors.push(...fakeErrors); - return escape(true); - } else if (children.length === 0) return escape(false); - else if (children.some((m) => m.any === true || m.size() === 0)) - return escape(false); - - // PREPARE MEATDATAS AND TAGS - const indexes: number[] = []; - const metadatas: MetadataSchema[] = []; - const tagObjects: MetadataObjectType[] = []; - children.forEach((child, i) => { - if ( - child.size() === 1 && - child.objects.length === 1 && - MetadataTypeTagFactory.is(child.objects[0]!.type) - ) - tagObjects.push(child.objects[0]!.type); - else { - indexes.push(i); - metadatas.push(child); - } - }); - - const nonsensible = () => { - props.errors.push({ - name: children.map((c) => c.getName()).join(" & "), - explore: { ...props.explore }, - messages: ["nonsensible intersection"], - }); - return escape(true); - }; - - // NO METADATA CASE - if (metadatas.length === 0) - if (tagObjects.length !== 0) { - props.errors.push({ - name: children.map((c) => c.getName()).join(" & "), - explore: { ...props.explore }, - messages: ["type tag cannot be standalone"], - }); - return escape(true); - } else return escape(false); - // ONLY OBJECTS CASE - else if ( - metadatas.every((m) => m.objects.length === 1) && - tagObjects.length === 0 - ) - return escape(false); - else if (metadatas.length !== 1) { - const indexes: number[] = metadatas - .map((m, i) => - m.size() === 1 && - m.objects.length === 1 && - (m.objects[0]!.type.properties.length === 0 || - m.objects[0]!.type.properties.every((p) => p.value.optional === true)) - ? i - : null, - ) - .filter((i) => i !== null); - if (indexes.length && metadatas.length !== indexes.length) - for (const i of indexes.reverse()) metadatas.splice(i, 1); - else return nonsensible(); - } else if (metadatas.some((m) => m.size() !== 1)) return nonsensible(); - - const candidates: Map = new Map(); - const assigners: Array<(tags: IMetadataTypeTag[]) => void> = []; - for (const meta of metadatas) { - for (const a of meta.atomics) { - candidates.set(a.type, a.type); - assigners.push((tags) => - props.metadata.atomics - .find((atom) => atom.type === a.type) - ?.tags.push(tags), - ); - } - for (const c of meta.constants) - for (const v of c.values) { - candidates.set(c.type, c.type); - assigners.push((tags) => - props.metadata.constants - .find((constant) => constant.type === c.type) - ?.values.find((value) => value === v) - ?.tags?.push(tags), - ); - } - for (const t of meta.templates) { - candidates.set("string", "string"); - assigners.push((tags) => - props.metadata.templates - .find((tpl) => tpl.getBaseName() === t.getBaseName()) - ?.tags.push(tags), - ); - } - if (meta.objects.length) { - candidates.set("object", "object"); - assigners.push((tags) => props.metadata.objects.at(-1)?.tags.push(tags)); - } - if (meta.arrays.length) { - candidates.set("array", "array"); - assigners.push((tags) => props.metadata.arrays.at(-1)?.tags.push(tags)); - } - if (meta.tuples.length) candidates.set("invalid", "tuple"); - for (const n of meta.natives) { - candidates.set(`native::${n.name}`, "object"); - assigners.push((tags) => - props.metadata.natives - .find((native) => native.name === n.name) - ?.tags.push(tags), - ); - } - for (const s of meta.sets) { - candidates.set(`set::${s.value.getName()}`, "object"); - assigners.push((tags) => - props.metadata.sets - .find((set) => set.value.getName() === s.value.getName()) - ?.tags.push(tags), - ); - } - for (const e of meta.maps) { - candidates.set(`map::${e.key.getName()}::${e.value.getName()}`, "object"); - assigners.push((tags) => - props.metadata.maps - .find( - (map) => - map.key.getName() === e.key.getName() && - map.value.getName() === e.value.getName(), - ) - ?.tags.push(tags), - ); - } - } - if ( - candidates.size !== 1 || - candidates.has("nonsensible") - // || - // (candidates.size !== 1 && - // Array.from(candidates.keys()).some((v) => v !== "object")) - ) - return nonsensible(); - - const tags: IMetadataTypeTag[] = MetadataTypeTagFactory.analyze({ - errors: props.errors, - type: candidates.values().next().value as "string", - objects: tagObjects, - explore: props.explore, - }); - Object.assign(props.components, commit); - iterate_metadata({ - ...props, - type: props.type.types[indexes[0]!]!, - options: { - ...props.options, - functional: false, - }, - explore: { - ...props.explore, - aliased: false, - escaped: false, - }, - intersected: true, - }); - if (tags.length) assigners.forEach((fn) => fn(tags)); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_map.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_map.ts deleted file mode 100644 index 04c0b2fe820..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_map.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataMap } from "../../../schemas/metadata/MetadataMap"; -import { TypeFactory } from "../../TypeFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; - -export const iterate_metadata_map = ( - props: IMetadataIteratorProps, -): boolean => { - const type: ts.Type = props.checker.getApparentType(props.type); - - const name: string = TypeFactory.getFullName({ - checker: props.checker, - type, - symbol: type.getSymbol(), - aliasTypeArguments: false, - }); - const generic: readonly ts.Type[] | undefined = - props.checker.getTypeArguments(type as ts.TypeReference); - if (name.substring(0, 4) !== "Map<" || generic?.length !== 2) return false; - - const key: ts.Type = generic[0]!; - const value: ts.Type = generic[1]!; - - ArrayUtil.set( - props.metadata.maps, - MetadataMap.create({ - key: explore_metadata({ - ...props, - type: key, - explore: { - ...props.explore, - escaped: false, - aliased: false, - }, - intersected: false, - }), - value: explore_metadata({ - ...props, - type: value, - explore: { - ...props.explore, - escaped: false, - aliased: false, - }, - intersected: false, - }), - tags: [], - }), - (elem) => `Map<${elem.key.getName()}, ${elem.value.getName()}>`, - ); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_native.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_native.ts deleted file mode 100644 index 340789c9878..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_native.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataNative } from "../../../schemas/metadata/MetadataNative"; -import { TypeFactory } from "../../TypeFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; - -export const iterate_metadata_native = ( - props: IMetadataIteratorProps, -): boolean => { - const name: string = getNativeName( - TypeFactory.getFullName({ - checker: props.checker, - type: props.type, - symbol: props.type.getSymbol(), - }), - ); - const simple: IClassInfo | undefined = SIMPLES.get(name); - if ( - simple !== undefined && - validate({ - checker: props.checker, - type: props.type, - info: simple, - }) - ) { - ArrayUtil.take( - props.metadata.natives, - (native) => native.name === name, - () => - MetadataNative.create({ - name, - tags: [], - }), - ); - return true; - } - - for (const generic of GENERICS) - if ( - name.substring(0, generic.name.length) === generic.name && - validate({ - checker: props.checker, - type: props.type, - info: generic, - }) - ) { - ArrayUtil.take( - props.metadata.natives, - (native) => native.name === name, - () => - MetadataNative.create({ - name: generic.name ?? name, - tags: [], - }), - ); - return true; - } - return false; -}; - -const validate = (props: { - checker: ts.TypeChecker; - type: ts.Type; - info: IClassInfo; -}) => - (props.info.methods ?? []).every((method) => { - const returnType = TypeFactory.getReturnTypeOfClassMethod({ - checker: props.checker, - class: props.type, - function: method.name, - }); - return ( - returnType !== null && - props.checker.typeToString(returnType).split("<")?.[0] === method.return - ); - }) && - (props.info.properties ?? []).every((property) => { - const prop = props.checker.getPropertyOfType(props.type, property.name); - const propType = prop?.valueDeclaration - ? props.checker.getTypeAtLocation(prop?.valueDeclaration) - : undefined; - return ( - propType !== undefined && - props.checker.typeToString(propType).split("<")?.[0] === property.type - ); - }); - -const getBinaryProps = (className: string): IClassInfo => ({ - name: className, - methods: [ - ...["indexOf", "lastIndexOf"].map((name) => ({ - name, - return: "number", - })), - ...["some", "every"].map((name) => ({ - name, - return: "boolean", - })), - ...["join", "toLocaleString"].map((name) => ({ - name, - return: "string", - })), - ...["reverse", "slice", "subarray"].map((name) => ({ - name, - return: className, - })), - ], - properties: ["BYTES_PER_ELEMENT", "length", "byteLength", "byteOffset"].map( - (name) => ({ - name, - type: "number", - }), - ), -}); - -const getNativeName = (name: string): string => { - name = name.split("<")?.[0] ?? ""; - if (name.startsWith('"') && name.slice(0).includes('".')) - name = name.slice(name.indexOf('".', 1) + 2); - return name; -}; - -const SIMPLES: Map = new Map([ - [ - "Date", - { - methods: ["getTime", "getFullYear", "getMonth", "getMinutes"].map( - (name) => ({ - name, - return: "number", - }), - ), - }, - ], - [ - "Boolean", - { - methods: [ - { - name: "valueOf", - return: "boolean", - }, - ], - }, - ], - [ - "Number", - { - methods: [ - ...["toFixed", "toExponential", "toPrecision"].map((name) => ({ - name, - return: "string", - })), - { name: "valueOf", return: "number" }, - ], - }, - ], - [ - "String", - { - methods: [ - "charAt", - "concat", - "valueOf", - "trim", - "replace", - "substring", - ].map((name) => ({ name, return: "string" })), - }, - ], - ...[ - "Uint8Array", - "Uint8ClampedArray", - "Uint16Array", - "Uint32Array", - "BigUint64Array", - "Int8Array", - "Int16Array", - "Int32Array", - "BigInt64Array", - "Float32Array", - "Float64Array", - ].map((name) => [name, getBinaryProps(name)] as const), - ...["ArrayBuffer", "SharedArrayBuffer"].map((className) => { - const info: IClassInfo = { - methods: [{ name: "slice", return: className }], - properties: [{ name: "byteLength", type: "number" }], - }; - return [className, info] as const; - }), - ...["Blob", "File"].map( - (className) => - [ - className, - { - methods: [ - { name: "arrayBuffer", return: "Promise" }, - { name: "slice", return: "Blob" }, - { name: "text", return: "Promise" }, - ], - properties: [ - { name: "size", type: "number" }, - { name: "type", type: "string" }, - ], - }, - ] satisfies [string, IClassInfo], - ), - [ - "DataView", - { - methods: [ - "getFloat32", - "getFloat64", - "getInt8", - "getInt16", - "getInt32", - "getUint8", - "getUint16", - "getUint32", - ].map((name) => ({ - name, - return: "number", - })), - }, - ], - [ - "RegExp", - { - methods: [ - { - name: "test", - return: "boolean", - }, - ], - }, - ], -]); -const GENERICS: Array = [ - "WeakMap", - "WeakSet", -].map((name) => ({ - name, - methods: ["has", "delete"].map((name) => ({ - name, - return: "boolean", - })), -})); - -interface IClassInfo { - name?: string; - methods?: IMethod[]; - properties?: IProperty[]; -} -interface IProperty { - name: string; - type: string; -} -interface IMethod { - name: string; - return: string; -} diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_object.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_object.ts deleted file mode 100644 index 394626fceaf..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_object.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataObject } from "../../../schemas/metadata/MetadataObject"; -import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { emplace_metadata_object } from "./emplace_metadata_object"; - -export const iterate_metadata_object = ( - props: IMetadataIteratorProps, - ensure: boolean = false, -): boolean => { - if (ensure === false) { - const filter = (flag: ts.TypeFlags) => (props.type.getFlags() & flag) !== 0; - if ( - !filter(ts.TypeFlags.Object) && - !props.type.isIntersection() && - (props.type as any).intrinsicName !== "object" - ) - return false; - } - - const obj: MetadataObjectType = emplace_metadata_object(props); - ArrayUtil.add( - props.metadata.objects, - MetadataObject.create({ - type: obj, - tags: [], - }), - (elem) => elem.type.name === obj.name, - ); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_set.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_set.ts deleted file mode 100644 index 5f3a10b5268..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_set.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { MetadataSet } from "../../../schemas/metadata/MetadataSet"; -import { TypeFactory } from "../../TypeFactory"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { explore_metadata } from "./explore_metadata"; - -export const iterate_metadata_set = ( - props: IMetadataIteratorProps, -): boolean => { - const type: ts.Type = props.checker.getApparentType(props.type); - - const name = TypeFactory.getFullName({ - checker: props.checker, - type: type, - symbol: type.getSymbol(), - aliasTypeArguments: false, - }); - const generic = props.checker.getTypeArguments(type as ts.TypeReference); - if (name.substring(0, 4) !== "Set<" || generic?.length !== 1) return false; - - const key: ts.Type = generic[0]!; - const value: MetadataSchema = explore_metadata({ - ...props, - type: key, - explore: { - ...props.explore, - escaped: false, - aliased: false, - }, - intersected: false, - }); - ArrayUtil.take( - props.metadata.sets, - (elem) => elem.value.getName() === value.getName(), - () => - MetadataSet.create({ - value: explore_metadata({ - ...props, - type: key, - explore: { - ...props.explore, - escaped: false, - aliased: false, - }, - intersected: false, - }), - tags: [], - }), - ); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_sort.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_sort.ts deleted file mode 100644 index ca70052f327..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_sort.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { MetadataCollection } from "../../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; - -export const iterate_metadata_sort = (props: { - collection: MetadataCollection; - metadata: MetadataSchema; -}) => { - const visited: Set = new Set(); - const next = (metadata: MetadataSchema) => - iterate({ - visited, - collection: props.collection, - metadata, - }); - for (const array of props.collection.arrays()) next(array.value); - for (const tuple of props.collection.tuples()) - for (const element of tuple.elements) next(element); - for (const object of props.collection.objects()) - for (const property of object.properties) next(property.value); - next(props.metadata); -}; - -const iterate = (props: { - visited: Set; - collection: MetadataCollection; - metadata: MetadataSchema; -}) => { - if (props.visited.has(props.metadata)) return; - props.visited.add(props.metadata); - - const next = (metadata: MetadataSchema) => - iterate({ - ...props, - metadata, - }); - - // ITERATE CHILDREN - for (const map of props.metadata.maps) next(map.value); - for (const set of props.metadata.sets) next(set.value); - if (props.metadata.escaped !== null) next(props.metadata.escaped.returns); - if (props.metadata.rest !== null) next(props.metadata.rest); - - // SORT OBJECTS - if (props.metadata.objects.length > 1) { - props.metadata.objects.sort((x, y) => - MetadataObjectType.covers(x.type, y.type) - ? -1 - : MetadataObjectType.covers(y.type, x.type) - ? 1 - : 0, - ); - props.metadata.union_index = props.collection.getUnionIndex(props.metadata); - } - - // SORT ARRAYS AND TUPLES - if (props.metadata.arrays.length > 1) - props.metadata.arrays.sort((x, y) => - MetadataSchema.covers(x.type.value, y.type.value) - ? -1 - : MetadataSchema.covers(y.type.value, x.type.value) - ? 1 - : 0, - ); - if (props.metadata.tuples.length > 1) - props.metadata.tuples.sort((x, y) => { - const xt = MetadataSchema.initialize(); - const yt = MetadataSchema.initialize(); - - xt.tuples.push(x); - yt.tuples.push(y); - - return MetadataSchema.covers(xt, yt) - ? -1 - : MetadataSchema.covers(yt, xt) - ? 1 - : 0; - }); - - // SORT CONSTANT VALUES - for (const constant of props.metadata.constants) - if (constant.type === "string") constant.values.sort(); - else if (constant.type === "number") - constant.values.sort((a, b) => (a.value as number) - (b.value as number)); - else if (constant.type === "bigint") - constant.values.sort((a, b) => - (a.value as bigint) < (b.value as bigint) ? -1 : 1, - ); - else constant.values.sort((a, _b) => (a.value === false ? -1 : 1)); -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_template.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_template.ts deleted file mode 100644 index 6eb7b63584e..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_template.ts +++ /dev/null @@ -1,40 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../../../schemas/metadata/MetadataSchema"; -import { MetadataTemplate } from "../../../schemas/metadata/MetadataTemplate"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { MetadataHelper } from "./MetadataHelper"; -import { explore_metadata } from "./explore_metadata"; - -export const iterate_metadata_template = ( - props: IMetadataIteratorProps, -): boolean => { - const filter = (flag: ts.TypeFlags) => (props.type.getFlags() & flag) !== 0; - if (!filter(ts.TypeFlags.TemplateLiteral)) return false; - - const template: ts.TemplateLiteralType = props.type as ts.TemplateLiteralType; - const row: MetadataSchema[] = []; - - template.texts.forEach((text, i) => { - // TEXT LITERAL TYPE - if (text !== "") row.push(MetadataHelper.literal_to_metadata(text)); - - // BINDED TEMPLATE TYPE - const binded: ts.Type | undefined = template.types[i]; - if (binded) - row.push( - explore_metadata({ - ...props, - type: binded, - explore: { - ...props.explore, - escaped: false, - aliased: false, - }, - intersected: false, - }), - ); - }); - props.metadata.templates.push(MetadataTemplate.create({ row, tags: [] })); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_tuple.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_tuple.ts deleted file mode 100644 index c58a49c4263..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_tuple.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ArrayUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { MetadataTuple } from "../../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../../schemas/metadata/MetadataTupleType"; -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { emplace_metadata_tuple } from "./emplace_metadata_tuple"; - -export const iterate_metadata_tuple = ( - props: IMetadataIteratorProps, -): boolean => { - if (!props.checker.isTupleType(props.type)) return false; - - const tupleType: MetadataTupleType = emplace_metadata_tuple(props); - ArrayUtil.add( - props.metadata.tuples, - MetadataTuple.create({ - type: tupleType, - tags: [], - }), - (elem) => elem.type.name === tupleType.name, - ); - return true; -}; diff --git a/packages/core/src/factories/internal/metadata/iterate_metadata_union.ts b/packages/core/src/factories/internal/metadata/iterate_metadata_union.ts deleted file mode 100644 index 7944e6634f2..00000000000 --- a/packages/core/src/factories/internal/metadata/iterate_metadata_union.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IMetadataIteratorProps } from "./IMetadataIteratorProps"; -import { iterate_metadata } from "./iterate_metadata"; - -export const iterate_metadata_union = ( - props: IMetadataIteratorProps, -): boolean => { - if (!props.type.isUnion()) return false; - props.type.types.forEach((t) => - iterate_metadata({ - ...props, - type: t, - explore: { - ...props.explore, - aliased: false, - }, - }), - ); - return true; -}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts deleted file mode 100644 index d50da898fd7..00000000000 --- a/packages/core/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./context/index"; -export * from "./factories/index"; -export * from "./programmers/index"; -export * from "./schemas/index"; diff --git a/packages/core/src/programmers/AssertProgrammer.ts b/packages/core/src/programmers/AssertProgrammer.ts deleted file mode 100644 index 9564fd98fb8..00000000000 --- a/packages/core/src/programmers/AssertProgrammer.ts +++ /dev/null @@ -1,466 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../context/IProgrammerProps"; -import { ITypiaContext } from "../context/ITypiaContext"; -import { IdentifierFactory } from "../factories/IdentifierFactory"; -import { StatementFactory } from "../factories/StatementFactory"; -import { TypeFactory } from "../factories/TypeFactory"; -import { IsProgrammer } from "./IsProgrammer"; -import { FunctionProgrammer } from "./helpers/FunctionProgrammer"; -import { IExpressionEntry } from "./helpers/IExpressionEntry"; -import { OptionPredicator } from "./helpers/OptionPredicator"; -import { CheckerProgrammer } from "./internal/CheckerProgrammer"; -import { FeatureProgrammer } from "./internal/FeatureProgrammer"; -import { check_object } from "./iterate/check_object"; - -/** - * Assertion code generator. - * - * Generates runtime assertion code that throws `TypeGuardError` on failure. - * Used by `typia.assert()` and `typia.assertEquals()`. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace AssertProgrammer { - /** Assertion configuration. */ - export interface IConfig { - /** Check for superfluous properties. */ - equals: boolean; - - /** Use type guard return type. */ - guard: boolean; - } - - /** Properties for assertion code generation. */ - export interface IProps extends IProgrammerProps { - config: IConfig; - } - - export const decompose = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - config: { - equals: props.config.equals, - }, - }); - const composed: FeatureProgrammer.IComposed = CheckerProgrammer.compose({ - ...props, - config: { - prefix: "_a", - path: true, - trace: true, - numeric: OptionPredicator.numeric(props.context.options), - equals: props.config.equals, - atomist: (next) => - [ - ...(next.entry.expression ? [next.entry.expression] : []), - ...(next.entry.conditions.length === 0 - ? [] - : next.entry.conditions.length === 1 - ? next.entry.conditions[0]!.map((cond) => - ts.factory.createLogicalOr( - cond.expression, - create_guard_call({ - context: props.context, - functor: props.functor, - exceptionable: - next.explore.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier( - next.explore.postfix - ? `_path + ${next.explore.postfix}` - : "_path", - ), - expected: cond.expected, - input: next.input, - }), - ), - ) - : [ - ts.factory.createLogicalOr( - next.entry.conditions - .map((set) => - set - .map((s) => s.expression) - .reduce((a, b) => - ts.factory.createLogicalAnd(a, b), - ), - ) - .reduce((a, b) => ts.factory.createLogicalOr(a, b)), - create_guard_call({ - context: props.context, - functor: props.functor, - exceptionable: - next.explore.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier( - next.explore.postfix - ? `_path + ${next.explore.postfix}` - : "_path", - ), - expected: next.entry.expected, - input: next.input, - }), - ), - ]), - ].reduce((x, y) => ts.factory.createLogicalAnd(x, y)), - combiner: combiner(props), - joiner: joiner(props), - success: ts.factory.createTrue(), - }, - }); - const arrow: ts.ArrowFunction = ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - props.config.guard - ? ts.factory.createTypePredicateNode( - ts.factory.createToken(ts.SyntaxKind.AssertsKeyword), - ts.factory.createIdentifier("input"), - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ) - : ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - undefined, - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("_errorFactory"), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createIdentifier("errorFactory"), - ), - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - undefined, - undefined, - composed.body, - ), - undefined, - [ - ts.factory.createIdentifier("input"), - ts.factory.createStringLiteral("$input"), - ts.factory.createTrue(), - ], - ), - ), - ], - true, - ), - undefined, - ), - ...(props.config.guard === false - ? [ - ts.factory.createReturnStatement( - ts.factory.createIdentifier(`input`), - ), - ] - : []), - ], - true, - ), - ); - return { - functions: { - ...is.functions, - ...composed.functions, - }, - statements: [ - ...is.statements, - ...composed.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.mut({ - name: "_errorFactory", - }), - ], - arrow, - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const combiner = - (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - }): CheckerProgrammer.IConfig.Combiner => - (next) => { - if (next.explore.tracable === false) - return IsProgrammer.configure({ - options: { - object: (v) => - assert_object({ - config: props.config, - context: props.context, - functor: props.functor, - entries: v.entries, - input: v.input, - }), - numeric: true, - }, - context: props.context, - functor: props.functor, - }).combiner(next); - - const path: string = next.explore.postfix - ? `_path + ${next.explore.postfix}` - : "_path"; - return next.logic === "and" - ? next.binaries - .map((binary) => - binary.combined - ? binary.expression - : ts.factory.createLogicalOr( - binary.expression, - create_guard_call({ - context: props.context, - functor: props.functor, - exceptionable: - next.explore.source === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier(path), - expected: next.expected, - input: next.input, - }), - ), - ) - .reduce(ts.factory.createLogicalAnd) - : ts.factory.createLogicalOr( - next.binaries - .map((binary) => binary.expression) - .reduce(ts.factory.createLogicalOr), - create_guard_call({ - context: props.context, - functor: props.functor, - exceptionable: - next.explore.source === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier(path), - expected: next.expected, - input: next.input, - }), - ); - }; - - const assert_object = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - entries: IExpressionEntry[]; - input: ts.Expression; - }) => - check_object({ - config: { - equals: props.config.equals, - assert: true, - undefined: true, - reduce: ts.factory.createLogicalAnd, - positive: ts.factory.createTrue(), - superfluous: (input) => - create_guard_call({ - context: props.context, - functor: props.functor, - path: ts.factory.createAdd( - ts.factory.createIdentifier("_path"), - ts.factory.createCallExpression( - props.context.importer.internal("accessExpressionAsString"), - undefined, - [ts.factory.createIdentifier("key")], - ), - ), - expected: "undefined", - input, - }), - halt: (expr) => - ts.factory.createLogicalOr( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createIdentifier("_exceptionable"), - ), - expr, - ), - }, - context: props.context, - entries: props.entries, - input: props.input, - }); - - const joiner = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - }): CheckerProgrammer.IConfig.IJoiner => ({ - object: (next) => - assert_object({ - config: props.config, - context: props.context, - functor: props.functor, - entries: next.entries, - input: next.input, - }), - array: (props) => - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "every"), - undefined, - [props.arrow], - ), - failure: (next) => - create_guard_call({ - context: props.context, - functor: props.functor, - exceptionable: - next.explore?.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier( - next.explore?.postfix ? `_path + ${next.explore.postfix}` : "_path", - ), - expected: next.expected, - input: next.input, - }), - full: props.config.equals - ? undefined - : (next) => - ts.factory.createLogicalOr( - next.condition, - create_guard_call({ - context: props.context, - functor: props.functor, - exceptionable: - next.explore.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier("_path"), - expected: next.expected, - input: next.input, - }), - ), - }); - - const create_guard_call = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - expected: string; - input: ts.Expression; - path: ts.Expression; - exceptionable?: ts.Expression; - }): ts.Expression => - ts.factory.createCallExpression( - props.context.importer.internal("assertGuard"), - undefined, - [ - props.exceptionable ?? ts.factory.createIdentifier("_exceptionable"), - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "method", - ts.factory.createStringLiteral(props.functor.method), - ), - ts.factory.createPropertyAssignment("path", props.path), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.input), - ], - true, - ), - ts.factory.createIdentifier("_errorFactory"), - ], - ); - - export namespace Guardian { - export const identifier = () => ts.factory.createIdentifier("errorFactory"); - export const parameter = (props: { - context: ITypiaContext; - init: ts.Expression | undefined; - }) => - IdentifierFactory.parameter( - "errorFactory", - type(props.context), - props.init ?? ts.factory.createToken(ts.SyntaxKind.QuestionToken), - ); - export const type = (context: ITypiaContext) => - ts.factory.createFunctionTypeNode( - undefined, - [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier("p"), - undefined, - context.importer.type({ - file: "typia", - name: "TypeGuardError.IProps", - }), - undefined, - ), - ], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier("Error"), - undefined, - ), - ); - } -} diff --git a/packages/core/src/programmers/ImportProgrammer.ts b/packages/core/src/programmers/ImportProgrammer.ts deleted file mode 100644 index d67edea1552..00000000000 --- a/packages/core/src/programmers/ImportProgrammer.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { MapUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -/** - * Import statement manager for code generation. - * - * Collects and deduplicates import declarations needed by generated code. - * Tracks default imports ({@link default}), named imports ({@link instance}), and - * namespace imports ({@link namespace}). Call {@link toStatements} to emit the - * final import declaration AST nodes. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export class ImportProgrammer { - private readonly assets_: Map = new Map(); - private readonly options_: Readonly; - - public constructor(options?: Partial) { - this.options_ = { - internalPrefix: options?.internalPrefix ?? "", - runtime: options?.runtime, - }; - } - - /* ----------------------------------------------------------- - ENROLLMENTS - ----------------------------------------------------------- */ - public default(props: ImportProgrammer.IDefault): ts.Identifier { - const asset: IAsset = this.take(props.file); - asset.default ??= props; - asset.default.type ||= props.type; - return ts.factory.createIdentifier(asset.default.name); - } - - public instance(props: ImportProgrammer.IInstance): ts.Identifier { - const alias: string = props.alias ?? props.name; - const asset: IAsset = this.take(props.file); - MapUtil.take(asset.instances, alias, () => props); - return ts.factory.createIdentifier(alias); - } - - public namespace(props: ImportProgrammer.INamespace): ts.Identifier { - const asset: IAsset = this.take(props.file); - asset.namespace ??= props; - return ts.factory.createIdentifier(asset.namespace.name); - } - - public type(props: { - file: string; - name: string | ts.EntityName; - arguments?: ts.TypeNode[]; - }): ts.ImportTypeNode { - return ts.factory.createImportTypeNode( - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral(props.file), - ), - undefined, - typeof props.name === "string" - ? ts.factory.createIdentifier(props.name) - : props.name, - props.arguments, - ); - } - - /** @internal */ - public internal(name: string): ts.PropertyAccessExpression { - if (name.startsWith("_") === false) name = `_${name}`; - return ts.factory.createPropertyAccessExpression( - this.namespace({ - file: `typia/lib/internal/${name}`, - name: this.alias(name), - }), - name, - ); - } - - /** @internal */ - public getInternalText(name: string): string { - if (name.startsWith("_") === false) name = `_${name}`; - const asset: IAsset | undefined = this.take(`typia/lib/internal/${name}`); - if (!asset?.namespace) throw new Error(`Internal asset not found: ${name}`); - return `${asset.namespace.name}.${name}`; - } - - /** @internal */ - private take(file: string): IAsset { - return MapUtil.take(this.assets_, file, () => ({ - file, - default: null, - namespace: null, - instances: new Map(), - })); - } - - private alias(name: string): string { - return `__${this.options_.internalPrefix}${name}`; - } - - /* ----------------------------------------------------------- - PROGRAM STATEMENTS - ----------------------------------------------------------- */ - public toStatements(): ts.ImportDeclaration[] { - const statements: ts.ImportDeclaration[] = []; - for (const asset of this.assets_.values()) { - if (asset.namespace !== null) - statements.push( - ts.factory.createImportDeclaration( - undefined, - ts.factory.createImportClause( - false, - undefined, - ts.factory.createNamespaceImport( - ts.factory.createIdentifier(asset.namespace.name), - ), - ), - ts.factory.createStringLiteral(asset.file), - ), - ); - if (asset.default !== null) - statements.push( - ts.factory.createImportDeclaration( - undefined, - ts.factory.createImportClause( - asset.default.type, - ts.factory.createIdentifier(asset.default.name), - undefined, - ), - ts.factory.createStringLiteral(asset.file), - ), - ); - if (asset.instances.size > 0) - statements.push( - ts.factory.createImportDeclaration( - undefined, - ts.factory.createImportClause( - false, - undefined, - asset.instances.size > 0 - ? ts.factory.createNamedImports( - [...asset.instances.values()].map((ins) => - ts.factory.createImportSpecifier( - false, - ins.alias || ins.alias === ins.name - ? ts.factory.createIdentifier(ins.name) - : undefined, - ts.factory.createIdentifier(ins.alias ?? ins.name), - ), - ), - ) - : undefined, - ), - ts.factory.createStringLiteral(asset.file), - undefined, - ), - ); - } - return statements; - } -} - -export namespace ImportProgrammer { - export interface IOptions { - internalPrefix: string; - runtime?: "ts" | "js"; - } - - export interface IDefault { - file: string; - name: string; - type: boolean; - } - export interface IInstance { - file: string; - name: string; - alias: string | null; - } - export interface INamespace { - file: string; - name: string; - } -} - -interface IAsset { - file: string; - default: ImportProgrammer.IDefault | null; - namespace: ImportProgrammer.INamespace | null; - instances: Map; -} diff --git a/packages/core/src/programmers/IsProgrammer.ts b/packages/core/src/programmers/IsProgrammer.ts deleted file mode 100644 index 82980e84792..00000000000 --- a/packages/core/src/programmers/IsProgrammer.ts +++ /dev/null @@ -1,278 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../context/IProgrammerProps"; -import { ITypiaContext } from "../context/ITypiaContext"; -import { ExpressionFactory } from "../factories/ExpressionFactory"; -import { IdentifierFactory } from "../factories/IdentifierFactory"; -import { ValueFactory } from "../factories/ValueFactory"; -import { MetadataCollection } from "../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "./helpers/FunctionProgrammer"; -import { IExpressionEntry } from "./helpers/IExpressionEntry"; -import { OptionPredicator } from "./helpers/OptionPredicator"; -import { CheckerProgrammer } from "./internal/CheckerProgrammer"; -import { FeatureProgrammer } from "./internal/FeatureProgrammer"; -import { check_object } from "./iterate/check_object"; - -/** - * Type guard code generator. - * - * Generates runtime type guard code returning boolean. Used by `typia.is()` - * and `typia.equals()`. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace IsProgrammer { - export const configure = (props: { - options?: Partial; - context: ITypiaContext; - functor: FunctionProgrammer; - }): CheckerProgrammer.IConfig => ({ - prefix: "_i", - equals: !!props.options?.object, - trace: false, - path: false, - numeric: OptionPredicator.numeric({ - numeric: props.options?.numeric, - }), - atomist: ({ entry }) => - [ - ...(entry.expression ? [entry.expression] : []), - ...(entry.conditions.length === 0 - ? [] - : [ - entry.conditions - .map((set) => - set - .map((s) => s.expression) - .reduce((a, b) => ts.factory.createLogicalAnd(a, b)), - ) - .reduce((a, b) => ts.factory.createLogicalOr(a, b)), - ]), - ].reduce((x, y) => ts.factory.createLogicalAnd(x, y)), - combiner: (next) => { - const initial: ts.TrueLiteral | ts.FalseLiteral = - next.logic === "and" - ? ts.factory.createTrue() - : ts.factory.createFalse(); - const binder = - next.logic === "and" - ? ts.factory.createLogicalAnd - : ts.factory.createLogicalOr; - return next.binaries.length - ? next.binaries.map((binary) => binary.expression).reduce(binder) - : initial; - }, - joiner: { - object: props.options?.object - ? (v) => props.options!.object!(v) - : (v) => - check_object({ - config: { - equals: !!props.options?.object, - undefined: OptionPredicator.undefined({ - undefined: props.options?.undefined, - }), - assert: true, - reduce: ts.factory.createLogicalAnd, - positive: ts.factory.createTrue(), - superfluous: () => ts.factory.createFalse(), - }, - context: props.context, - entries: v.entries, - input: v.input, - }), - array: (props) => - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "every"), - undefined, - [props.arrow], - ), - failure: () => ts.factory.createFalse(), - }, - success: ts.factory.createTrue(), - }); - - export namespace CONFIG { - export interface IOptions { - numeric: boolean; - undefined: boolean; - object: (props: { - input: ts.Expression; - entries: IExpressionEntry[]; - }) => ts.Expression; - } - } - - /* ----------------------------------------------------------- - WRITERS - ----------------------------------------------------------- */ - export interface IConfig { - equals: boolean; - } - export interface IProps extends IProgrammerProps { - config: IConfig; - } - - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - config: IConfig; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - // CONFIGURATION - const config: CheckerProgrammer.IConfig = { - ...configure({ - options: { - object: (v) => - check_object({ - config: { - equals: props.config.equals, - undefined: OptionPredicator.undefined(props.context.options), - assert: true, - reduce: ts.factory.createLogicalAnd, - positive: ts.factory.createTrue(), - superfluous: () => ts.factory.createFalse(), - }, - context: props.context, - entries: v.entries, - input: v.input, - }), - numeric: OptionPredicator.numeric(props.context.options), - }, - context: props.context, - functor: props.functor, - }), - trace: props.config.equals, - }; - - // COMPOSITION - const composed: FeatureProgrammer.IComposed = CheckerProgrammer.compose({ - ...props, - config, - }); - return { - functions: composed.functions, - statements: composed.statements, - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - composed.response, - undefined, - composed.body, - ), - }; - }; - - export const write = (props: IProps) => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - config: props.config, - context: props.context, - functor, - type: props.type, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const write_function_statements = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - collection: MetadataCollection; - }) => { - const config: CheckerProgrammer.IConfig = configure(props); - const next = { - ...props, - config, - }; - const objects: ts.VariableStatement[] = - CheckerProgrammer.write_object_functions(next); - const unions: ts.VariableStatement[] = - CheckerProgrammer.write_union_functions(next); - const arrays: ts.VariableStatement[] = - CheckerProgrammer.write_array_functions(next); - const tuples: ts.VariableStatement[] = - CheckerProgrammer.write_tuple_functions(next); - - return [ - ...objects.filter((_, i) => - props.functor.hasLocal(`${config.prefix}o${i}`), - ), - ...unions.filter((_, i) => - props.functor.hasLocal(`${config.prefix}u${i}`), - ), - ...arrays.filter((_, i) => - props.functor.hasLocal(`${config.prefix}a${i}`), - ), - ...tuples.filter((_, i) => - props.functor.hasLocal(`${config.prefix}t${i}`), - ), - ]; - }; - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - export const decode = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - metadata: MetadataSchema; - input: ts.Expression; - explore: CheckerProgrammer.IExplore; - }) => - CheckerProgrammer.decode({ - context: props.context, - config: configure(props), - functor: props.functor, - metadata: props.metadata, - input: props.input, - explore: props.explore, - }); - - export const decode_object = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - object: MetadataObjectType; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; - }) => - CheckerProgrammer.decode_object({ - config: configure(props), - functor: props.functor, - object: props.object, - input: props.input, - explore: props.explore, - }); - - export const decode_to_json = (props: { - input: ts.Expression; - checkNull: boolean; - }): ts.Expression => - ts.factory.createLogicalAnd( - ExpressionFactory.isObject({ - checkArray: false, - checkNull: props.checkNull, - input: props.input, - }), - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("function"), - ValueFactory.TYPEOF(IdentifierFactory.access(props.input, "toJSON")), - ), - ); - - export const decode_functional = (input: ts.Expression) => - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("function"), - ValueFactory.TYPEOF(input), - ); -} diff --git a/packages/core/src/programmers/RandomProgrammer.ts b/packages/core/src/programmers/RandomProgrammer.ts deleted file mode 100644 index a0f7052693f..00000000000 --- a/packages/core/src/programmers/RandomProgrammer.ts +++ /dev/null @@ -1,1200 +0,0 @@ -import { OpenApi } from "@typia/interface"; -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../context/ITypiaContext"; -import { TransformerError } from "../context/TransformerError"; -import { ExpressionFactory } from "../factories/ExpressionFactory"; -import { IdentifierFactory } from "../factories/IdentifierFactory"; -import { LiteralFactory } from "../factories/LiteralFactory"; -import { MetadataCommentTagFactory } from "../factories/MetadataCommentTagFactory"; -import { MetadataFactory } from "../factories/MetadataFactory"; -import { StatementFactory } from "../factories/StatementFactory"; -import { TemplateFactory } from "../factories/TemplateFactory"; -import { TypeFactory } from "../factories/TypeFactory"; -import { MetadataArray } from "../schemas/metadata/MetadataArray"; -import { MetadataArrayType } from "../schemas/metadata/MetadataArrayType"; -import { MetadataAtomic } from "../schemas/metadata/MetadataAtomic"; -import { MetadataCollection } from "../schemas/metadata/MetadataCollection"; -import { MetadataMap } from "../schemas/metadata/MetadataMap"; -import { MetadataObjectType } from "../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../schemas/metadata/MetadataSchema"; -import { MetadataSet } from "../schemas/metadata/MetadataSet"; -import { MetadataTemplate } from "../schemas/metadata/MetadataTemplate"; -import { MetadataTuple } from "../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../schemas/metadata/MetadataTupleType"; -import { FunctionProgrammer } from "./helpers/FunctionProgrammer"; -import { RandomJoiner } from "./helpers/RandomJoiner"; -import { FeatureProgrammer } from "./internal/FeatureProgrammer"; -import { json_schema_array } from "./iterate/json_schema_array"; -import { json_schema_bigint } from "./iterate/json_schema_bigint"; -import { json_schema_boolean } from "./iterate/json_schema_boolean"; -import { json_schema_number } from "./iterate/json_schema_number"; -import { json_schema_string } from "./iterate/json_schema_string"; - -/** - * Random data generator code generator. - * - * Generates code that creates random values matching type constraints. Used by - * `typia.random()`. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace RandomProgrammer { - /** Properties for random code generation. */ - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - type: ts.Type; - name: string | undefined; - init: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init: ts.Expression | undefined; - } - - export const decompose = ( - props: IDecomposeProps, - ): FeatureProgrammer.IDecomposed => { - const collection: MetadataCollection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate: ({ metadata }) => { - const output: string[] = []; - if (metadata.natives.some((native) => native.name === "WeakSet")) - output.push(`WeakSet is not supported.`); - else if (metadata.natives.some((native) => native.name === "WeakMap")) - output.push(`WeakMap is not supported.`); - return output; - }, - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - - // GENERATE FUNCTION - const functions: Record = Object.fromEntries([ - ...write_object_functions({ - context: props.context, - functor: props.functor, - collection, - }).map((v, i) => [Prefix.object(i), v]), - ...write_array_functions({ - context: props.context, - functor: props.functor, - collection, - }).map((v, i) => [Prefix.array(i), v]), - ...write_tuple_functions({ - context: props.context, - functor: props.functor, - collection, - }).map((v, i) => [Prefix.tuple(i), v]), - ]); - const arrow: ts.ArrowFunction = ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "generator", - ts.factory.createTypeReferenceNode("Partial", [ - props.context.importer.type({ - file: "typia", - name: "IRandomGenerator", - }), - ]), - props.init ?? ts.factory.createToken(ts.SyntaxKind.QuestionToken), - ), - ], - props.context.importer.type({ - file: "typia", - name: "Resolved", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("_generator"), - ts.SyntaxKind.EqualsToken, - ts.factory.createIdentifier("generator"), - ), - ), - ts.factory.createReturnStatement( - decode({ - context: props.context, - functor: props.functor, - explore: { - function: false, - recursive: false, - }, - metadata: result.data, - }), - ), - ], - true, - ), - ); - return { - functions, - statements: [ - StatementFactory.mut({ - name: "_generator", - type: ts.factory.createUnionTypeNode([ - ts.factory.createTypeReferenceNode("Partial", [ - props.context.importer.type({ - file: "typia", - name: "IRandomGenerator", - }), - ]), - ts.factory.createTypeReferenceNode("undefined"), - ]), - }), - ], - arrow, - }; - }; - - export const write = (props: IProps) => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const write_object_functions = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection.objects().map((obj, i) => - StatementFactory.constant({ - name: Prefix.object(i), - value: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "_recursive", - TypeFactory.keyword("boolean"), - ts.factory.createIdentifier(String(obj.recursive)), - ), - IdentifierFactory.parameter( - "_depth", - TypeFactory.keyword("number"), - ExpressionFactory.number(0), - ), - ], - TypeFactory.keyword("any"), - undefined, - RandomJoiner.object({ - decode: (metadata) => - decode({ - context: props.context, - functor: props.functor, - explore: { - recursive: obj.recursive, - function: true, - }, - metadata, - }), - object: obj, - }), - ), - }), - ); - - const write_array_functions = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .arrays() - .filter((a) => a.recursive) - .map((array, i) => - StatementFactory.constant({ - name: Prefix.array(i), - value: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "_schema", - TypeFactory.keyword("boolean"), - ), - IdentifierFactory.parameter( - "_recursive", - TypeFactory.keyword("boolean"), - ts.factory.createTrue(), - ), - IdentifierFactory.parameter( - "_depth", - TypeFactory.keyword("number"), - ExpressionFactory.number(0), - ), - ], - TypeFactory.keyword("any"), - undefined, - RandomJoiner.array({ - decode: (metadata) => - decode({ - context: props.context, - functor: props.functor, - explore: { - recursive: true, - function: true, - }, - metadata, - }), - recursive: true, - expression: coalesce({ - context: props.context, - method: "array", - internal: "randomArray", - }), - array, - schema: undefined, - }), - ), - }), - ); - - const write_tuple_functions = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .tuples() - .filter((a) => a.recursive) - .map((tuple, i) => - StatementFactory.constant({ - name: Prefix.tuple(i), - value: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "_recursive", - TypeFactory.keyword("boolean"), - ts.factory.createTrue(), - ), - IdentifierFactory.parameter( - "_depth", - TypeFactory.keyword("number"), - ExpressionFactory.number(0), - ), - ], - TypeFactory.keyword("any"), - undefined, - RandomJoiner.tuple({ - decode: (metadata) => - decode({ - context: props.context, - functor: props.functor, - explore: { - function: true, - recursive: true, - }, - metadata, - }), - elements: tuple.elements, - }), - ), - }), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decode = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - metadata: MetadataSchema; - }): ts.Expression => { - const expressions: ts.Expression[] = []; - if (props.metadata.any === true) - expressions.push(ts.factory.createStringLiteral("any type used...")); - - // NULL COALESCING - if ( - props.metadata.isRequired() === false || - props.metadata.functions.length !== 0 - ) - expressions.push(ts.factory.createIdentifier("undefined")); - if (props.metadata.nullable === true) - expressions.push(ts.factory.createNull()); - - // CONSTANT TYPES - for (const constant of props.metadata.constants) - for (const { value } of constant.values) - expressions.push( - constant.type === "boolean" - ? value === true - ? ts.factory.createTrue() - : ts.factory.createFalse() - : constant.type === "bigint" - ? ExpressionFactory.bigint(value as bigint) - : constant.type === "number" - ? ExpressionFactory.number(value as number) - : ts.factory.createStringLiteral(value as string), - ); - - // ATOMIC VARIABLES - for (const template of props.metadata.templates) - expressions.push( - decode_template({ - ...props, - template, - }), - ); - for (const atomic of props.metadata.atomics) - expressions.push( - ...decode_atomic({ - context: props.context, - atomic, - }), - ); - - // INSTANCE TYPES - if (props.metadata.escaped) - expressions.push( - decode({ - ...props, - metadata: props.metadata.escaped.returns, - }), - ); - for (const array of props.metadata.arrays) - expressions.push( - ...decode_array({ - ...props, - array, - }), - ); - for (const tuple of props.metadata.tuples) - expressions.push( - decode_tuple({ - ...props, - tuple, - }), - ); - for (const object of props.metadata.objects) - expressions.push( - decode_object({ - ...props, - object: object.type, - }), - ); - for (const native of props.metadata.natives) - expressions.push( - decode_native({ - context: props.context, - functor: props.functor, - explore: props.explore, - name: native.name, - }), - ); - for (const set of props.metadata.sets) - expressions.push( - decode_set({ - ...props, - set, - }), - ); - for (const entry of props.metadata.maps) - expressions.push( - decode_map({ - ...props, - map: entry, - }), - ); - - // PICK UP A TYPE - if (expressions.length === 1) return expressions[0]!; - return ts.factory.createCallExpression( - ts.factory.createCallExpression( - props.context.importer.internal("randomPick"), - undefined, - [ - ts.factory.createArrayLiteralExpression( - expressions.map((expr) => - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - expr, - ), - ), - true, - ), - ], - ), - undefined, - undefined, - ); - }; - - const decode_atomic = (props: { - context: ITypiaContext; - atomic: MetadataAtomic; - }) => { - const schemaList: OpenApi.IJsonSchema[] = - props.atomic.type === "boolean" - ? json_schema_boolean(props.atomic) - : props.atomic.type === "string" - ? json_schema_string(props.atomic) - : props.atomic.type === "bigint" - ? json_schema_bigint(props.atomic) - : json_schema_number(props.atomic); - return schemaList.map((schema) => { - interface IComposed { - method: string; - internal: string; - arguments: ts.Expression[]; - } - const composed = ((): IComposed => { - if (props.atomic.type === "string") { - const string: OpenApi.IJsonSchema.IString = - schema as OpenApi.IJsonSchema.IString; - if (string.format !== undefined) { - const format: string = string.format!; - if (format === "date-time") - return { - method: "datetime", - internal: "randomFormatDatetime", - arguments: [], - }; - return { - method: format - .split("-") - .map((s, i) => (i === 0 ? s : NamingConvention.capitalize(s))) - .join(""), - internal: `randomFormat${format - .split("-") - .map(NamingConvention.capitalize) - .join("")}`, - arguments: [], - }; - } else if (string.pattern !== undefined) - return { - method: "pattern", - internal: "randomPattern", - arguments: [ - ts.factory.createNewExpression( - ts.factory.createIdentifier("RegExp"), - undefined, - [ - ts.factory.createStringLiteral( - (schema as OpenApi.IJsonSchema.IString).pattern!, - ), - ], - ), - ], - }; - } else if (props.atomic.type === "number") { - const number: - | OpenApi.IJsonSchema.INumber - | OpenApi.IJsonSchema.IInteger = schema as - | OpenApi.IJsonSchema.INumber - | OpenApi.IJsonSchema.IInteger; - if (number.type === "integer") - return { - method: "integer", - internal: "randomInteger", - arguments: [LiteralFactory.write(schema)], - }; - } else if (props.atomic.type === "boolean") - return { - method: props.atomic.type, - internal: "randomBoolean", - arguments: [], - }; - return { - method: props.atomic.type, - internal: `random${NamingConvention.capitalize(props.atomic.type)}`, - arguments: [LiteralFactory.write(schema)], - }; - })(); - return ts.factory.createCallExpression( - ExpressionFactory.coalesce( - ts.factory.createPropertyAccessChain( - ts.factory.createIdentifier("_generator"), - ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), - ts.factory.createIdentifier(composed.method), - ), - props.context.importer.internal(composed.internal), - ), - undefined, - composed.arguments, - ); - }); - }; - - const decode_template = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - template: MetadataTemplate; - }) => - TemplateFactory.generate( - props.template.row.map((metadata) => - decode({ - ...props, - metadata, - }), - ), - ); - - const decode_array = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - array: MetadataArray; - }): ts.Expression[] => { - const components: OpenApi.IComponents = {}; - const schemaList: OpenApi.IJsonSchema.IArray[] = json_schema_array({ - components, - array: props.array, - }) as OpenApi.IJsonSchema.IArray[]; - if (props.array.type.recursive) - return schemaList.map((schema) => - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(Prefix.array(props.array.type.index!)), - ), - undefined, - [ - ts.factory.createObjectLiteralExpression( - Object.entries(schema) - .filter(([key]) => key !== "items") - .map(([key, value]) => - ts.factory.createPropertyAssignment( - key, - LiteralFactory.write(value), - ), - ), - true, - ), - ], - ), - ); - return schemaList.map((schema) => - RandomJoiner.array({ - decode: (metadata) => - decode({ - ...props, - metadata, - }), - expression: coalesce({ - context: props.context, - method: "array", - internal: "randomArray", - }), - array: props.array.type, - recursive: props.explore.recursive, - schema, - }), - ); - }; - - const decode_tuple = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - tuple: MetadataTuple; - }): ts.Expression => - props.tuple.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(Prefix.tuple(props.tuple.type.index!)), - ), - undefined, - [ - ts.factory.createTrue(), - props.explore.recursive - ? ts.factory.createAdd( - ExpressionFactory.number(1), - ts.factory.createIdentifier("_depth"), - ) - : ExpressionFactory.number(0), - ], - ) - : RandomJoiner.tuple({ - decode: (metadata) => - decode({ - ...props, - metadata, - }), - elements: props.tuple.type.elements, - }); - - const decode_object = (props: { - functor: FunctionProgrammer; - explore: IExplore; - object: MetadataObjectType; - }) => - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(Prefix.object(props.object.index)), - ), - undefined, - props.explore.function - ? [ - props.explore.recursive - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_recursive"), - ts.factory.createConditionalExpression( - ts.factory.createIdentifier("_recursive"), - undefined, - ts.factory.createAdd( - ExpressionFactory.number(1), - ts.factory.createIdentifier("_depth"), - ), - undefined, - ts.factory.createIdentifier("_depth"), - ), - ] - : undefined, - ); - - /* ----------------------------------------------------------- - NATIVE CLASSES - ----------------------------------------------------------- */ - const decode_set = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - set: MetadataSet; - }) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Set"), - undefined, - [ - decode_array({ - ...props, - array: MetadataArray.create({ - tags: [], - type: MetadataArrayType.create({ - value: props.set.value, - recursive: false, - index: null, - nullables: [], - name: props.set.getName(), - }), - }), - })[0]!, - ], - ); - - const decode_map = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - map: MetadataMap; - }) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - undefined, - [ - decode_array({ - ...props, - array: MetadataArray.create({ - tags: [], - type: MetadataArrayType.create({ - name: props.map.getName(), - index: null, - recursive: false, - nullables: [], - value: MetadataSchema.create({ - ...MetadataSchema.initialize(), - tuples: [ - (() => { - const type = MetadataTupleType.create({ - name: `[${props.map.key.getName()}, ${props.map.value.getName()}]`, - index: null, - recursive: false, - nullables: [], - elements: [props.map.key, props.map.value], - }); - type.of_map = true; - return MetadataTuple.create({ - type, - tags: [], - }); - })(), - ], - }), - }), - }), - })[0]!, - ], - ); - - const decode_native = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - name: string; - }): ts.Expression => { - if ( - props.name === "Boolean" || - props.name === "Number" || - props.name === "BigInt" || - props.name === "String" - ) - return decode_atomic({ - context: props.context, - atomic: MetadataAtomic.create({ - type: props.name.toLowerCase() as "string", - tags: [], - }), - })[0]!; - else if (props.name === "Date") return decode_native_date(props.context); - else if ( - props.name === "Uint8Array" || - props.name === "Uint8ClampedArray" || - props.name === "Uint16Array" || - props.name === "Uint32Array" || - props.name === "BigUint64Array" || - props.name === "Int8Array" || - props.name === "Int16Array" || - props.name === "Int32Array" || - props.name === "BigInt64Array" || - props.name === "Float32Array" || - props.name === "Float64Array" - ) - return decode_native_byte_array({ - ...props, - name: props.name, - }); - else if (props.name === "ArrayBuffer" || props.name === "SharedArrayBuffer") - return decode_native_array_buffer({ - ...props, - name: props.name, - }); - else if (props.name === "DataView") return decode_native_data_view(props); - else if (props.name === "Blob") return decode_native_blob(props); - else if (props.name === "File") return decode_native_file(props); - else if (props.name === "RegExp") return decode_regexp(props.context); - else - return ts.factory.createNewExpression( - ts.factory.createIdentifier(props.name), - undefined, - [], - ); - }; - - const decode_native_date = (context: ITypiaContext) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Date"), - undefined, - [ - ts.factory.createCallExpression( - coalesce({ - context, - method: "datetime", - internal: "randomFormatDatetime", - }), - undefined, - [], - ), - ], - ); - - const decode_native_byte_array = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - name: - | "Uint8Array" - | "Uint8ClampedArray" - | "Uint16Array" - | "Uint32Array" - | "BigUint64Array" - | "Int8Array" - | "Int16Array" - | "Int32Array" - | "BigInt64Array" - | "Float32Array" - | "Float64Array"; - }): ts.Expression => { - new BigInt64Array(); - const [type, minimum, maximum]: [string, number, number] = (() => { - if (props.name === "Uint8Array" || props.name === "Uint8ClampedArray") - return ["uint32", 0, 255]; - else if (props.name === "Uint16Array") return ["uint32", 0, 65535]; - else if (props.name === "Uint32Array") return ["uint32", 0, 4294967295]; - else if (props.name === "BigUint64Array") - return ["uint64", 0, 18446744073709551615]; - else if (props.name === "Int8Array") return ["int32", -128, 127]; - else if (props.name === "Int16Array") return ["int32", -32768, 32767]; - else if (props.name === "Int32Array") - return ["int32", -2147483648, 2147483647]; - else if (props.name === "BigInt64Array") - return ["uint64", -9223372036854775808, 9223372036854775807]; - else if (props.name === "Float32Array") - return ["float", -1.175494351e38, 3.4028235e38]; - return ["double", Number.MIN_VALUE, Number.MAX_VALUE]; - })(); - const atomic: "bigint" | "number" = - props.name === "BigInt64Array" || props.name === "BigUint64Array" - ? "bigint" - : "number"; - const value: MetadataSchema = MetadataSchema.create({ - ...MetadataSchema.initialize(), - atomics: [ - MetadataAtomic.create({ - type: atomic, - tags: [ - [ - ...MetadataCommentTagFactory.get({ - kind: "type", - type: atomic, - value: type, - }), - ...MetadataCommentTagFactory.get({ - kind: "minimum", - type: "number", - value: minimum.toString(), - }), - ...MetadataCommentTagFactory.get({ - kind: "maximum", - type: "number", - value: maximum.toString(), - }), - ], - ], - }), - ], - }); - return ts.factory.createNewExpression( - ts.factory.createIdentifier(props.name), - [], - decode_array({ - context: props.context, - functor: props.functor, - explore: props.explore, - array: MetadataArray.create({ - tags: [], - type: MetadataArrayType.create({ - name: `${props.name}<${atomic}>`, - value, - recursive: false, - index: null, - nullables: [], - }), - }), - }), - ); - }; - - const decode_native_blob = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - }) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Blob"), - undefined, - [ - ts.factory.createArrayLiteralExpression( - [ - decode_native_byte_array({ - context: props.context, - functor: props.functor, - explore: props.explore, - name: "Uint8Array", - }), - ], - true, - ), - ], - ); - - const decode_native_file = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - }) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("File"), - undefined, - [ - ts.factory.createArrayLiteralExpression( - [ - decode_native_byte_array({ - context: props.context, - functor: props.functor, - explore: props.explore, - name: "Uint8Array", - }), - ], - true, - ), - ts.factory.createTemplateExpression(ts.factory.createTemplateHead(""), [ - ts.factory.createTemplateSpan( - writeRangedString({ - context: props.context, - minLength: 1, - maxLength: 8, - }), - ts.factory.createTemplateMiddle("."), - ), - ts.factory.createTemplateSpan( - writeRangedString({ - context: props.context, - minLength: 3, - maxLength: 3, - }), - ts.factory.createTemplateTail(""), - ), - ]), - ], - ); - - const decode_native_array_buffer = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - name: "ArrayBuffer" | "SharedArrayBuffer"; - }): ts.Expression => - props.name === "ArrayBuffer" - ? IdentifierFactory.access( - decode_native_byte_array({ - context: props.context, - functor: props.functor, - explore: props.explore, - name: "Uint8Array", - }), - "buffer", - ) - : ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "length", - value: decode_atomic({ - context: props.context, - atomic: MetadataAtomic.create({ - type: "number", - tags: [ - MetadataCommentTagFactory.get({ - type: "number", - kind: "type", - value: "uint32", - }), - ], - }), - })[0]!, - }), - StatementFactory.constant({ - name: "buffer", - value: ts.factory.createNewExpression( - ts.factory.createIdentifier("SharedArrayBuffer"), - [], - [ts.factory.createIdentifier("length")], - ), - }), - StatementFactory.constant({ - name: "bytes", - value: ts.factory.createNewExpression( - ts.factory.createIdentifier("Uint8Array"), - [], - [ts.factory.createIdentifier("buffer")], - ), - }), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createIdentifier("bytes"), - "set", - ), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createNewExpression( - ts.factory.createIdentifier("Array"), - undefined, - [ts.factory.createIdentifier("length")], - ), - ts.factory.createIdentifier("fill"), - ), - undefined, - [ts.factory.createNumericLiteral("0")], - ), - ts.factory.createIdentifier("map"), - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - decode_atomic({ - context: props.context, - atomic: MetadataAtomic.create({ - type: "number", - tags: [ - [ - ...MetadataCommentTagFactory.get({ - kind: "type", - type: "number", - value: "uint32", - }), - ...MetadataCommentTagFactory.get({ - kind: "minimum", - type: "number", - value: "0", - }), - ...MetadataCommentTagFactory.get({ - kind: "maximum", - type: "number", - value: "255", - }), - ], - ], - }), - })[0]!, - ), - ], - ), - ExpressionFactory.number(0), - ], - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("buffer"), - ), - ], - true, - ), - ); - - const decode_native_data_view = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - explore: IExplore; - }) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("DataView"), - [], - [ - IdentifierFactory.access( - decode_native_byte_array({ - context: props.context, - functor: props.functor, - explore: props.explore, - name: "Uint8Array", - }), - "buffer", - ), - ], - ); - - const decode_regexp = (context: ITypiaContext) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("RegExp"), - [], - [ - ts.factory.createCallExpression( - coalesce({ - context, - method: "regex", - internal: "randomFormatRegex", - }), - undefined, - undefined, - ), - ], - ); - - const writeRangedString = (props: { - context: ITypiaContext; - minLength: number; - maxLength: number; - }): ts.CallExpression => - decode_atomic({ - context: props.context, - atomic: MetadataAtomic.create({ - type: "string", - tags: [ - [ - ...MetadataCommentTagFactory.get({ - kind: "minLength", - type: "string", - value: props.minLength.toString(), - }), - ...MetadataCommentTagFactory.get({ - kind: "maxLength", - type: "string", - value: props.maxLength.toString(), - }), - ], - ], - }), - })[0]!; -} - -const coalesce = (props: { - context: ITypiaContext; - method: string; - internal: string; -}): ts.Expression => - ExpressionFactory.coalesce( - ts.factory.createPropertyAccessChain( - ts.factory.createIdentifier("_generator"), - ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), - ts.factory.createIdentifier(props.method), - ), - props.context.importer.internal(props.internal), - ); - -interface IExplore { - function: boolean; - recursive: boolean; -} - -const Prefix = { - object: (i: number) => `_ro${i}`, - array: (i: number) => `_ra${i}`, - tuple: (i: number) => `_rt${i}`, -}; diff --git a/packages/core/src/programmers/ValidateProgrammer.ts b/packages/core/src/programmers/ValidateProgrammer.ts deleted file mode 100644 index e5bf2da6a47..00000000000 --- a/packages/core/src/programmers/ValidateProgrammer.ts +++ /dev/null @@ -1,471 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../context/IProgrammerProps"; -import { ITypiaContext } from "../context/ITypiaContext"; -import { ExpressionFactory } from "../factories/ExpressionFactory"; -import { IdentifierFactory } from "../factories/IdentifierFactory"; -import { StatementFactory } from "../factories/StatementFactory"; -import { TypeFactory } from "../factories/TypeFactory"; -import { IsProgrammer } from "./IsProgrammer"; -import { FunctionProgrammer } from "./helpers/FunctionProgrammer"; -import { IExpressionEntry } from "./helpers/IExpressionEntry"; -import { OptionPredicator } from "./helpers/OptionPredicator"; -import { CheckerProgrammer } from "./internal/CheckerProgrammer"; -import { FeatureProgrammer } from "./internal/FeatureProgrammer"; -import { check_everything } from "./iterate/check_everything"; -import { check_object } from "./iterate/check_object"; - -/** - * Validation code generator. - * - * Generates runtime validation code returning `IValidation` result. Collects - * all errors instead of failing on first error. Used by `typia.validate()` - * and `typia.validateEquals()`. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace ValidateProgrammer { - /** Validation configuration. */ - export interface IConfig { - /** Check for superfluous properties. */ - equals: boolean; - - /** Use standard schema format. */ - standardSchema?: boolean; - } - - /** Properties for validation code generation. */ - export interface IProps extends IProgrammerProps { - config: IConfig; - } - - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - config: IConfig; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose(props); - const composed: FeatureProgrammer.IComposed = CheckerProgrammer.compose({ - ...props, - config: { - prefix: "_v", - path: true, - trace: true, - numeric: OptionPredicator.numeric(props.context.options), - equals: props.config.equals, - atomist: (next) => - [ - ...(next.entry.expression ? [next.entry.expression] : []), - ...(next.entry.conditions.length === 0 - ? [] - : next.entry.conditions.length === 1 - ? next.entry.conditions[0]!.map((cond) => - ts.factory.createLogicalOr( - cond.expression, - create_report_call({ - exceptionable: - next.explore.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier( - next.explore.postfix - ? `_path + ${next.explore.postfix}` - : "_path", - ), - expected: cond.expected, - value: next.input, - }), - ), - ) - : [ - ts.factory.createLogicalOr( - next.entry.conditions - .map((set) => - set - .map((s) => s.expression) - .reduce((a, b) => - ts.factory.createLogicalAnd(a, b), - ), - ) - .reduce((a, b) => ts.factory.createLogicalOr(a, b)), - create_report_call({ - exceptionable: - next.explore.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier( - next.explore.postfix - ? `_path + ${next.explore.postfix}` - : "_path", - ), - expected: next.entry.expected, - value: next.input, - }), - ), - ]), - ].reduce((x, y) => ts.factory.createLogicalAnd(x, y)), - combiner: combine(props), - joiner: joiner(props), - success: ts.factory.createTrue(), - }, - }); - const arrow: ts.ArrowFunction = ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock( - [ - // validate when false === is(input) - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createBlock([ - // prepare errors - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("errors"), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createArrayLiteralExpression([]), - ), - ), - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("_report"), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createCallExpression( - ts.factory.createAsExpression( - props.context.importer.internal("validateReport"), - TypeFactory.keyword("any"), - ), - [], - [ts.factory.createIdentifier("errors")], - ), - ), - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - undefined, - undefined, - composed.body, - ), - undefined, - [ - ts.factory.createIdentifier("input"), - ts.factory.createStringLiteral("$input"), - ts.factory.createTrue(), - ], - ), - ), - StatementFactory.constant({ - name: "success", - value: ts.factory.createStrictEquality( - ExpressionFactory.number(0), - ts.factory.createIdentifier("errors.length"), - ), - }), - ts.factory.createReturnStatement( - ts.factory.createAsExpression( - create_output(), - TypeFactory.keyword("any"), - ), - ), - ]), - ), - ts.factory.createReturnStatement( - ts.factory.createAsExpression( - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "success", - ts.factory.createTrue(), - ), - ts.factory.createPropertyAssignment( - "data", - ts.factory.createIdentifier("input"), - ), - ], - true, - ), - TypeFactory.keyword("any"), - ), - ), - ], - true, - ), - ); - return { - functions: { - ...is.functions, - ...composed.functions, - }, - statements: [ - ...is.statements, - ...composed.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.mut({ name: "errors" }), - StatementFactory.mut({ name: "_report" }), - ], - arrow, - }; - }; - - export const write = (props: IProps) => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - config: props.config, - context: props.context, - modulo: props.modulo, - functor, - type: props.type, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - returnWrapper: props.config.standardSchema - ? (arrow) => - ts.factory.createCallExpression( - props.context.importer.internal("createStandardSchema"), - undefined, - [arrow], - ) - : undefined, - }); - }; -} - -const combine = - (props: { - config: ValidateProgrammer.IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - }): CheckerProgrammer.IConfig.Combiner => - (next) => { - if (next.explore.tracable === false) - return IsProgrammer.configure({ - options: { - object: (v) => - validate_object({ - context: props.context, - functor: props.functor, - config: props.config, - entries: v.entries, - input: v.input, - }), - numeric: true, - }, - context: props.context, - functor: props.functor, - }).combiner(next); - - const path: string = next.explore.postfix - ? `_path + ${next.explore.postfix}` - : "_path"; - return next.logic === "and" - ? next.binaries - .map((binary) => - binary.combined - ? binary.expression - : ts.factory.createLogicalOr( - binary.expression, - create_report_call({ - exceptionable: - next.explore.source === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier(path), - expected: next.expected, - value: next.input, - }), - ), - ) - .reduce(ts.factory.createLogicalAnd) - : ts.factory.createLogicalOr( - next.binaries - .map((binary) => binary.expression) - .reduce(ts.factory.createLogicalOr), - create_report_call({ - exceptionable: - next.explore.source === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier(path), - expected: next.expected, - value: next.input, - }), - ); - }; - -const validate_object = (props: { - config: ValidateProgrammer.IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - entries: IExpressionEntry[]; - input: ts.Expression; -}) => - check_object({ - config: { - equals: props.config.equals, - undefined: true, - assert: false, - reduce: ts.factory.createLogicalAnd, - positive: ts.factory.createTrue(), - superfluous: (input, description) => - create_report_call({ - path: ts.factory.createAdd( - ts.factory.createIdentifier("_path"), - ts.factory.createCallExpression( - props.context.importer.internal("accessExpressionAsString"), - undefined, - [ts.factory.createIdentifier("key")], - ), - ), - expected: "undefined", - value: input, - description, - }), - halt: (expr) => - ts.factory.createLogicalOr( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createIdentifier("_exceptionable"), - ), - expr, - ), - }, - context: props.context, - entries: props.entries, - input: props.input, - }); - -const joiner = (props: { - config: ValidateProgrammer.IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; -}): CheckerProgrammer.IConfig.IJoiner => ({ - object: (v) => - validate_object({ - context: props.context, - functor: props.functor, - config: props.config, - entries: v.entries, - input: v.input, - }), - array: (props) => - check_everything( - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "map"), - undefined, - [props.arrow], - ), - ), - failure: (next) => - create_report_call({ - exceptionable: - next.explore?.from === "top" - ? ts.factory.createTrue() - : ts.factory.createIdentifier("_exceptionable"), - path: ts.factory.createIdentifier( - next.explore?.postfix ? `_path + ${next.explore.postfix}` : "_path", - ), - expected: next.expected, - value: next.input, - }), - tuple: (binaries) => - check_everything(ts.factory.createArrayLiteralExpression(binaries, true)), -}); - -const create_output = () => - ts.factory.createConditionalExpression( - ts.factory.createIdentifier("success"), - undefined, - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createShorthandPropertyAssignment("success"), - ts.factory.createPropertyAssignment( - "data", - ts.factory.createIdentifier("input"), - ), - ], - true, - ), - undefined, - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createShorthandPropertyAssignment("success"), - ts.factory.createShorthandPropertyAssignment("errors"), - ts.factory.createPropertyAssignment( - "data", - ts.factory.createIdentifier("input"), - ), - ], - true, - ), - ); - -const create_report_call = (props: { - exceptionable?: ts.Expression; - path: ts.Expression; - expected: string; - value: ts.Expression; - description?: ts.Expression; -}): ts.Expression => - ts.factory.createCallExpression( - ts.factory.createIdentifier("_report"), - undefined, - [ - props.exceptionable ?? ts.factory.createIdentifier("_exceptionable"), - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment("path", props.path), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.value), - ...(props.description - ? [ - ts.factory.createPropertyAssignment( - "description", - props.description, - ), - ] - : []), - ], - true, - ), - ], - ); diff --git a/packages/core/src/programmers/functional/FunctionalAssertFunctionProgrammer.ts b/packages/core/src/programmers/functional/FunctionalAssertFunctionProgrammer.ts deleted file mode 100644 index cd68da59c25..00000000000 --- a/packages/core/src/programmers/functional/FunctionalAssertFunctionProgrammer.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { StringUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionalAssertParametersProgrammer } from "./FunctionalAssertParametersProgrammer"; -import { FunctionAssertReturnProgrammer } from "./FunctionalAssertReturnProgrammer"; - -export namespace FunctionalAssertFunctionProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - - export const write = (props: IProps): ts.CallExpression => { - const wrapper = errorFactoryWrapper({ - context: props.context, - parameters: props.declaration.parameters, - init: props.init, - }); - const p = FunctionalAssertParametersProgrammer.decompose({ - context: props.context, - modulo: props.modulo, - config: props.config, - parameters: props.declaration.parameters, - wrapper: wrapper.name, - }); - const r = FunctionAssertReturnProgrammer.decompose({ - context: props.context, - modulo: props.modulo, - config: props.config, - expression: props.expression, - declaration: props.declaration, - wrapper: wrapper.name, - }); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - wrapper.variable, - ...p.functions, - ...r.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - r.async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - props.declaration.type, - undefined, - ts.factory.createBlock([ - ...p.expressions.map(ts.factory.createExpressionStatement), - ts.factory.createReturnStatement(r.value), - ]), - ), - ), - ], - true, - ), - ); - }; - - export const errorFactoryWrapper = (props: { - context: ITypiaContext; - parameters: readonly ts.ParameterDeclaration[]; - init: ts.Expression | undefined; - }): { - name: string; - variable: ts.VariableStatement; - } => { - const name: string = StringUtil.escapeDuplicate({ - keep: props.parameters.map((p) => p.name.getText()), - input: "errorFactoryWrapper", - }); - const variable: ts.VariableStatement = ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - name, - undefined, - AssertProgrammer.Guardian.type(props.context), - props.init ?? - props.context.importer.internal( - "functionalTypeGuardErrorFactory", - ), - ), - ], - ts.NodeFlags.Const, - ), - ); - return { name, variable }; - }; - - export const hookPath = (props: { - wrapper: string; - replacer: string; - }): ts.ArrowFunction => - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("p")], - undefined, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier(props.wrapper), - undefined, - [ - ts.factory.createObjectLiteralExpression([ - ts.factory.createSpreadAssignment(ts.factory.createIdentifier("p")), - ts.factory.createPropertyAssignment( - "path", - ts.factory.createConditionalExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier("p"), - "path", - ), - undefined, - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier("p"), - "path", - ), - "replace", - ), - undefined, - [ - ts.factory.createStringLiteral("$input"), - ts.factory.createStringLiteral(props.replacer), - ], - ), - undefined, - ts.factory.createIdentifier("undefined"), - ), - ), - ]), - ], - ), - ); -} diff --git a/packages/core/src/programmers/functional/FunctionalAssertParametersProgrammer.ts b/packages/core/src/programmers/functional/FunctionalAssertParametersProgrammer.ts deleted file mode 100644 index ff6c4b30890..00000000000 --- a/packages/core/src/programmers/functional/FunctionalAssertParametersProgrammer.ts +++ /dev/null @@ -1,123 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionalAssertFunctionProgrammer } from "./FunctionalAssertFunctionProgrammer"; -import { FunctionalGeneralProgrammer } from "./internal/FunctionalGeneralProgrammer"; - -export namespace FunctionalAssertParametersProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - config: IConfig; - modulo: ts.LeftHandSideExpression; - parameters: readonly ts.ParameterDeclaration[]; - wrapper: string; - } - export interface IDecomposeOutput { - functions: ts.Statement[]; - expressions: ts.Expression[]; - } - - export const write = (props: IProps): ts.CallExpression => { - const wrapper = FunctionalAssertFunctionProgrammer.errorFactoryWrapper({ - context: props.context, - parameters: props.declaration.parameters, - init: props.init, - }); - const { async } = FunctionalGeneralProgrammer.getReturnType({ - checker: props.context.checker, - declaration: props.declaration, - }); - const result = decompose({ - context: props.context, - modulo: props.modulo, - config: props.config, - parameters: props.declaration.parameters, - wrapper: wrapper.name, - }); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - wrapper.variable, - ...result.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - props.declaration.type, - undefined, - ts.factory.createBlock( - [ - ...result.expressions.map( - ts.factory.createExpressionStatement, - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - props.expression, - undefined, - props.declaration.parameters.map((p) => - ts.factory.createIdentifier(p.name.getText()), - ), - ), - ), - ], - true, - ), - ), - ), - ], - true, - ), - ); - }; - - export const decompose = (props: IDecomposeProps): IDecomposeOutput => ({ - functions: props.parameters.map((p, i) => - StatementFactory.constant({ - name: `__assert_param_${i}`, - value: AssertProgrammer.write({ - context: props.context, - modulo: props.modulo, - config: { - equals: props.config.equals, - guard: false, - }, - type: p.type - ? props.context.checker.getTypeFromTypeNode(p.type) - : props.context.checker.getTypeFromTypeNode( - TypeFactory.keyword("any"), - ), - name: undefined, - init: FunctionalAssertFunctionProgrammer.hookPath({ - wrapper: props.wrapper, - replacer: `$input.parameters[${i}]`, - }), - }), - }), - ), - expressions: props.parameters.map((p, i) => - ts.factory.createCallExpression( - ts.factory.createIdentifier(`__assert_param_${i}`), - undefined, - [ts.factory.createIdentifier(p.name.getText())], - ), - ), - }); -} diff --git a/packages/core/src/programmers/functional/FunctionalAssertReturnProgrammer.ts b/packages/core/src/programmers/functional/FunctionalAssertReturnProgrammer.ts deleted file mode 100644 index b5aa7ac2698..00000000000 --- a/packages/core/src/programmers/functional/FunctionalAssertReturnProgrammer.ts +++ /dev/null @@ -1,113 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionalAssertFunctionProgrammer } from "./FunctionalAssertFunctionProgrammer"; -import { FunctionalGeneralProgrammer } from "./internal/FunctionalGeneralProgrammer"; - -export namespace FunctionAssertReturnProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - expression: ts.Expression; - declaration: ts.FunctionDeclaration; - init?: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - expression: ts.Expression; - declaration: ts.FunctionDeclaration; - wrapper: string; - } - export interface IDecomposeOutput { - async: boolean; - functions: ts.Statement[]; - value: ts.Expression; - } - - export const write = (props: IProps): ts.CallExpression => { - const wrapper = FunctionalAssertFunctionProgrammer.errorFactoryWrapper({ - context: props.context, - parameters: props.declaration.parameters, - init: props.init, - }); - const result = decompose({ - context: props.context, - modulo: props.modulo, - config: props.config, - expression: props.expression, - declaration: props.declaration, - wrapper: wrapper.name, - }); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - wrapper.variable, - ...result.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - result.async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - props.declaration.type, - undefined, - result.value, - ), - ), - ], - true, - ), - ); - }; - - export const decompose = (props: IDecomposeProps): IDecomposeOutput => { - const { type, async } = FunctionalGeneralProgrammer.getReturnType({ - checker: props.context.checker, - declaration: props.declaration, - }); - const caller: ts.CallExpression = ts.factory.createCallExpression( - props.expression, - undefined, - props.declaration.parameters.map((p) => - ts.factory.createIdentifier(p.name.getText()), - ), - ); - return { - async, - functions: [ - StatementFactory.constant({ - name: "__assert_return", - value: AssertProgrammer.write({ - context: props.context, - modulo: props.modulo, - config: { - equals: props.config.equals, - guard: false, - }, - type, - name: undefined, - init: FunctionalAssertFunctionProgrammer.hookPath({ - wrapper: props.wrapper, - replacer: "$input.return", - }), - }), - }), - ], - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert_return"), - undefined, - [async ? ts.factory.createAwaitExpression(caller) : caller], - ), - }; - }; -} diff --git a/packages/core/src/programmers/functional/FunctionalIsFunctionProgrammer.ts b/packages/core/src/programmers/functional/FunctionalIsFunctionProgrammer.ts deleted file mode 100644 index 0298cbcf59e..00000000000 --- a/packages/core/src/programmers/functional/FunctionalIsFunctionProgrammer.ts +++ /dev/null @@ -1,70 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { FunctionalIsParametersProgrammer } from "./FunctionalIsParametersProgrammer"; -import { FunctionalIsReturnProgrammer } from "./FunctionalIsReturnProgrammer"; - -export namespace FunctionalIsFunctionProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - - export const write = (props: IProps): ts.CallExpression => { - const p = FunctionalIsParametersProgrammer.decompose(props); - const r = FunctionalIsReturnProgrammer.decompose(props); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - ...p.functions, - ...r.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - r.async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - getReturnTypeNode({ - declaration: props.declaration, - async: r.async, - }), - undefined, - ts.factory.createBlock([...p.statements, ...r.statements], true), - ), - ), - ], - true, - ), - ); - }; - - export const getReturnTypeNode = (props: { - declaration: ts.FunctionDeclaration; - async: boolean; - }): ts.TypeNode | undefined => - props.declaration.type - ? props.async - ? !!(props.declaration.type! as ts.TypeReferenceNode).typeArguments?.[0] - ? ts.factory.createTypeReferenceNode("Promise", [ - ts.factory.createUnionTypeNode([ - (props.declaration.type! as ts.TypeReferenceNode) - .typeArguments![0]!, - ts.factory.createTypeReferenceNode("null"), - ]), - ]) - : undefined - : ts.factory.createUnionTypeNode([ - props.declaration.type, - ts.factory.createTypeReferenceNode("null"), - ]) - : undefined; -} diff --git a/packages/core/src/programmers/functional/FunctionalIsParametersProgrammer.ts b/packages/core/src/programmers/functional/FunctionalIsParametersProgrammer.ts deleted file mode 100644 index 615a7a95cf6..00000000000 --- a/packages/core/src/programmers/functional/FunctionalIsParametersProgrammer.ts +++ /dev/null @@ -1,111 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionalIsFunctionProgrammer } from "./FunctionalIsFunctionProgrammer"; -import { FunctionalGeneralProgrammer } from "./internal/FunctionalGeneralProgrammer"; - -export namespace FunctionalIsParametersProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - config: IConfig; - modulo: ts.LeftHandSideExpression; - declaration: ts.FunctionDeclaration; - } - export interface IDecomposeOutput { - functions: ts.Statement[]; - statements: ts.Statement[]; - } - - export const write = (props: IProps): ts.CallExpression => { - const { async } = FunctionalGeneralProgrammer.getReturnType({ - checker: props.context.checker, - declaration: props.declaration, - }); - const result = decompose(props); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - ...result.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - FunctionalIsFunctionProgrammer.getReturnTypeNode({ - declaration: props.declaration, - async, - }), - undefined, - ts.factory.createBlock( - [ - ...result.statements, - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - props.expression, - undefined, - props.declaration.parameters.map((p) => - ts.factory.createIdentifier(p.name.getText()), - ), - ), - ), - ], - true, - ), - ), - ), - ], - true, - ), - ); - }; - - export const decompose = (props: IDecomposeProps): IDecomposeOutput => ({ - functions: props.declaration.parameters.map((p, i) => - StatementFactory.constant({ - name: `__is_param_${i}`, - value: IsProgrammer.write({ - context: props.context, - modulo: props.modulo, - config: props.config, - type: props.context.checker.getTypeFromTypeNode( - p.type ?? TypeFactory.keyword("any"), - ), - init: undefined, - name: undefined, - }), - }), - ), - statements: props.declaration.parameters - .map((p, i) => [ - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createCallExpression( - ts.factory.createIdentifier(`__is_param_${i}`), - undefined, - [ts.factory.createIdentifier(p.name.getText())], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ]) - .flat(), - }); -} diff --git a/packages/core/src/programmers/functional/FunctionalIsReturnProgrammer.ts b/packages/core/src/programmers/functional/FunctionalIsReturnProgrammer.ts deleted file mode 100644 index 29c5c4035fd..00000000000 --- a/packages/core/src/programmers/functional/FunctionalIsReturnProgrammer.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { StringUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionalIsFunctionProgrammer } from "./FunctionalIsFunctionProgrammer"; -import { FunctionalGeneralProgrammer } from "./internal/FunctionalGeneralProgrammer"; - -export namespace FunctionalIsReturnProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - expression: ts.Expression; - declaration: ts.FunctionDeclaration; - } - export interface IDecomposeOutput { - async: boolean; - functions: ts.Statement[]; - statements: ts.Statement[]; - } - - export const write = (props: IProps): ts.CallExpression => { - const result = decompose(props); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - ...result.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - result.async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - FunctionalIsFunctionProgrammer.getReturnTypeNode({ - declaration: props.declaration, - async: result.async, - }), - undefined, - ts.factory.createBlock(result.statements, true), - ), - ), - ], - true, - ), - ); - }; - - export const decompose = (props: IDecomposeProps): IDecomposeOutput => { - const { type, async } = FunctionalGeneralProgrammer.getReturnType({ - checker: props.context.checker, - declaration: props.declaration, - }); - const caller: ts.CallExpression = ts.factory.createCallExpression( - props.expression, - undefined, - props.declaration.parameters.map((p) => - ts.factory.createIdentifier(p.name.getText()), - ), - ); - const name: string = StringUtil.escapeDuplicate({ - keep: props.declaration.parameters.map((p) => p.name.getText()), - input: "result", - }); - return { - async, - functions: [ - StatementFactory.constant({ - name: "__is_return", - value: IsProgrammer.write({ - ...props, - type, - name: undefined, - init: undefined, - }), - }), - ], - statements: [ - StatementFactory.constant({ - name, - value: async ? ts.factory.createAwaitExpression(caller) : caller, - }), - ts.factory.createReturnStatement( - ts.factory.createConditionalExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is_return"), - undefined, - [ts.factory.createIdentifier(name)], - ), - undefined, - ts.factory.createIdentifier(name), - undefined, - ts.factory.createNull(), - ), - ), - ], - }; - }; -} diff --git a/packages/core/src/programmers/functional/FunctionalValidateFunctionProgrammer.ts b/packages/core/src/programmers/functional/FunctionalValidateFunctionProgrammer.ts deleted file mode 100644 index 0ab5194fe0e..00000000000 --- a/packages/core/src/programmers/functional/FunctionalValidateFunctionProgrammer.ts +++ /dev/null @@ -1,117 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { FunctionalValidateParametersProgrammer } from "./FunctionalValidateParametersProgrammer"; -import { FunctionalValidateReturnProgrammer } from "./FunctionalValidateReturnProgrammer"; - -export namespace FunctionalValidateFunctionProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - - export const write = (props: IProps): ts.CallExpression => { - const p = FunctionalValidateParametersProgrammer.decompose(props); - const r = FunctionalValidateReturnProgrammer.decompose(props); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - ...p.functions, - ...r.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - r.async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - getReturnTypeNode({ - context: props.context, - declaration: props.declaration, - async: r.async, - }), - undefined, - ts.factory.createBlock([...p.statements, ...r.statements], true), - ), - ), - ], - true, - ), - ); - }; - - export const hookErrors = (props: { - expression: ts.Expression; - replacer: ts.Expression; - }): ts.CallExpression => - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(props.expression, "map"), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("error")], - undefined, - undefined, - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createSpreadAssignment( - ts.factory.createIdentifier("error"), - ), - ts.factory.createPropertyAssignment( - "path", - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier("error"), - "path", - ), - "replace", - ), - undefined, - [ts.factory.createStringLiteral("$input"), props.replacer], - ), - ), - ], - true, - ), - ), - ], - ); - - export const getReturnTypeNode = (props: { - context: ITypiaContext; - declaration: ts.FunctionDeclaration; - async: boolean; - }): ts.TypeNode | undefined => - props.declaration.type - ? props.async - ? !!(props.declaration.type! as ts.TypeReferenceNode).typeArguments?.[0] - ? ts.factory.createTypeReferenceNode("Promise", [ - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [ - (props.declaration.type! as ts.TypeReferenceNode) - .typeArguments![0]!, - ], - }), - ]) - : undefined - : props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [props.declaration.type], - }) - : undefined; -} diff --git a/packages/core/src/programmers/functional/FunctionalValidateParametersProgrammer.ts b/packages/core/src/programmers/functional/FunctionalValidateParametersProgrammer.ts deleted file mode 100644 index ace9792d78b..00000000000 --- a/packages/core/src/programmers/functional/FunctionalValidateParametersProgrammer.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { StringUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionalValidateFunctionProgrammer } from "./FunctionalValidateFunctionProgrammer"; -import { FunctionalGeneralProgrammer } from "./internal/FunctionalGeneralProgrammer"; - -export namespace FunctionalValidateParametersProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - } - export interface IDecomposeOutput { - functions: ts.Statement[]; - statements: ts.Statement[]; - } - - export const write = (props: IProps): ts.CallExpression => { - const { async } = FunctionalGeneralProgrammer.getReturnType({ - checker: props.context.checker, - declaration: props.declaration, - }); - const result = decompose(props); - const caller: ts.CallExpression = ts.factory.createCallExpression( - props.expression, - undefined, - props.declaration.parameters.map((p) => - ts.factory.createIdentifier(p.name.getText()), - ), - ); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - ...result.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - FunctionalValidateFunctionProgrammer.getReturnTypeNode({ - context: props.context, - declaration: props.declaration, - async, - }), - undefined, - ts.factory.createBlock( - [ - ...result.statements, - ts.factory.createReturnStatement( - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "success", - ts.factory.createTrue(), - ), - ts.factory.createPropertyAssignment( - "data", - async - ? ts.factory.createAwaitExpression(caller) - : caller, - ), - ts.factory.createPropertyAssignment( - "errors", - ts.factory.createArrayLiteralExpression([]), - ), - ], - true, - ), - ), - ], - true, - ), - ), - ), - ], - true, - ), - ); - }; - - export const decompose = (props: IDecomposeProps): IDecomposeOutput => { - const resultName: string = StringUtil.escapeDuplicate({ - keep: props.declaration.parameters.map((p) => p.name.getText()), - input: "paramErrorResults", - }); - const validationResultArray: ts.ArrayLiteralExpression = - ts.factory.createArrayLiteralExpression( - props.declaration.parameters.map((p, i) => - ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier(`__validate_param_${i}`), - undefined, - [ts.factory.createIdentifier(p.name.getText())], - ), - props.context.importer.type({ - file: "typia", - name: "IValidation.IFailure", - }), - ), - ), - true, - ); - const errorMatrix = ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(validationResultArray, "map"), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("r"), IdentifierFactory.parameter("i")], - undefined, - undefined, - ts.factory.createConditionalExpression( - ts.factory.createStrictEquality( - ts.factory.createTrue(), - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier("r"), - "success", - ), - ), - undefined, - ts.factory.createIdentifier("r"), - undefined, - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createSpreadAssignment( - ts.factory.createIdentifier("r"), - ), - ts.factory.createPropertyAssignment( - "errors", - FunctionalValidateFunctionProgrammer.hookErrors({ - expression: ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier("r"), - "errors", - ), - replacer: ts.factory.createTemplateExpression( - ts.factory.createTemplateHead("$input.parameters["), - [ - ts.factory.createTemplateSpan( - ts.factory.createIdentifier("i"), - ts.factory.createTemplateTail("]"), - ), - ], - ), - }), - ), - ], - true, - ), - ), - ), - ], - ); - const failures = ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(errorMatrix, "filter"), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("r")], - undefined, - undefined, - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier("r"), - "success", - ), - ), - ), - ], - ); - return { - functions: props.declaration.parameters.map((p, i) => - StatementFactory.constant({ - name: `__validate_param_${i}`, - value: ValidateProgrammer.write({ - ...props, - type: props.context.checker.getTypeFromTypeNode( - p.type ?? TypeFactory.keyword("any"), - ), - name: undefined, - init: undefined, - }), - }), - ), - statements: [ - StatementFactory.constant({ - name: resultName, - value: failures, - }), - ts.factory.createIfStatement( - ts.factory.createStrictInequality( - ts.factory.createNumericLiteral("0"), - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(resultName), - "length", - ), - ), - ts.factory.createReturnStatement( - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "success", - ts.factory.createFalse(), - ), - ts.factory.createPropertyAssignment( - "errors", - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createIdentifier(resultName), - "map", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "r", - TypeFactory.keyword("any"), - ), - ], - undefined, - undefined, - IdentifierFactory.access( - ts.factory.createIdentifier("r"), - "errors", - ), - ), - ], - ), - "flat", - ), - undefined, - undefined, - ), - ), - ], - true, - ), - ), - ), - ], - }; - }; -} diff --git a/packages/core/src/programmers/functional/FunctionalValidateReturnProgrammer.ts b/packages/core/src/programmers/functional/FunctionalValidateReturnProgrammer.ts deleted file mode 100644 index 9f0ae6f6241..00000000000 --- a/packages/core/src/programmers/functional/FunctionalValidateReturnProgrammer.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { StringUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionalValidateFunctionProgrammer } from "./FunctionalValidateFunctionProgrammer"; -import { FunctionalGeneralProgrammer } from "./internal/FunctionalGeneralProgrammer"; - -export namespace FunctionalValidateReturnProgrammer { - export interface IConfig { - equals: boolean; - } - export interface IProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - declaration: ts.FunctionDeclaration; - expression: ts.Expression; - init?: ts.Expression | undefined; - } - export interface IDecomposeProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - config: IConfig; - expression: ts.Expression; - declaration: ts.FunctionDeclaration; - } - export interface IDecomposeOutput { - async: boolean; - functions: ts.Statement[]; - statements: ts.Statement[]; - } - - export const write = (props: IProps): ts.CallExpression => { - const result = decompose(props); - return ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - ...result.functions, - ts.factory.createReturnStatement( - ts.factory.createArrowFunction( - result.async - ? [ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword)] - : undefined, - undefined, - props.declaration.parameters, - FunctionalValidateFunctionProgrammer.getReturnTypeNode({ - context: props.context, - declaration: props.declaration, - async: result.async, - }), - undefined, - ts.factory.createBlock(result.statements, true), - ), - ), - ], - true, - ), - ); - }; - - export const decompose = (props: IDecomposeProps): IDecomposeOutput => { - const { type, async } = FunctionalGeneralProgrammer.getReturnType({ - checker: props.context.checker, - declaration: props.declaration, - }); - const caller: ts.CallExpression = ts.factory.createCallExpression( - props.expression, - undefined, - props.declaration.parameters.map((p) => - ts.factory.createIdentifier(p.name.getText()), - ), - ); - - const name: string = StringUtil.escapeDuplicate({ - keep: props.declaration.parameters.map((p) => p.name.getText()), - input: "result", - }); - return { - async, - functions: [ - StatementFactory.constant({ - name: "__validate_return", - value: ValidateProgrammer.write({ - ...props, - type, - name: undefined, - init: undefined, - }), - }), - ], - statements: [ - StatementFactory.constant({ - name, - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate_return"), - undefined, - [async ? ts.factory.createAwaitExpression(caller) : caller], - ), - }), - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(name), - ts.factory.createIdentifier("success"), - ), - ), - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(name), - ts.factory.createIdentifier("errors"), - ), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - FunctionalValidateFunctionProgrammer.hookErrors({ - expression: ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(name), - ts.factory.createIdentifier("errors"), - ), - replacer: ts.factory.createStringLiteral("$input.return"), - }), - ), - ), - ), - ts.factory.createReturnStatement(ts.factory.createIdentifier("result")), - ], - }; - }; -} diff --git a/packages/core/src/programmers/functional/index.ts b/packages/core/src/programmers/functional/index.ts deleted file mode 100644 index 6aa7fe024bf..00000000000 --- a/packages/core/src/programmers/functional/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./FunctionalAssertFunctionProgrammer"; -export * from "./FunctionalAssertParametersProgrammer"; -export * from "./FunctionalAssertReturnProgrammer"; -export * from "./FunctionalIsFunctionProgrammer"; -export * from "./FunctionalIsParametersProgrammer"; -export * from "./FunctionalIsReturnProgrammer"; -export * from "./FunctionalValidateFunctionProgrammer"; -export * from "./FunctionalValidateParametersProgrammer"; -export * from "./FunctionalValidateReturnProgrammer"; diff --git a/packages/core/src/programmers/functional/internal/FunctionalGeneralProgrammer.ts b/packages/core/src/programmers/functional/internal/FunctionalGeneralProgrammer.ts deleted file mode 100644 index d6e79b05b90..00000000000 --- a/packages/core/src/programmers/functional/internal/FunctionalGeneralProgrammer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { TypeFactory } from "../../../factories/TypeFactory"; - -export namespace FunctionalGeneralProgrammer { - export interface IProps { - checker: ts.TypeChecker; - declaration: ts.FunctionDeclaration | ts.SignatureDeclaration; - } - export interface IOutput { - type: ts.Type; - async: boolean; - } - export const getReturnType = (props: IProps): IOutput => { - const signature: ts.Signature | undefined = - props.checker.getSignatureFromDeclaration(props.declaration); - const type: ts.Type = - signature?.getReturnType() ?? - props.checker.getTypeFromTypeNode(TypeFactory.keyword("any")); - - if (type.symbol?.name === "Promise") { - const generic: readonly ts.Type[] = props.checker.getTypeArguments( - type as ts.TypeReference, - ); - return generic.length === 1 - ? { type: generic[0]!, async: true } - : { - type: props.checker.getTypeFromTypeNode(TypeFactory.keyword("any")), - async: false, - }; - } - return { type, async: false }; - }; -} diff --git a/packages/core/src/programmers/helpers/AtomicPredicator.ts b/packages/core/src/programmers/helpers/AtomicPredicator.ts deleted file mode 100644 index f45e49397ba..00000000000 --- a/packages/core/src/programmers/helpers/AtomicPredicator.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Atomic } from "@typia/interface"; -import { ArrayUtil } from "@typia/utils"; - -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace AtomicPredicator { - export const constant = (props: { - metadata: MetadataSchema; - name: Atomic.Literal; - }): boolean => - !ArrayUtil.has( - props.metadata.natives, - (native) => native.name.toLowerCase() === props.name, - ); - - export const atomic = (props: { - metadata: MetadataSchema; - name: Atomic.Literal; - }): boolean => - !ArrayUtil.has( - props.metadata.natives, - (native) => native.name.toLowerCase() === props.name, - ); - - export const native = (name: string) => LIKE.has(name.toLowerCase()); - - export const template = (metadata: MetadataSchema): boolean => - !ArrayUtil.has( - metadata.natives, - (native) => native.name.toLowerCase() === "string", - ); -} - -const LIKE = new Set(["boolean", "bigint", "number", "string"]); diff --git a/packages/core/src/programmers/helpers/CloneJoiner.ts b/packages/core/src/programmers/helpers/CloneJoiner.ts deleted file mode 100644 index 8bdc412eda3..00000000000 --- a/packages/core/src/programmers/helpers/CloneJoiner.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { metadata_to_pattern } from "../iterate/metadata_to_pattern"; -import { IExpressionEntry } from "./IExpressionEntry"; - -export namespace CloneJoiner { - export const object = (props: { - input: ts.Expression; - entries: IExpressionEntry[]; - }): ts.ConciseBody => { - if (props.entries.length === 0) return ts.factory.createIdentifier("{}"); - - const regular = props.entries.filter((e) => e.key.isSoleLiteral()); - const dynamic = props.entries.filter((e) => !e.key.isSoleLiteral()); - const literal = ts.factory.createObjectLiteralExpression( - regular.map((entry) => { - const str: string = entry.key.getSoleLiteral()!; - return ts.factory.createPropertyAssignment( - NamingConvention.variable(str) - ? str - : ts.factory.createStringLiteral(str), - entry.expression, - ); - }), - true, - ); - if (dynamic.length === 0) return literal; - - const key = ts.factory.createIdentifier("key"); - const output = ts.factory.createIdentifier("output"); - - const statements: ts.Statement[] = []; - if (regular.length !== 0) - statements.push( - ts.factory.createIfStatement( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createArrayLiteralExpression( - regular.map((r) => - ts.factory.createStringLiteral(r.key.getSoleLiteral()!), - ), - ), - "some", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("regular")], - undefined, - undefined, - ts.factory.createStrictEquality( - ts.factory.createIdentifier("regular"), - ts.factory.createIdentifier("key"), - ), - ), - ], - ), - ts.factory.createContinueStatement(), - ), - ); - statements.push( - ...dynamic.map((entry) => - ts.factory.createIfStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - `RegExp(/${metadata_to_pattern({ - top: true, - metadata: entry.key, - })}/).test`, - ), - undefined, - [key], - ), - ts.factory.createBlock([ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createElementAccessExpression(output, key), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - entry.expression, - ), - ), - ts.factory.createContinueStatement(), - ]), - ), - ), - ); - - return ts.factory.createBlock([ - StatementFactory.constant({ - name: "output", - value: ts.factory.createAsExpression( - literal, - TypeFactory.keyword("any"), - ), - }), - ts.factory.createForOfStatement( - undefined, - StatementFactory.entry({ - key: "key", - value: "value", - }), - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.entries"), - undefined, - [props.input], - ), - ts.factory.createBlock(statements), - ), - ts.factory.createReturnStatement(output), - ]); - }; - - export const tuple = (props: { - elements: ts.Expression[]; - rest: ts.Expression | null; - }): ts.Expression => { - return ts.factory.createAsExpression( - ts.factory.createArrayLiteralExpression( - props.rest === null - ? props.elements - : [...props.elements, ts.factory.createSpreadElement(props.rest)], - true, - ), - TypeFactory.keyword("any"), - ); - }; - - export const array = (props: { - input: ts.Expression; - arrow: ts.Expression; - }) => - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(props.input, "map"), - undefined, - [props.arrow], - ); -} diff --git a/packages/core/src/programmers/helpers/FunctionProgrammer.ts b/packages/core/src/programmers/helpers/FunctionProgrammer.ts deleted file mode 100644 index 16e60648b48..00000000000 --- a/packages/core/src/programmers/helpers/FunctionProgrammer.ts +++ /dev/null @@ -1,67 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { StatementFactory } from "../../factories/StatementFactory"; - -export class FunctionProgrammer { - private readonly local_: Set = new Set(); - private readonly unions_: Map = new Map(); - private readonly variables_: Map = new Map(); - private sequence_: number = 0; - - public constructor(public readonly method: string) {} - - public useLocal(name: string): string { - this.local_.add(name); - return name; - } - - public hasLocal(name: string): boolean { - return this.local_.has(name); - } - - public declare(includeUnions: boolean = true): ts.Statement[] { - return [ - ...[...this.variables_.entries()].map(([name, value]) => - StatementFactory.constant({ name, value }), - ), - ...(includeUnions === true - ? [...this.unions_.values()].map(([name, value]) => - StatementFactory.constant({ name, value }), - ) - : []), - ]; - } - - public declareUnions(): ts.Statement[] { - return [...this.unions_.values()].map(([name, value]) => - StatementFactory.constant({ name, value }), - ); - } - - public increment(): number { - return ++this.sequence_; - } - - public emplaceUnion( - prefix: string, - name: string, - factory: () => ts.ArrowFunction, - ): string { - const key: string = `${prefix}::${name}`; - const oldbie = this.unions_.get(key); - if (oldbie) return oldbie[0]; - - const index: number = this.unions_.size; - const accessor: string = `${prefix}p${index}`; - - const tuple: [string, ReturnType] = [accessor, null!]; - this.unions_.set(key, tuple); - tuple[1] = factory(); - return accessor; - } - - public emplaceVariable(name: string, value: ts.Expression): ts.Expression { - this.variables_.set(name, value); - return ts.factory.createIdentifier(name); - } -} diff --git a/packages/core/src/programmers/helpers/HttpMetadataUtil.ts b/packages/core/src/programmers/helpers/HttpMetadataUtil.ts deleted file mode 100644 index 5ee88283f9e..00000000000 --- a/packages/core/src/programmers/helpers/HttpMetadataUtil.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace HttpMetadataUtil { - export const atomics = ( - metadata: MetadataSchema, - ): Set<"boolean" | "bigint" | "number" | "string"> => - new Set([ - ...metadata.atomics.map((a) => a.type), - ...metadata.constants.map((c) => c.type), - ...(metadata.templates.length ? (["string"] as const) : []), - ]); - - export const isUnion = (metadata: MetadataSchema): boolean => - atomics(metadata).size + - metadata.arrays.length + - metadata.tuples.length + - metadata.natives.length + - metadata.maps.length + - metadata.objects.length > - 1; -} diff --git a/packages/core/src/programmers/helpers/ICheckEntry.ts b/packages/core/src/programmers/helpers/ICheckEntry.ts deleted file mode 100644 index f18e9cd9bb8..00000000000 --- a/packages/core/src/programmers/helpers/ICheckEntry.ts +++ /dev/null @@ -1,13 +0,0 @@ -import ts from "@typescript/native-preview"; - -export interface ICheckEntry { - expected: string; - expression: ts.Expression | null; - conditions: ICheckEntry.ICondition[][]; -} -export namespace ICheckEntry { - export interface ICondition { - expected: string; - expression: ts.Expression; - } -} diff --git a/packages/core/src/programmers/helpers/IExpressionEntry.ts b/packages/core/src/programmers/helpers/IExpressionEntry.ts deleted file mode 100644 index 3f75c4d68a9..00000000000 --- a/packages/core/src/programmers/helpers/IExpressionEntry.ts +++ /dev/null @@ -1,12 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export interface IExpressionEntry< - Expression extends ts.ConciseBody = ts.ConciseBody, -> { - input: ts.Expression; - key: MetadataSchema; - meta: MetadataSchema; - expression: Expression; -} diff --git a/packages/core/src/programmers/helpers/NotationJoiner.ts b/packages/core/src/programmers/helpers/NotationJoiner.ts deleted file mode 100644 index b686c62b723..00000000000 --- a/packages/core/src/programmers/helpers/NotationJoiner.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { metadata_to_pattern } from "../iterate/metadata_to_pattern"; -import { IExpressionEntry } from "./IExpressionEntry"; - -export namespace NotationJoiner { - export const object = (props: { - rename: (str: string) => string; - input: ts.Expression; - entries: IExpressionEntry[]; - }): ts.ConciseBody => { - if (props.entries.length === 0) return ts.factory.createIdentifier("{}"); - - const regular = props.entries.filter((e) => e.key.isSoleLiteral()); - const dynamic = props.entries.filter((e) => !e.key.isSoleLiteral()); - const literal = ts.factory.createObjectLiteralExpression( - regular.map((entry) => { - const str: string = props.rename(entry.key.getSoleLiteral()!); - return ts.factory.createPropertyAssignment( - NamingConvention.variable(str) - ? str - : ts.factory.createStringLiteral(str), - entry.expression, - ); - }), - true, - ); - if (dynamic.length === 0) return literal; - - const key = ts.factory.createIdentifier("key"); - const output = ts.factory.createIdentifier("output"); - - const statements: ts.Statement[] = []; - if (regular.length !== 0) - statements.push( - ts.factory.createIfStatement( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createArrayLiteralExpression( - regular.map((r) => - ts.factory.createStringLiteral(r.key.getSoleLiteral()!), - ), - ), - "some", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("regular")], - undefined, - undefined, - ts.factory.createStrictEquality( - ts.factory.createIdentifier("regular"), - ts.factory.createIdentifier("key"), - ), - ), - ], - ), - ts.factory.createContinueStatement(), - ), - ); - statements.push( - ...dynamic.map((entry) => - ts.factory.createIfStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - `RegExp(/${metadata_to_pattern({ - top: true, - metadata: entry.key, - })}/).test`, - ), - undefined, - [key], - ), - ts.factory.createBlock([ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createElementAccessExpression(output, key), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - entry.expression, - ), - ), - ts.factory.createContinueStatement(), - ]), - ), - ), - ); - - return ts.factory.createBlock([ - StatementFactory.constant({ - name: "output", - value: ts.factory.createAsExpression( - literal, - TypeFactory.keyword("any"), - ), - }), - ts.factory.createForOfStatement( - undefined, - StatementFactory.entry({ - key: "key", - value: "value", - }), - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.entries"), - undefined, - [props.input], - ), - ts.factory.createBlock(statements), - ), - ts.factory.createReturnStatement(output), - ]); - }; - - export const tuple = (props: { - elements: ts.Expression[]; - rest: ts.Expression | null; - }): ts.Expression => { - return ts.factory.createAsExpression( - ts.factory.createArrayLiteralExpression( - props.rest === null - ? props.elements - : [...props.elements, ts.factory.createSpreadElement(props.rest)], - true, - ), - TypeFactory.keyword("any"), - ); - }; - - export const array = (props: { - input: ts.Expression; - arrow: ts.Expression; - }) => - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(props.input, "map"), - undefined, - [props.arrow], - ); -} diff --git a/packages/core/src/programmers/helpers/OptionPredicator.ts b/packages/core/src/programmers/helpers/OptionPredicator.ts deleted file mode 100644 index 099bdf35d08..00000000000 --- a/packages/core/src/programmers/helpers/OptionPredicator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ITransformOptions } from "../../context/ITransformOptions"; - -export namespace OptionPredicator { - export const numeric = (options: ITransformOptions): boolean => - finite(options) || options.numeric === true; - - export const functional = (options: ITransformOptions): boolean => - options.functional === true; - - export const finite = (options: ITransformOptions): boolean => - options.finite === true; - - export const undefined = (options: ITransformOptions): boolean => - options.undefined !== false; -} diff --git a/packages/core/src/programmers/helpers/ProtobufUtil.ts b/packages/core/src/programmers/helpers/ProtobufUtil.ts deleted file mode 100644 index e9e353f393e..00000000000 --- a/packages/core/src/programmers/helpers/ProtobufUtil.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { IMetadataTypeTag, ProtobufAtomic } from "@typia/interface"; - -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace ProtobufUtil { - export const isStaticObject = (obj: MetadataObjectType): boolean => - obj.properties.length >= 1 && - obj.properties.every((p) => p.key.isSoleLiteral()); - - export const size = (meta: MetadataSchema): number => - getAtomics(meta).size + - meta.arrays.length + - meta.tuples.length + - meta.natives.length + - meta.objects.length + - meta.maps.length; - - export const getSequence = (tags: IMetadataTypeTag[]): number | null => { - const sequence = tags.find( - (t) => - t.kind === "sequence" && - typeof (t.schema as any)?.["x-protobuf-sequence"] === "number", - ); - if (sequence === undefined) return null; - const value: number = Number( - (sequence.schema as any)["x-protobuf-sequence"], - ); - return Number.isNaN(value) ? null : value; - }; - - export const isUnion = (meta: MetadataSchema): boolean => size(meta) > 1; - - export const getAtomics = ( - meta: MetadataSchema, - union?: Map, - ): Map => { - const map: Map = union ?? new Map(); - - // CONSTANTS - for (const c of meta.constants) - if (c.type === "boolean") - map.set("bool", getSequence(c.values[0]?.tags[0] ?? [])); - else if (c.type === "bigint") { - const init: ProtobufAtomic.BigNumeric = deduce_bigint_type( - c.values.map((v) => BigInt(v.value)), - ); - for (const value of c.values) - decode_bigint({ - map, - tags: value.tags, - default: init, - }); - } else if (c.type === "number") { - const init: ProtobufAtomic.Numeric = deduce_numeric_type( - c.values.map((v) => v.value) as number[], - ); - for (const value of c.values) - decode_number({ - map, - tags: value.tags, - default: init, - }); - } else if (c.type === "string") - map.set("string", getSequence(c.values[0]?.tags[0] ?? [])); - if (meta.templates.length) - map.set("string", getSequence(meta.templates[0]?.tags[0] ?? [])); - - // ATOMICS - for (const atomic of meta.atomics) - if (atomic.type === "boolean") - map.set("bool", getSequence(atomic.tags[0] ?? [])); - else if (atomic.type === "bigint") - decode_bigint({ - map, - tags: atomic.tags, - default: "int64", - }); - else if (atomic.type === "number") - decode_number({ - map, - tags: atomic.tags, - default: "double", - }); - else if (atomic.type === "string") - map.set("string", getSequence(atomic.tags[0] ?? [])); - - // SORTING IF REQUIRED - if (Array.from(map.values()).some((v) => v === null)) arrange(map); - return map; - }; - - export const getNumbers = ( - meta: MetadataSchema, - union?: Map, - ): Map => { - const map: Map = union ?? new Map(); - for (const c of meta.constants) - if (c.type === "number") { - const init: ProtobufAtomic.Numeric = deduce_numeric_type( - c.values.map((v) => v.value) as number[], - ); - for (const value of c.values) - decode_number({ - map, - tags: value.tags, - default: init, - }); - } - for (const atomic of meta.atomics) - if (atomic.type === "number") - decode_number({ - map, - tags: atomic.tags, - default: "double", - }); - if (Array.from(map.values()).some((v) => v === null)) arrange(map); - return map; - }; - - export const getBigints = ( - meta: MetadataSchema, - union?: Map, - ): Map => { - const map: Map = - union ?? new Map(); - for (const c of meta.constants) - if (c.type === "bigint") { - const init: ProtobufAtomic.BigNumeric = deduce_bigint_type( - c.values.map((v) => BigInt(v.value)), - ); - for (const value of c.values) - decode_bigint({ - map, - tags: value.tags, - default: init, - }); - } - for (const atomic of meta.atomics) - if (atomic.type === "bigint") - decode_bigint({ - map, - tags: atomic.tags, - default: "int64", - }); - if (Array.from(map.values()).some((v) => v === null)) arrange(map); - return map; - }; - - const arrange = (map: Map): void => { - const entries = Array.from(map.entries()).sort((a, b) => - compare(a[0], b[0]), - ); - map.clear(); - for (const [key, value] of entries) map.set(key, value); - }; - - export const compare = (x: ProtobufAtomic, y: ProtobufAtomic): number => - ATOMIC_ORDER.get(x)! - ATOMIC_ORDER.get(y)!; -} - -const ATOMIC_ORDER = new Map( - ( - [ - "bool", - "int32", - "uint32", - "int64", - "uint64", - "float", - "double", - "string", - ] as const - ).map((str, i) => [str, i]), -); - -const deduce_bigint_type = (values: bigint[]): ProtobufAtomic.BigNumeric => - values.some((v) => v < 0) ? "int64" : "uint64"; -const deduce_numeric_type = (values: number[]): ProtobufAtomic.Numeric => - values.every((v) => Math.floor(v) === v) - ? values.every((v) => -2147483648 <= v && v <= 2147483647) - ? "int32" - : "int64" - : "double"; - -const decode_bigint = (next: { - map: Map; - tags: IMetadataTypeTag[][]; - default: ProtobufAtomic.BigNumeric; -}): void => { - if (next.tags.length === 0) { - next.map.set(next.default, null); - return; - } - for (const row of next.tags) { - const value: ProtobufAtomic.BigNumeric | undefined = row.find( - (tag) => - tag.kind === "type" && - (tag.value === "int64" || tag.value === "uint64"), - )?.value; - next.map.set(value ?? "int64", ProtobufUtil.getSequence(row)); - } -}; - -const decode_number = (next: { - map: Map; - tags: IMetadataTypeTag[][]; - default: ProtobufAtomic.Numeric; -}): void => { - if (next.tags.length === 0) { - next.map.set(next.default, null); - return; - } - for (const row of next.tags) { - const value: ProtobufAtomic.Numeric | undefined = row.find( - (tag) => - tag.kind === "type" && - (tag.value === "int32" || - tag.value === "uint32" || - tag.value === "int64" || - tag.value === "uint64" || - tag.value === "float" || - tag.value === "double"), - )?.value; - next.map.set(value ?? "double", ProtobufUtil.getSequence(row)); - } -}; diff --git a/packages/core/src/programmers/helpers/ProtobufWire.ts b/packages/core/src/programmers/helpers/ProtobufWire.ts deleted file mode 100644 index 4c2a834f6c1..00000000000 --- a/packages/core/src/programmers/helpers/ProtobufWire.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const enum ProtobufWire { - /** - * - Integers - * - Bool - * - Enum - */ - VARIANT = 0, - - /** - * - Fixed64 - * - Sfixed64 - * - Double - */ - I64 = 1, - - /** - * - String - * - Bytes - * - Mebedded messages - * - Packed repeated fields - */ - LEN = 2, - - START_GROUP = 3, - - END_GROUP = 4, - - /** - * - Fixed - * - Sfixed32 - * - Float - */ - I32 = 5, -} diff --git a/packages/core/src/programmers/helpers/PruneJoiner.ts b/packages/core/src/programmers/helpers/PruneJoiner.ts deleted file mode 100644 index 8353ff987dd..00000000000 --- a/packages/core/src/programmers/helpers/PruneJoiner.ts +++ /dev/null @@ -1,146 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { metadata_to_pattern } from "../iterate/metadata_to_pattern"; -import { prune_object_properties } from "../iterate/prune_object_properties"; -import { IExpressionEntry } from "./IExpressionEntry"; - -export namespace PruneJoiner { - export const object = (props: { - input: ts.Expression; - entries: IExpressionEntry[]; - object: MetadataObjectType; - }): ts.ConciseBody => { - // PREPARE ASSETS - const regular = props.entries.filter((entry) => entry.key.isSoleLiteral()); - const dynamic = props.entries.filter((entry) => !entry.key.isSoleLiteral()); - - const statements: ts.Statement[] = regular - .map((entry) => - ts.isBlock(entry.expression) - ? [...entry.expression.statements] - : [ts.factory.createExpressionStatement(entry.expression)], - ) - .flat(); - if (dynamic.length) - statements.push( - ts.factory.createExpressionStatement( - iterate_dynamic_properties({ - regular, - dynamic, - input: props.input, - }), - ), - ); - - statements.push(prune_object_properties(props.object)); - return ts.factory.createBlock(statements, true); - }; - - export const array = (props: { - input: ts.Expression; - arrow: ts.ArrowFunction; - }) => - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "forEach"), - undefined, - [props.arrow], - ); - - export const tuple = (props: { - elements: ts.ConciseBody[]; - rest: ts.ConciseBody | null; - }): ts.Block => { - const entire: ts.ConciseBody[] = [...props.elements]; - if (props.rest !== null) entire.push(props.rest); - - const statements: ts.Statement[] = entire - .map((elem) => - ts.isBlock(elem) - ? [...elem.statements] - : [ts.factory.createExpressionStatement(elem)], - ) - .flat(); - return ts.factory.createBlock(statements, true); - }; -} - -const iterate_dynamic_properties = (props: { - regular: IExpressionEntry[]; - dynamic: IExpressionEntry[]; - input: ts.Expression; -}) => - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.entries"), - undefined, - [props.input], - ), - "forEach", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - ts.factory.createArrayBindingPattern( - ["key", "value"].map((l) => - ts.factory.createBindingElement( - undefined, - undefined, - ts.factory.createIdentifier(l), - undefined, - ), - ), - ), - ), - ], - undefined, - undefined, - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createIdentifier("undefined"), - ts.factory.createIdentifier("value"), - ), - ts.factory.createReturnStatement(), - ), - ...props.regular.map(({ key }) => - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral(key.getSoleLiteral()!), - ts.factory.createIdentifier("key"), - ), - ts.factory.createReturnStatement(), - ), - ), - ...props.dynamic.map((dynamic) => - ts.factory.createIfStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - `RegExp(/${metadata_to_pattern({ - top: true, - metadata: dynamic.key, - })}/).test`, - ), - undefined, - [ts.factory.createIdentifier("key")], - ), - ts.isBlock(dynamic.expression) - ? dynamic.expression - : ts.factory.createBlock([ - ts.factory.createExpressionStatement(dynamic.expression), - ]), - ), - ), - ], - true, - ), - ), - ], - ); diff --git a/packages/core/src/programmers/helpers/RandomJoiner.ts b/packages/core/src/programmers/helpers/RandomJoiner.ts deleted file mode 100644 index 2e69ea76ef4..00000000000 --- a/packages/core/src/programmers/helpers/RandomJoiner.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { OpenApi } from "@typia/interface"; -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; - -export namespace RandomJoiner { - export type Decoder = (metadata: MetadataSchema) => ts.Expression; - - export const array = (props: { - decode: Decoder; - recursive: boolean; - expression: ts.Expression; - array: MetadataArrayType; - schema: Omit | undefined; - }): ts.Expression => { - const call: ts.Expression = ts.factory.createCallExpression( - props.expression, - undefined, - [ - ts.factory.createObjectLiteralExpression( - [ - ...(props.schema - ? Object.entries(props.schema) - .filter(([key]) => key !== "items") - .map(([key, value]) => - ts.factory.createPropertyAssignment( - IdentifierFactory.identifier(key), - LiteralFactory.write(value), - ), - ) - : []), - ...(props.schema - ? [] - : [ - ts.factory.createSpreadAssignment( - ts.factory.createIdentifier("_schema"), - ), - ]), - ts.factory.createPropertyAssignment( - "element", - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - props.decode(props.array.value), - ), - ), - ], - true, - ), - ], - ); - if (props.recursive === false) return call; - return ts.factory.createConditionalExpression( - ts.factory.createGreaterThanEquals( - ExpressionFactory.number(5), - ts.factory.createIdentifier("_depth"), - ), - undefined, - call, - undefined, - ts.factory.createArrayLiteralExpression([]), - ); - }; - - export const tuple = (props: { - decode: Decoder; - elements: MetadataSchema[]; - }): ts.ArrayLiteralExpression => - ts.factory.createArrayLiteralExpression( - props.elements.map((elem) => props.decode(elem.rest ?? elem)), - true, - ); - - export const object = (props: { - decode: Decoder; - object: MetadataObjectType; - }): ts.ConciseBody => { - if (props.object.properties.length === 0) return LiteralFactory.write({}); - - // LIST UP PROPERTIES - const regular = props.object.properties.filter((p) => - p.key.isSoleLiteral(), - ); - const dynamic = props.object.properties.filter( - (p) => !p.key.isSoleLiteral(), - ); - - return ts.factory.createObjectLiteralExpression( - [ - ...regular.map((p) => { - const str: string = p.key.getSoleLiteral()!; - return ts.factory.createPropertyAssignment( - NamingConvention.variable(str) - ? str - : ts.factory.createStringLiteral(str), - props.decode(p.value), - ); - }), - ...dynamic.map((property) => - ts.factory.createSpreadAssignment( - dynamicProperty({ - decode: props.decode, - property: property, - }), - ), - ), - ], - true, - ); - }; - - const dynamicProperty = (props: { - decode: Decoder; - property: MetadataProperty; - }) => { - const tuple: MetadataTuple = MetadataTuple.create({ - type: MetadataTupleType.create({ - name: `[${props.property.key.getName()}, ${props.property.value.getName()}]`, - elements: [props.property.key, props.property.value], - index: null, - recursive: false, - nullables: [false], - }), - tags: [], - }); - const array: MetadataArray = MetadataArray.create({ - type: MetadataArrayType.create({ - name: `Array<[${props.property.key.getName()}, ${props.property.value.getName()}]>`, - value: MetadataSchema.create({ - ...MetadataSchema.initialize(), - tuples: [tuple], - }), - nullables: [false], - recursive: false, - index: null, - }), - tags: [[]], - }); - return ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createIdentifier("Object"), - "fromEntries", - ), - undefined, - [ - props.decode( - MetadataSchema.create({ - ...MetadataSchema.initialize(), - arrays: [array], - }), - ), - ], - ); - }; -} diff --git a/packages/core/src/programmers/helpers/StringifyJoinder.ts b/packages/core/src/programmers/helpers/StringifyJoinder.ts deleted file mode 100644 index 92fc22e7279..00000000000 --- a/packages/core/src/programmers/helpers/StringifyJoinder.ts +++ /dev/null @@ -1,113 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { TemplateFactory } from "../../factories/TemplateFactory"; -import { stringify_dynamic_properties } from "../iterate/stringify_dynamic_properties"; -import { stringify_regular_properties } from "../iterate/stringify_regular_properties"; -import { IExpressionEntry } from "./IExpressionEntry"; - -export namespace StringifyJoiner { - export const object = (props: { - context: ITypiaContext; - entries: IExpressionEntry[]; - }): ts.Expression => { - // CHECK AND SORT ENTRIES - if (props.entries.length === 0) return ts.factory.createStringLiteral("{}"); - - // PROPERTIES - const regular: IExpressionEntry[] = props.entries.filter( - (entry) => entry.key.isSoleLiteral(), - ); - const dynamic: IExpressionEntry[] = props.entries.filter( - (entry) => !entry.key.isSoleLiteral(), - ); - const expressions: ts.Expression[] = [ - ...stringify_regular_properties({ - regular, - dynamic, - }), - ...(dynamic.length - ? [ - stringify_dynamic_properties( - dynamic, - regular.map((r) => r.key.getSoleLiteral()!), - ), - ] - : []), - ]; - - // POP LAST COMMA, IF REQUIRED - const filtered: ts.Expression[] = - (regular.length && - regular[regular.length - 1]!.meta.isRequired() && - dynamic.length === 0) || - (regular.length === 0 && dynamic.length) - ? expressions - : [ - ts.factory.createCallExpression( - props.context.importer.internal("jsonStringifyTail"), - undefined, - [TemplateFactory.generate(expressions)], - ), - ]; - - // RETURNS WITH OBJECT BRACKET - return TemplateFactory.generate([ - ts.factory.createStringLiteral(`{`), - ...filtered, - ts.factory.createStringLiteral(`}`), - ]); - }; - - export const array = (props: { - input: ts.Expression; - arrow: ts.ArrowFunction; - }): ts.Expression => - TemplateFactory.generate([ - ts.factory.createStringLiteral(`[`), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "map"), - undefined, - [props.arrow], - ), - ts.factory.createIdentifier("join"), - ), - undefined, - [ts.factory.createStringLiteral(`,`)], - ), - ts.factory.createStringLiteral(`]`), - ]); - - export const tuple = (props: { - elements: ts.Expression[]; - rest: ts.Expression | null; - }): ts.Expression => { - if (props.elements.length === 0) - return ts.factory.createStringLiteral("[]"); - if ( - props.rest === null && - props.elements.every((child) => ts.isStringLiteral(child)) - ) - return ts.factory.createStringLiteral( - "[" + - props.elements - .map((child) => (child as ts.StringLiteral).text) - .join(",") + - "]", - ); - - const expressions: ts.Expression[] = [ts.factory.createStringLiteral(`[`)]; - props.elements.forEach((child, i) => { - expressions.push(child); - if (i !== props.elements.length - 1) - expressions.push(ts.factory.createStringLiteral(`,`)); - }); - if (props.rest !== null) expressions.push(props.rest); - - expressions.push(ts.factory.createStringLiteral(`]`)); - return TemplateFactory.generate(expressions); - }; -} diff --git a/packages/core/src/programmers/helpers/StringifyPredicator.ts b/packages/core/src/programmers/helpers/StringifyPredicator.ts deleted file mode 100644 index a6b5c1d7e55..00000000000 --- a/packages/core/src/programmers/helpers/StringifyPredicator.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace StringifyPredicator { - export const require_escape = (value: string): boolean => - value.split("").some((ch) => ESCAPED.some((escaped) => escaped === ch)); - - export const undefindable = (metadata: MetadataSchema): boolean => - metadata.isRequired() === false || - (metadata.escaped !== null && - metadata.escaped.returns.isRequired() === false); - - const ESCAPED = ['"', "\\", "\b", "\f", "\n", "\n", "\r", "\t"]; -} diff --git a/packages/core/src/programmers/helpers/UnionExplorer.ts b/packages/core/src/programmers/helpers/UnionExplorer.ts deleted file mode 100644 index d2f81032c3b..00000000000 --- a/packages/core/src/programmers/helpers/UnionExplorer.ts +++ /dev/null @@ -1,377 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataMap } from "../../schemas/metadata/MetadataMap"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataSet } from "../../schemas/metadata/MetadataSet"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { check_union_array_like } from "../iterate/check_union_array_like"; -import { UnionPredicator } from "./UnionPredicator"; - -export namespace UnionExplorer { - export interface Decoder { - (props: { - input: ts.Expression; - definition: T; - explore: FeatureProgrammer.IExplore; - }): ts.Expression; - } - export type ObjectCombiner = Decoder; - - /* ----------------------------------------------------------- - OBJECT - ----------------------------------------------------------- */ - export const object = (props: { - config: FeatureProgrammer.IConfig; - level?: number; - objects: MetadataObjectType[]; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - // BREAKER - if (props.objects.length === 1) - return props.config.objector.decoder({ - input: props.input, - object: props.objects[0]!, - explore: props.explore, - }); - - const expected: string = `(${props.objects.map((t) => t.name).join(" | ")})`; - - // POSSIBLE TO SPECIALIZE? - const specList = UnionPredicator.object(props.objects); - if (specList.length === 0) { - const condition: ts.Expression = props.config.objector.unionizer({ - objects: props.objects, - input: props.input, - explore: { - ...props.explore, - tracable: false, - }, - }); - return props.config.objector.full - ? props.config.objector.full({ - condition, - expected, - explore: props.explore, - input: props.input, - }) - : condition; - } - const remained: MetadataObjectType[] = props.objects.filter( - (t) => specList.find((s) => s.object === t) === undefined, - ); - - // DO SPECIALIZE - const condition: ts.IfStatement = specList - .filter((spec) => spec.property.key.getSoleLiteral() !== null) - .map((spec, i, array) => { - const key: string = spec.property.key.getSoleLiteral()!; - const accessor: ts.Expression = IdentifierFactory.access( - props.input, - key, - ); - const pred: ts.Expression = spec.neighbor - ? props.config.objector.checker({ - input: accessor, - metadata: spec.property.value, - explore: { - ...props.explore, - tracable: false, - postfix: IdentifierFactory.postfix(key), - }, - }) - : (props.config.objector.required || ((exp) => exp))( - ExpressionFactory.isRequired(accessor), - ); - return ts.factory.createIfStatement( - (props.config.objector.is || ((exp) => exp))(pred), - ts.factory.createReturnStatement( - props.config.objector.decoder({ - object: spec.object, - input: props.input, - explore: props.explore, - }), - ), - i === array.length - 1 - ? remained.length - ? ts.factory.createReturnStatement( - object({ - config: props.config, - level: (props.level ?? 0) + 1, - input: props.input, - objects: remained, - explore: props.explore, - }), - ) - : props.config.objector.failure({ - input: props.input, - explore: props.explore, - expected, - }) - : undefined, - ); - }) - .reverse() - .reduce((a, b) => - ts.factory.createIfStatement(b.expression, b.thenStatement, a), - ); - - // RETURNS WITH CONDITIONS - return ts.factory.createCallExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - ts.factory.createBlock([condition], true), - ), - undefined, - undefined, - ); - }; - - /* ----------------------------------------------------------- - ARRAY LIKE - ----------------------------------------------------------- */ - export const tuple = (props: { - config: check_union_array_like.IConfig; - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - tuples: MetadataTuple[]; - explore: FeatureProgrammer.IExplore; - }) => - check_union_array_like({ - config: props.config, - accessor: { - transform: (x) => x, - element: (x) => x, - size: null!, - front: (input) => input, - array: (input) => input, - name: (t) => t.type.name, - }, - parameters: props.parameters, - input: props.input, - definitions: props.tuples, - explore: props.explore, - }); - export namespace tuple { - export type IConfig = check_union_array_like.IConfig< - MetadataTuple, - MetadataTuple - >; - } - - export const array = (props: { - config: array.IConfig; - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - arrays: MetadataArray[]; - explore: FeatureProgrammer.IExplore; - }) => - check_union_array_like({ - config: props.config, - accessor: { - transform: (x) => x, - element: (x) => x.type.value, - size: (input) => IdentifierFactory.access(input, "length"), - front: (input) => ts.factory.createElementAccessExpression(input, 0), - array: (input) => input, - name: (t) => t.type.name, - }, - parameters: props.parameters, - input: props.input, - definitions: props.arrays, - explore: props.explore, - }); - export namespace array { - export type IConfig = check_union_array_like.IConfig< - MetadataArray, - MetadataSchema - >; - } - - export const array_or_tuple = (props: { - config: array_or_tuple.IConfig; - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - definitions: (MetadataArray | MetadataTuple)[]; - explore: FeatureProgrammer.IExplore; - }) => - check_union_array_like< - MetadataArray | MetadataTuple, - MetadataArray | MetadataTuple, - MetadataSchema | MetadataTuple - >({ - config: props.config, - accessor: { - transform: (x) => x, - element: (x) => (x instanceof MetadataArray ? x.type.value : x), - size: (input) => IdentifierFactory.access(input, "length"), - front: (input) => ts.factory.createElementAccessExpression(input, 0), - array: (input) => input, - name: (m) => m.type.name, - }, - parameters: props.parameters, - input: props.input, - definitions: props.definitions, - explore: props.explore, - }); - export namespace array_or_tuple { - export type IConfig = check_union_array_like.IConfig< - MetadataArray | MetadataTuple, - MetadataSchema | MetadataTuple - >; - } - - export const set = (props: { - config: set.IConfig; - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - sets: MetadataSet[]; - explore: FeatureProgrammer.IExplore; - }) => - check_union_array_like({ - config: props.config, - accessor: { - transform: (value: MetadataSchema) => - MetadataArray.create({ - tags: [], - type: MetadataArrayType.create({ - name: `Set<${value.getName()}>`, - index: null, - recursive: false, - nullables: [], - value, - }), - }), - element: (array) => array.type.value, - size: (input) => IdentifierFactory.access(input, "size"), - front: (input) => - IdentifierFactory.access( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - IdentifierFactory.access(input, "values"), - undefined, - undefined, - ), - "next", - ), - undefined, - undefined, - ), - "value", - ), - array: (input) => - ts.factory.createArrayLiteralExpression( - [ts.factory.createSpreadElement(input)], - false, - ), - name: (_m, e) => `Set<${e.getName()}>`, - }, - parameters: props.parameters, - input: props.input, - definitions: props.sets.map((s) => s.value), - explore: props.explore, - }); - export namespace set { - export type IConfig = check_union_array_like.IConfig< - MetadataArray, - MetadataSchema - >; - } - - export const map = (props: { - config: map.IConfig; - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - maps: MetadataMap[]; - explore: FeatureProgrammer.IExplore; - }) => - check_union_array_like< - MetadataMap, - MetadataArray, - [MetadataSchema, MetadataSchema] - >({ - config: props.config, - accessor: { - element: (array) => - array.type.value.tuples[0]!.type.elements as [ - MetadataSchema, - MetadataSchema, - ], - size: (input) => IdentifierFactory.access(input, "size"), - front: (input) => - IdentifierFactory.access( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - IdentifierFactory.access(input, "entries"), - undefined, - undefined, - ), - "next", - ), - undefined, - undefined, - ), - "value", - ), - array: (input) => - ts.factory.createArrayLiteralExpression( - [ts.factory.createSpreadElement(input)], - false, - ), - name: (_m, [k, v]) => `Map<${k.getName()}, ${v.getName()}>`, - transform: (m: MetadataMap) => - MetadataArray.create({ - tags: [], - type: MetadataArrayType.create({ - name: `Map<${m.key.getName()}, ${m.value.getName()}>`, - index: null, - recursive: false, - nullables: [], - value: MetadataSchema.create({ - ...MetadataSchema.initialize(), - tuples: [ - (() => { - const tuple = MetadataTuple.create({ - tags: [], - type: MetadataTupleType.create({ - name: `[${m.key.getName()}, ${m.value.getName()}]`, - index: null, - recursive: false, - nullables: [], - elements: [m.key, m.value], - }), - }); - tuple.type.of_map = true; - return tuple; - })(), - ], - }), - }), - }), - }, - parameters: props.parameters, - input: props.input, - definitions: props.maps, - explore: props.explore, - }); - - export namespace map { - export type IConfig = check_union_array_like.IConfig< - MetadataArray, - [MetadataSchema, MetadataSchema] - >; - } -} diff --git a/packages/core/src/programmers/helpers/UnionPredicator.ts b/packages/core/src/programmers/helpers/UnionPredicator.ts deleted file mode 100644 index 6910cb5668f..00000000000 --- a/packages/core/src/programmers/helpers/UnionPredicator.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { ArrayUtil, MapUtil } from "@typia/utils"; - -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace UnionPredicator { - export interface ISpecialized { - index: number; - object: MetadataObjectType; - property: MetadataProperty; - neighbor: boolean; - } - - export const object = ( - objects: MetadataObjectType[], - ): Array => { - // PROPERTY MATRIX - const matrix: Map> = new Map(); - for (const obj of objects) - for (const prop of obj.properties) { - const key: string | null = prop.key.getSoleLiteral(); - if (key !== null) - MapUtil.take(matrix, key, () => - ArrayUtil.repeat(objects.length, () => null), - ); - } - objects.forEach((obj, i) => { - for (const prop of obj.properties) { - const key: string | null = prop.key.getSoleLiteral(); - if (key !== null) matrix.get(key)![i] = prop; - } - }); - - // EXPLORE SPECIALIZERS - const output: ISpecialized[] = []; - objects.forEach((obj, i) => { - const children: ISpecializedProperty[] = []; - obj.properties.forEach((prop) => { - // MUST BE REQUIRED - if (prop.value.isRequired() === false) return; - const key: string | null = prop.key.getSoleLiteral(); - if (key === null) return; - - // FIND NEIGHBORHOOD PROPERTIES - const neighbors: MetadataProperty[] = matrix - .get(key)! - .filter((oppo, k) => i !== k && oppo !== null) as MetadataProperty[]; - - // NO NEIGHBORHOOD - const unique: boolean = - neighbors.length === 0 || - neighbors.every( - (n) => !MetadataSchema.intersects(prop.value, n.value), - ); - if (unique === true) - children.push({ - property: prop, - neighbor: neighbors.length !== 0, - }); - }); - if (children.length === 0) return; - - const top: ISpecializedProperty = - children.find((child) => child.property.value.isConstant()) || - children[0]!; - output.push({ - index: i, - object: obj, - ...top, - }); - }); - return output; - }; -} - -interface ISpecializedProperty { - property: MetadataProperty; - neighbor: boolean; -} diff --git a/packages/core/src/programmers/helpers/disable_function_programmer_declare.ts b/packages/core/src/programmers/helpers/disable_function_programmer_declare.ts deleted file mode 100644 index d0bffb94c5d..00000000000 --- a/packages/core/src/programmers/helpers/disable_function_programmer_declare.ts +++ /dev/null @@ -1,32 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { FunctionProgrammer } from "./FunctionProgrammer"; - -export const disable_function_programmer_declare = ( - functor: FunctionProgrammer, -): FunctionProgrammer => disable(functor) as FunctionProgrammer; - -const disable = ( - functor: FunctionProgrammer, -): MethodOnly => ({ - method: functor.method, - useLocal: (name: string): string => functor.useLocal(name), - hasLocal: (name: string): boolean => functor.hasLocal(name), - declare: (): ts.Statement[] => [], - declareUnions: (): ts.Statement[] => [], - increment: (): number => functor.increment(), - emplaceUnion: ( - prefix: string, - name: string, - factory: () => ts.ArrowFunction, - ): string => functor.emplaceUnion(prefix, name, factory), - emplaceVariable: (key, value) => functor.emplaceVariable(key, value), -}); - -type MethodOnly = { - [P in keyof T]: T[P] extends Function - ? T[P] - : P extends "method" - ? T[P] - : never; -}; diff --git a/packages/core/src/programmers/http/HttpAssertFormDataProgrammer.ts b/packages/core/src/programmers/http/HttpAssertFormDataProgrammer.ts deleted file mode 100644 index 56096016b80..00000000000 --- a/packages/core/src/programmers/http/HttpAssertFormDataProgrammer.ts +++ /dev/null @@ -1,97 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpFormDataProgrammer } from "./HttpFormDataProgrammer"; - -export namespace HttpAssertFormDataProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: true, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - HttpFormDataProgrammer.decompose(props); - return { - functions: { - ...assert.functions, - ...decode.functions, - }, - statements: [ - ...assert.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - decode.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - AssertProgrammer.Guardian.identifier(), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpAssertHeadersProgrammer.ts b/packages/core/src/programmers/http/HttpAssertHeadersProgrammer.ts deleted file mode 100644 index 8278ce2bbe3..00000000000 --- a/packages/core/src/programmers/http/HttpAssertHeadersProgrammer.ts +++ /dev/null @@ -1,97 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpHeadersProgrammer } from "./HttpHeadersProgrammer"; - -export namespace HttpAssertHeadersProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - HttpHeadersProgrammer.decompose(props); - return { - functions: { - ...assert.functions, - ...decode.functions, - }, - statements: [ - ...assert.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - decode.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - AssertProgrammer.Guardian.identifier(), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpAssertQueryProgrammer.ts b/packages/core/src/programmers/http/HttpAssertQueryProgrammer.ts deleted file mode 100644 index af20e870ab0..00000000000 --- a/packages/core/src/programmers/http/HttpAssertQueryProgrammer.ts +++ /dev/null @@ -1,103 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpQueryProgrammer } from "./HttpQueryProgrammer"; - -export namespace HttpAssertQueryProgrammer { - export interface IProps extends IProgrammerProps { - allowOptional?: boolean; - } - - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - allowOptional: boolean; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - HttpQueryProgrammer.decompose(props); - return { - functions: { - ...assert.functions, - ...decode.functions, - }, - statements: [ - ...assert.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - decode.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - AssertProgrammer.Guardian.identifier(), - ], - ), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - allowOptional: !!props.allowOptional, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpFormDataProgrammer.ts b/packages/core/src/programmers/http/HttpFormDataProgrammer.ts deleted file mode 100644 index 88a3a4045b3..00000000000 --- a/packages/core/src/programmers/http/HttpFormDataProgrammer.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { Atomic } from "@typia/interface"; -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { HttpMetadataUtil } from "../helpers/HttpMetadataUtil"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace HttpFormDataProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - // ANALYZE TYPE - const collection: MetadataCollection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate, - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - - // DO TRANSFORM - const object: MetadataObjectType = result.data.objects[0]!.type; - const statements: ts.Statement[] = decode_object({ - context: props.context, - object, - }); - return { - functions: {}, - statements: [], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode("FormData"), - ), - ], - props.context.importer.type({ - file: "typia", - name: "Resolved", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock(statements, true), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const errors: string[] = []; - const insert = (msg: string) => errors.push(msg); - - if (props.explore.top === true) { - // TOP MUST BE ONLY OBJECT - if (props.metadata.objects.length !== 1 || props.metadata.bucket() !== 1) - insert("only one object type is allowed."); - if (props.metadata.nullable === true) - insert("formdata parameters cannot be null."); - if (props.metadata.isRequired() === false) - insert("formdata parameters cannot be undefined."); - } else if ( - props.explore.nested !== null && - props.explore.nested instanceof MetadataArrayType - ) { - //---- - // ARRAY - //---- - const atomics = HttpMetadataUtil.atomics(props.metadata); - const expected: number = - props.metadata.atomics.length + - props.metadata.templates.length + - props.metadata.constants - .map((c) => c.values.length) - .reduce((a, b) => a + b, 0) + - props.metadata.natives.filter( - (native) => native.name === "Blob" || native.name === "File", - ).length; - if (atomics.size > 1) insert("union type is not allowed in array."); - if (props.metadata.size() !== expected) - insert( - "only atomic, constant or blob (file) types are allowed in array.", - ); - } else if (props.explore.object && props.explore.property !== null) { - //---- - // COMMON - //---- - // PROPERTY MUST BE SOLE - if (typeof props.explore.property === "object") - insert("dynamic property is not allowed."); - // DO NOT ALLOW TUPLE TYPE - if (props.metadata.tuples.length) insert("tuple type is not allowed."); - // DO NOT ALLOW UNION TYPE - if (HttpMetadataUtil.isUnion(props.metadata)) - insert("union type is not allowed."); - // DO NOT ALLOW NESTED OBJECT - if ( - props.metadata.objects.length || - props.metadata.sets.length || - props.metadata.maps.length || - props.metadata.natives.filter( - (native) => native.name !== "Blob" && native.name !== "File", - ).length - ) - insert("nested object type is not allowed."); - } - return errors; - }; - - const decode_object = (props: { - context: ITypiaContext; - object: MetadataObjectType; - }): ts.Statement[] => { - // const input: ts.Identifier = ts.factory.createIdentifier("input"); - const output: ts.Identifier = ts.factory.createIdentifier("output"); - return [ - StatementFactory.constant({ - name: "output", - value: ts.factory.createObjectLiteralExpression( - props.object.properties.map((p) => - decode_regular_property({ - context: props.context, - property: p, - }), - ), - true, - ), - }), - ts.factory.createReturnStatement( - ts.factory.createAsExpression(output, TypeFactory.keyword("any")), - ), - ]; - }; - - const decode_regular_property = (props: { - context: ITypiaContext; - property: MetadataProperty; - }): ts.PropertyAssignment => { - const key: string = props.property.key.constants[0]!.values[0]! - .value as string; - const value: MetadataSchema = props.property.value; - - const [type, isArray]: [Atomic.Literal | "blob" | "file", boolean] = value - .atomics.length - ? [value.atomics[0]!.type, false] - : value.constants.length - ? [value.constants[0]!.type, false] - : value.templates.length - ? ["string", false] - : value.natives.some((native) => native.name === "Blob") - ? ["blob", false] - : value.natives.some((native) => native.name === "File") - ? ["file", false] - : (() => { - const meta = - value.arrays[0]?.type.value ?? - value.tuples[0]!.type.elements[0]!; - return meta.atomics.length - ? [meta.atomics[0]!.type, true] - : meta.templates.length - ? ["string", true] - : meta.natives.some((native) => native.name === "Blob") - ? ["blob", true] - : meta.natives.some((native) => native.name === "File") - ? ["file", true] - : [meta.constants[0]!.type, true]; - })(); - return ts.factory.createPropertyAssignment( - NamingConvention.variable(key) - ? key - : ts.factory.createStringLiteral(key), - isArray - ? decode_array({ - context: props.context, - metadata: value, - input: ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - ts.factory.createIdentifier("input.getAll"), - undefined, - [ts.factory.createStringLiteral(key)], - ), - "map", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("elem")], - undefined, - undefined, - decode_value({ - context: props.context, - type, - coalesce: false, - input: ts.factory.createIdentifier("elem"), - }), - ), - ], - ), - }) - : decode_value({ - context: props.context, - type, - coalesce: value.nullable === false && value.isRequired() === false, - input: ts.factory.createCallExpression( - ts.factory.createIdentifier("input.get"), - undefined, - [ts.factory.createStringLiteral(key)], - ), - }), - ); - }; - - const decode_value = (props: { - context: ITypiaContext; - type: Atomic.Literal | "blob" | "file"; - coalesce: boolean; - input: ts.Expression; - }) => { - const call = ts.factory.createCallExpression( - props.context.importer.internal( - `httpFormDataRead${NamingConvention.capitalize(props.type)}`, - ), - undefined, - [props.input], - ); - return props.coalesce - ? ts.factory.createBinaryExpression( - call, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), - ts.factory.createIdentifier("undefined"), - ) - : call; - }; - - const decode_array = (props: { - context: ITypiaContext; - metadata: MetadataSchema; - input: ts.Expression; - }): ts.Expression => - props.metadata.nullable || props.metadata.isRequired() === false - ? ts.factory.createCallExpression( - props.context.importer.internal("httpFormDataReadArray"), - undefined, - [ - props.input, - props.metadata.nullable - ? ts.factory.createNull() - : ts.factory.createIdentifier("undefined"), - ], - ) - : props.input; -} diff --git a/packages/core/src/programmers/http/HttpHeadersProgrammer.ts b/packages/core/src/programmers/http/HttpHeadersProgrammer.ts deleted file mode 100644 index 1aebd89cfba..00000000000 --- a/packages/core/src/programmers/http/HttpHeadersProgrammer.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { Atomic } from "@typia/interface"; -import { MapUtil, NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { HttpMetadataUtil } from "../helpers/HttpMetadataUtil"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace HttpHeadersProgrammer { - export const INPUT_TYPE = "Record"; - - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - // ANALYZE TYPE - const collection: MetadataCollection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate, - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - - // DO TRANSFORM - const object: MetadataObjectType = result.data.objects[0]!.type; - const statements: ts.Statement[] = decode_object({ - context: props.context, - object, - }); - return { - functions: {}, - statements: [], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode(INPUT_TYPE), - ), - ], - props.context.importer.type({ - file: "typia", - name: "Resolved", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock(statements, true), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const errors: string[] = []; - const insert = (msg: string) => errors.push(msg); - - if (props.explore.top === true) { - // TOP MUST BE ONLY OBJECT - if (props.metadata.objects.length !== 1 || props.metadata.bucket() !== 1) - insert("only one object type is allowed."); - if (props.metadata.nullable === true) insert("headers cannot be null."); - if (props.metadata.isRequired() === false) - insert("headers cannot be null."); - } else if ( - props.explore.nested !== null && - props.explore.nested instanceof MetadataArrayType - ) { - //---- - // ARRAY - //---- - const atomics = HttpMetadataUtil.atomics(props.metadata); - const expected: number = - props.metadata.atomics.length + - props.metadata.templates.length + - props.metadata.constants - .map((c) => c.values.length) - .reduce((a, b) => a + b, 0); - if (atomics.size > 1) insert("union type is not allowed in array."); - if (props.metadata.size() !== expected) - insert("only atomic or constant types are allowed in array."); - if (props.metadata.nullable === true) - insert("nullable type is not allowed in array."); - if (props.metadata.isRequired() === false) - insert("optional type is not allowed in array."); - } else if (props.explore.object && props.explore.property !== null) { - //---- - // COMMON - //---- - // PROPERTY MUST BE SOLE - if (typeof props.explore.property === "object") - insert("dynamic property is not allowed."); - // DO NOT ALLOW TUPLE TYPE - if (props.metadata.tuples.length) insert("tuple type is not allowed."); - // DO NOT ALLOW UNION TYPE - if (HttpMetadataUtil.isUnion(props.metadata)) - insert("union type is not allowed."); - // DO NOT ALLOW NESTED OBJECT - if ( - props.metadata.objects.length || - props.metadata.sets.length || - props.metadata.maps.length || - props.metadata.natives.length - ) - insert("nested object type is not allowed."); - // DO NOT ALLOW NULLABLE - if (props.metadata.nullable === true) - insert("nullable type is not allowed."); - - //---- - // SPECIAL KEY NAMES - //---- - const isArray: boolean = - props.metadata.arrays.length >= 1 || props.metadata.tuples.length >= 1; - // SET-COOKIE MUST BE ARRAY - if ( - typeof props.explore.property === "string" && - props.explore.property.toLowerCase() === "set-cookie" && - isArray === false - ) - insert(`${props.explore.property} property must be array.`); - // MUST BE SINGULAR CASE - if ( - typeof props.explore.property === "string" && - SINGULAR.has(props.explore.property.toLowerCase()) && - isArray === true - ) - insert("property cannot be array."); - } else if (props.explore.object && props.explore.property === null) { - const counter: Map> = new Map(); - for (const prop of props.explore.object.properties) { - const key: string | null = prop.key.getSoleLiteral(); - if (key === null) continue; - - MapUtil.take(counter, key.toLowerCase(), () => new Set()).add(key); - } - for (const [key, set] of counter) - if (set.size > 1) - insert( - `duplicated keys when converting to lowercase letters: [${[ - ...set, - ].join(", ")}] -> ${key}`, - ); - } - return errors; - }; - - const decode_object = (props: { - context: ITypiaContext; - object: MetadataObjectType; - }): ts.Statement[] => { - const output: ts.Identifier = ts.factory.createIdentifier("output"); - const optionals: string[] = []; - return [ - StatementFactory.constant({ - name: "output", - value: ts.factory.createObjectLiteralExpression( - props.object.properties.map((p) => { - if ( - !p.value.isRequired() && - p.value.arrays.length + p.value.tuples.length > 0 - ) - optionals.push(p.key.constants[0]!.values[0]!.value as string); - return decode_regular_property({ - context: props.context, - property: p, - }); - }), - true, - ), - }), - ...optionals.map((key) => { - const access = IdentifierFactory.access(output, key); - return ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ExpressionFactory.number(0), - IdentifierFactory.access(access, "length"), - ), - ts.factory.createExpressionStatement( - ts.factory.createDeleteExpression(access), - ), - ); - }), - ts.factory.createReturnStatement( - ts.factory.createAsExpression(output, TypeFactory.keyword("any")), - ), - ]; - }; - - const decode_regular_property = (props: { - context: ITypiaContext; - property: MetadataProperty; - }): ts.PropertyAssignment => { - const key: string = props.property.key.constants[0]!.values[0]! - .value as string; - const value: MetadataSchema = props.property.value; - - const [type, isArray]: [Atomic.Literal, boolean] = value.atomics.length - ? [value.atomics[0]!.type, false] - : value.constants.length - ? [value.constants[0]!.type, false] - : value.templates.length - ? ["string", false] - : (() => { - const meta: MetadataSchema = - value.arrays[0]?.type.value ?? - value.tuples[0]!.type.elements[0]!; - return meta.atomics.length - ? [meta.atomics[0]!.type, true] - : meta.templates.length - ? ["string", true] - : [meta.constants[0]!.type, true]; - })(); - const input = IdentifierFactory.access( - ts.factory.createIdentifier("input"), - key.toLowerCase(), - ); - - return ts.factory.createPropertyAssignment( - NamingConvention.variable(key) - ? key - : ts.factory.createStringLiteral(key), - isArray - ? key === "set-cookie" - ? input - : decode_array({ - context: props.context, - type, - key, - value, - input, - }) - : decode_value({ - context: props.context, - type, - input, - }), - ); - }; - - const decode_value = (props: { - context: ITypiaContext; - type: Atomic.Literal; - input: ts.Expression; - }) => - props.type === "string" - ? props.input - : ts.factory.createCallExpression( - props.context.importer.internal( - `httpHeaderRead${NamingConvention.capitalize(props.type)}`, - ), - undefined, - [props.input], - ); - - const decode_array = (props: { - context: ITypiaContext; - type: Atomic.Literal; - key: string; - value: MetadataSchema; - input: ts.Expression; - }) => { - const reader = - props.type === "string" - ? ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("str")], - undefined, - undefined, - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createIdentifier("str"), - "trim", - ), - undefined, - undefined, - ), - ) - : props.context.importer.internal( - `httpHeaderRead${NamingConvention.capitalize(props.type)}`, - ); - const split: ts.CallChain = ts.factory.createCallChain( - ts.factory.createPropertyAccessChain( - ts.factory.createCallChain( - ts.factory.createPropertyAccessChain( - props.input, - ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), - ts.factory.createIdentifier("split"), - ), - undefined, - undefined, - [ - ts.factory.createStringLiteral( - props.key === "cookie" ? "; " : ", ", - ), - ], - ), - ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), - ts.factory.createIdentifier("map"), - ), - undefined, - undefined, - [reader], - ); - return ts.factory.createConditionalExpression( - ExpressionFactory.isArray(props.input), - undefined, - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "map"), - undefined, - [reader], - ), - undefined, - props.value.isRequired() === false - ? split - : ts.factory.createBinaryExpression( - split, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), - ts.factory.createArrayLiteralExpression([], false), - ), - ); - }; -} - -const SINGULAR: Set = new Set([ - "age", - "authorization", - "content-length", - "content-type", - "etag", - "expires", - "from", - "host", - "if-modified-since", - "if-unmodified-since", - "last-modified", - "location", - "max-forwards", - "proxy-authorization", - "referer", - "retry-after", - "server", - "user-agent", -]); diff --git a/packages/core/src/programmers/http/HttpIsFormDataProgrammer.ts b/packages/core/src/programmers/http/HttpIsFormDataProgrammer.ts deleted file mode 100644 index d341dbf68a5..00000000000 --- a/packages/core/src/programmers/http/HttpIsFormDataProgrammer.ts +++ /dev/null @@ -1,106 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpFormDataProgrammer } from "./HttpFormDataProgrammer"; - -export namespace HttpIsFormDataProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - HttpFormDataProgrammer.decompose(props); - return { - functions: { - ...is.functions, - ...decode.functions, - }, - statements: [ - ...is.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - ts.factory.createUnionTypeNode([ - decode.arrow.type ?? TypeFactory.keyword("any"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "value", - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("value")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("value"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpIsHeadersProgrammer.ts b/packages/core/src/programmers/http/HttpIsHeadersProgrammer.ts deleted file mode 100644 index 03fc33d9f82..00000000000 --- a/packages/core/src/programmers/http/HttpIsHeadersProgrammer.ts +++ /dev/null @@ -1,106 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpHeadersProgrammer } from "./HttpHeadersProgrammer"; - -export namespace HttpIsHeadersProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: true, - }, - }, - config: { - equals: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - HttpHeadersProgrammer.decompose(props); - return { - functions: { - ...is.functions, - ...decode.functions, - }, - statements: [ - ...is.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - ts.factory.createUnionTypeNode([ - decode.arrow.type ?? TypeFactory.keyword("any"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "value", - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("value")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("value"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpIsQueryProgrammer.ts b/packages/core/src/programmers/http/HttpIsQueryProgrammer.ts deleted file mode 100644 index f45bb980620..00000000000 --- a/packages/core/src/programmers/http/HttpIsQueryProgrammer.ts +++ /dev/null @@ -1,112 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpQueryProgrammer } from "./HttpQueryProgrammer"; - -export namespace HttpIsQueryProgrammer { - export interface IProps extends IProgrammerProps { - allowOptional?: boolean; - } - - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - allowOptional: boolean; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: true, - }, - }, - config: { - equals: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - HttpQueryProgrammer.decompose(props); - return { - functions: { - ...is.functions, - ...decode.functions, - }, - statements: [ - ...is.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - ts.factory.createUnionTypeNode([ - decode.arrow.type ?? TypeFactory.keyword("any"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "value", - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("value")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("value"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - allowOptional: !!props.allowOptional, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpParameterProgrammer.ts b/packages/core/src/programmers/http/HttpParameterProgrammer.ts deleted file mode 100644 index d554b6efd6a..00000000000 --- a/packages/core/src/programmers/http/HttpParameterProgrammer.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { TransformerError } from "../../context/TransformerError"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { HttpMetadataUtil } from "../helpers/HttpMetadataUtil"; - -export namespace HttpParameterProgrammer { - export const write = (props: IProgrammerProps): ts.ArrowFunction => { - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate, - }, - components: new MetadataCollection(), - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.modulo.getText(), - errors: result.errors, - }); - - const atomic = [...HttpMetadataUtil.atomics(result.data)][0]!; - const block: ts.Statement[] = [ - StatementFactory.constant({ - name: "assert", - value: AssertProgrammer.write({ - ...props, - context: { - ...props.context, - options: { - numeric: true, - }, - }, - config: { - equals: false, - guard: false, - }, - }), - }), - StatementFactory.constant({ - name: "value", - value: ts.factory.createCallExpression( - props.context.importer.internal( - `httpParameterRead${NamingConvention.capitalize(atomic)}`, - ), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("assert"), - undefined, - [ts.factory.createIdentifier("value")], - ), - ), - ]; - - return ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode("string"), - ), - ], - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - undefined, - ts.factory.createBlock(block, true), - ); - }; - - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const errors: string[] = []; - const insert = (msg: string) => errors.push(msg); - - if (props.metadata.any) insert("do not allow any type"); - if (props.metadata.isRequired() === false) - insert("do not allow undefindable type"); - - const atomics = HttpMetadataUtil.atomics(props.metadata); - const expected: number = - props.metadata.atomics.length + - props.metadata.templates.length + - props.metadata.constants - .map((c) => c.values.length) - .reduce((a, b) => a + b, 0); - if (props.metadata.size() !== expected || atomics.size === 0) - insert("only atomic or constant types are allowed"); - if (atomics.size > 1) insert("do not allow union type"); - - return errors; - }; -} diff --git a/packages/core/src/programmers/http/HttpQueryProgrammer.ts b/packages/core/src/programmers/http/HttpQueryProgrammer.ts deleted file mode 100644 index 208701eaa5f..00000000000 --- a/packages/core/src/programmers/http/HttpQueryProgrammer.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { Atomic } from "@typia/interface"; -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { HttpMetadataUtil } from "../helpers/HttpMetadataUtil"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace HttpQueryProgrammer { - export interface IProps extends IProgrammerProps { - allowOptional?: boolean; - } - - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - allowOptional: boolean; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - // ANALYZE TYPE - const collection: MetadataCollection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate: (next) => - validate({ - metadata: next.metadata, - explore: next.explore, - allowOptional: props.allowOptional, - }), - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - - // DO TRANSFORM - const object: MetadataObjectType = result.data.objects[0]!.type; - const statements: ts.Statement[] = decode_object({ - context: props.context, - object, - }); - return { - functions: {}, - statements: [], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createUnionTypeNode([ - ts.factory.createTypeReferenceNode("string"), - props.context.importer.type({ - file: "typia", - name: "IReadableURLSearchParams", - }), - ]), - ), - ], - props.context.importer.type({ - file: "typia", - name: "Resolved", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock(statements, true), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - allowOptional: !!props.allowOptional, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - allowOptional?: boolean | undefined; - }): string[] => { - const errors: string[] = []; - const insert = (msg: string) => errors.push(msg); - - if (props.explore.top === true) { - // TOP MUST BE ONLY OBJECT - if (props.metadata.objects.length !== 1 || props.metadata.bucket() !== 1) - insert("only one object type is allowed."); - if (props.metadata.nullable === true) - insert("query parameters cannot be null."); - if (props.metadata.isRequired() === false) { - if (props.allowOptional === true) { - const everyPropertiesAreOptional: boolean = - props.metadata.size() === 1 && - props.metadata.objects.length === 1 && - props.metadata.objects[0]!.type.properties.every( - (p) => p.value.isRequired() === false, - ); - if (everyPropertiesAreOptional === false) - insert( - "query parameters can be optional only when every properties are optional.", - ); - } else insert("query parameters cannot be undefined."); - } - } else if ( - props.explore.nested !== null && - props.explore.nested instanceof MetadataArrayType - ) { - //---- - // ARRAY - //---- - const atomics = HttpMetadataUtil.atomics(props.metadata); - const expected: number = - props.metadata.atomics.length + - props.metadata.templates.length + - props.metadata.constants - .map((c) => c.values.length) - .reduce((a, b) => a + b, 0); - if (atomics.size > 1) insert("union type is not allowed in array."); - if (props.metadata.size() !== expected) - insert("only atomic or constant types are allowed in array."); - } else if (props.explore.object && props.explore.property !== null) { - //---- - // COMMON - //---- - // PROPERTY MUST BE SOLE - if (typeof props.explore.property === "object") - insert("dynamic property is not allowed."); - // DO NOT ALLOW TUPLE TYPE - if (props.metadata.tuples.length) insert("tuple type is not allowed."); - // DO NOT ALLOW UNION TYPE - if (HttpMetadataUtil.isUnion(props.metadata)) - insert("union type is not allowed."); - // DO NOT ALLOW NESTED OBJECT - if ( - props.metadata.objects.length || - props.metadata.sets.length || - props.metadata.maps.length || - props.metadata.natives.length - ) - insert("nested object type is not allowed."); - } - return errors; - }; - - const decode_object = (props: { - context: ITypiaContext; - object: MetadataObjectType; - }): ts.Statement[] => { - const input: ts.Identifier = ts.factory.createIdentifier("input"); - const output: ts.Identifier = ts.factory.createIdentifier("output"); - - return [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - input, - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createAsExpression( - ts.factory.createCallExpression( - props.context.importer.internal("httpQueryParseURLSearchParams"), - undefined, - [input], - ), - props.context.importer.type({ - file: "typia", - name: "IReadableURLSearchParams", - }), - ), - ), - ), - StatementFactory.constant({ - name: "output", - value: ts.factory.createObjectLiteralExpression( - props.object.properties.map((p) => - decode_regular_property({ - context: props.context, - property: p, - }), - ), - true, - ), - }), - ts.factory.createReturnStatement( - ts.factory.createAsExpression(output, TypeFactory.keyword("any")), - ), - ]; - }; - - const decode_regular_property = (props: { - context: ITypiaContext; - property: MetadataProperty; - }): ts.PropertyAssignment => { - const key: string = props.property.key.constants[0]!.values[0]! - .value as string; - const value: MetadataSchema = props.property.value; - - const [type, isArray]: [Atomic.Literal, boolean] = value.atomics.length - ? [value.atomics[0]!.type, false] - : value.constants.length - ? [value.constants[0]!.type, false] - : value.templates.length - ? ["string", false] - : (() => { - const meta = - value.arrays[0]?.type.value ?? - value.tuples[0]!.type.elements[0]!; - return meta.atomics.length - ? [meta.atomics[0]!.type, true] - : meta.templates.length - ? ["string", true] - : [meta.constants[0]!.type, true]; - })(); - return ts.factory.createPropertyAssignment( - NamingConvention.variable(key) - ? key - : ts.factory.createStringLiteral(key), - isArray - ? decode_array({ - context: props.context, - metadata: value, - input: ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - ts.factory.createIdentifier("input.getAll"), - undefined, - [ts.factory.createStringLiteral(key)], - ), - "map", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("elem")], - undefined, - undefined, - decode_value({ - context: props.context, - type, - coalesce: false, - input: ts.factory.createIdentifier("elem"), - }), - ), - ], - ), - }) - : decode_value({ - context: props.context, - type, - coalesce: value.nullable === false && value.isRequired() === false, - input: ts.factory.createCallExpression( - ts.factory.createIdentifier("input.get"), - undefined, - [ts.factory.createStringLiteral(key)], - ), - }), - ); - }; - - const decode_value = (props: { - context: ITypiaContext; - type: Atomic.Literal; - coalesce: boolean; - input: ts.Expression; - }) => { - const call = ts.factory.createCallExpression( - props.context.importer.internal( - `httpQueryRead${NamingConvention.capitalize(props.type)}`, - ), - undefined, - [props.input], - ); - return props.coalesce - ? ts.factory.createBinaryExpression( - call, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), - ts.factory.createIdentifier("undefined"), - ) - : call; - }; - - const decode_array = (props: { - context: ITypiaContext; - metadata: MetadataSchema; - input: ts.Expression; - }): ts.Expression => - props.metadata.nullable || props.metadata.isRequired() === false - ? ts.factory.createCallExpression( - props.context.importer.internal("httpQueryReadArray"), - undefined, - [ - props.input, - props.metadata.nullable - ? ts.factory.createNull() - : ts.factory.createIdentifier("undefined"), - ], - ) - : props.input; -} diff --git a/packages/core/src/programmers/http/HttpValidateFormDataProgrammer.ts b/packages/core/src/programmers/http/HttpValidateFormDataProgrammer.ts deleted file mode 100644 index 44207a628a2..00000000000 --- a/packages/core/src/programmers/http/HttpValidateFormDataProgrammer.ts +++ /dev/null @@ -1,90 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpFormDataProgrammer } from "./HttpFormDataProgrammer"; - -export namespace HttpValidateFormDataProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - const decode = HttpFormDataProgrammer.decompose(props); - return { - functions: { - ...validate.functions, - ...decode.functions, - }, - statements: [ - ...validate.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [decode.arrow.type ?? TypeFactory.keyword("any")], - }), - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpValidateHeadersProgrammer.ts b/packages/core/src/programmers/http/HttpValidateHeadersProgrammer.ts deleted file mode 100644 index 955c6934c13..00000000000 --- a/packages/core/src/programmers/http/HttpValidateHeadersProgrammer.ts +++ /dev/null @@ -1,90 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpHeadersProgrammer } from "./HttpHeadersProgrammer"; - -export namespace HttpValidateHeadersProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - const decode = HttpHeadersProgrammer.decompose(props); - return { - functions: { - ...validate.functions, - ...decode.functions, - }, - statements: [ - ...validate.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [decode.arrow.type ?? TypeFactory.keyword("any")], - }), - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/HttpValidateQueryProgrammer.ts b/packages/core/src/programmers/http/HttpValidateQueryProgrammer.ts deleted file mode 100644 index 5c865a2dba2..00000000000 --- a/packages/core/src/programmers/http/HttpValidateQueryProgrammer.ts +++ /dev/null @@ -1,96 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { HttpQueryProgrammer } from "./HttpQueryProgrammer"; - -export namespace HttpValidateQueryProgrammer { - export interface IProps extends IProgrammerProps { - allowOptional?: boolean; - } - - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - allowOptional: boolean; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - const decode = HttpQueryProgrammer.decompose(props); - return { - functions: { - ...validate.functions, - ...decode.functions, - }, - statements: [ - ...validate.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [decode.arrow.type ?? TypeFactory.keyword("any")], - }), - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - allowOptional: !!props.allowOptional, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/http/index.ts b/packages/core/src/programmers/http/index.ts deleted file mode 100644 index 3b4cec87c37..00000000000 --- a/packages/core/src/programmers/http/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from "./HttpAssertFormDataProgrammer"; -export * from "./HttpAssertHeadersProgrammer"; -export * from "./HttpAssertQueryProgrammer"; -export * from "./HttpFormDataProgrammer"; -export * from "./HttpHeadersProgrammer"; -export * from "./HttpIsFormDataProgrammer"; -export * from "./HttpIsHeadersProgrammer"; -export * from "./HttpIsQueryProgrammer"; -export * from "./HttpParameterProgrammer"; -export * from "./HttpQueryProgrammer"; -export * from "./HttpValidateFormDataProgrammer"; -export * from "./HttpValidateHeadersProgrammer"; -export * from "./HttpValidateQueryProgrammer"; diff --git a/packages/core/src/programmers/index.ts b/packages/core/src/programmers/index.ts deleted file mode 100644 index d8e00998620..00000000000 --- a/packages/core/src/programmers/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export * from "./helpers/FunctionProgrammer"; - -export * from "./AssertProgrammer"; -export * from "./ImportProgrammer"; -export * from "./IsProgrammer"; -export * from "./RandomProgrammer"; -export * from "./ValidateProgrammer"; - -export * from "./functional"; -export * from "./http"; -export * from "./json"; -export * from "./llm"; -export * from "./misc"; -export * from "./notations"; -export * from "./protobuf"; diff --git a/packages/core/src/programmers/internal/CheckerProgrammer.ts b/packages/core/src/programmers/internal/CheckerProgrammer.ts deleted file mode 100644 index b1e322062a2..00000000000 --- a/packages/core/src/programmers/internal/CheckerProgrammer.ts +++ /dev/null @@ -1,1614 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValueFactory } from "../../factories/ValueFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataConstant } from "../../schemas/metadata/MetadataConstant"; -import { MetadataMap } from "../../schemas/metadata/MetadataMap"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataSet } from "../../schemas/metadata/MetadataSet"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; -import { IsProgrammer } from "../IsProgrammer"; -import { AtomicPredicator } from "../helpers/AtomicPredicator"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { ICheckEntry } from "../helpers/ICheckEntry"; -import { IExpressionEntry } from "../helpers/IExpressionEntry"; -import { OptionPredicator } from "../helpers/OptionPredicator"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { check_array_length } from "../iterate/check_array_length"; -import { check_bigint } from "../iterate/check_bigint"; -import { check_native } from "../iterate/check_native"; -import { check_number } from "../iterate/check_number"; -import { check_string } from "../iterate/check_string"; -import { check_template } from "../iterate/check_template"; -import { decode_union_object } from "../iterate/decode_union_object"; -import { postfix_of_tuple } from "../iterate/postfix_of_tuple"; -import { wrap_metadata_rest_tuple } from "../iterate/wrap_metadata_rest_tuple"; -import { FeatureProgrammer } from "./FeatureProgrammer"; - -export namespace CheckerProgrammer { - export interface IConfig { - prefix: string; - path: boolean; - trace: boolean; - equals: boolean; - numeric: boolean; - addition?: () => ts.Statement[]; - decoder?: (props: { - metadata: MetadataSchema; - input: ts.Expression; - explore: IExplore; - }) => ts.Expression; - combiner: IConfig.Combiner; - atomist: (props: { - entry: ICheckEntry; - input: ts.Expression; - explore: IExplore; - }) => ts.Expression; - joiner: IConfig.IJoiner; - success: ts.Expression; - } - export namespace IConfig { - export interface Combiner { - (props: { - explore: IExplore; - logic: "and" | "or"; - input: ts.Expression; - binaries: IBinary[]; - expected: string; - }): ts.Expression; - } - export interface IJoiner { - object(props: { - input: ts.Expression; - entries: IExpressionEntry[]; - }): ts.Expression; - array(props: { - input: ts.Expression; - arrow: ts.ArrowFunction; - }): ts.Expression; - tuple?: undefined | ((exprs: ts.Expression[]) => ts.Expression); - - failure(props: { - input: ts.Expression; - expected: string; - explore?: undefined | FeatureProgrammer.IExplore; - }): ts.Expression; - is?(expression: ts.Expression): ts.Expression; - required?(exp: ts.Expression): ts.Expression; - full?: - | undefined - | ((props: { - condition: ts.Expression; - input: ts.Expression; - expected: string; - explore: IExplore; - }) => ts.Expression); - } - } - export type IExplore = FeatureProgrammer.IExplore; - - export interface IBinary { - expression: ts.Expression; - combined: boolean; - } - - /* ----------------------------------------------------------- - WRITERS - ----------------------------------------------------------- */ - export const compose = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IComposed => - FeatureProgrammer.compose({ - ...props, - config: configure(props), - }); - - export const write = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - type: ts.Type; - name?: string; - }): ts.ArrowFunction => - FeatureProgrammer.write({ - config: configure(props), - context: props.context, - functor: props.functor, - type: props.type, - name: props.name, - }); - - export const write_object_functions = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - FeatureProgrammer.write_object_functions({ - config: configure(props), - context: props.context, - collection: props.collection, - }); - - export const write_union_functions = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - FeatureProgrammer.write_union_functions({ - config: configure({ - context: props.context, - config: { - ...props.config, - numeric: false, - }, - functor: props.functor, - }), - collection: props.collection, - }); - - export const write_array_functions = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .arrays() - .filter((a) => a.recursive) - .map((type, i) => - StatementFactory.constant({ - name: `${props.config.prefix}a${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_array_inline({ - ...props, - input: ts.factory.createIdentifier("input"), - array: MetadataArray.create({ - type, - tags: [], - }), - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - export const write_tuple_functions = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .tuples() - .filter((t) => t.recursive) - .map((tuple, i) => - StatementFactory.constant({ - name: `${props.config.prefix}t${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_tuple_inline({ - config: props.config, - context: props.context, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - tuple, - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - const configure = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - }): FeatureProgrammer.IConfig => ({ - types: { - input: () => TypeFactory.keyword("any"), - output: (type, name) => - ts.factory.createTypePredicateNode( - undefined, - "input", - ts.factory.createTypeReferenceNode( - name ?? - TypeFactory.getFullName({ checker: props.context.checker, type }), - ), - ), - }, - trace: props.config.trace, - path: props.config.path, - prefix: props.config.prefix, - initializer: (next) => { - const collection: MetadataCollection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: next.context.checker, - transformer: next.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - }, - components: collection, - type: next.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: next.functor.method, - errors: result.errors, - }); - return { - collection, - metadata: result.data, - }; - }, - addition: props.config.addition, - decoder: props.config.decoder - ? (next) => props.config.decoder!(next) - : (next) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - objector: { - checker: props.config.decoder - ? (next) => props.config.decoder!(next) - : (next) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - decoder: (next) => - decode_object({ - config: props.config, - functor: props.functor, - input: next.input, - object: next.object, - explore: next.explore, - }), - joiner: props.config.joiner.object, - unionizer: props.config.equals - ? (next) => - decode_union_object({ - checker: (v) => - decode_object({ - config: props.config, - functor: props.functor, - object: v.object, - input: v.input, - explore: v.explore, - }), - decoder: (v) => - decode_object({ - config: props.config, - functor: props.functor, - input: v.input, - object: v.object, - explore: { - ...v.explore, - tracable: true, - }, - }), - success: props.config.joiner.is ?? ((expr) => expr), - escaper: (v) => - ts.factory.createReturnStatement( - props.config.joiner.failure(v), - ), - input: next.input, - objects: next.objects, - explore: next.explore, - }) - : (next) => - props.config.combiner({ - logic: "or", - explore: next.explore, - input: next.input, - binaries: next.objects.map((object) => ({ - expression: decode_object({ - config: props.config, - functor: props.functor, - object, - input: next.input, - explore: next.explore, - }), - combined: true, - })), - expected: `(${next.objects.map((t) => t.name).join(" | ")})`, - }), - failure: (next) => - ts.factory.createReturnStatement(props.config.joiner.failure(next)), - is: props.config.joiner.is, - required: props.config.joiner.required, - full: props.config.joiner.full - ? (next) => props.config.joiner.full!(next) - : undefined, - type: TypeFactory.keyword("boolean"), - }, - generator: { - unions: props.config.numeric - ? (collection) => - FeatureProgrammer.write_union_functions({ - config: configure({ - ...props, - config: { - ...props.config, - numeric: false, - }, - }), - collection, - }) - : undefined, - arrays: (collection) => - write_array_functions({ - ...props, - collection, - }), - tuples: (collection) => - write_tuple_functions({ - ...props, - collection, - }), - }, - }); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - export const decode = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: IExplore; - }): ts.Expression => { - if (props.metadata.any) return props.config.success; - - const top: IBinary[] = []; - const binaries: IBinary[] = []; - const add = (next: { - exact: boolean; - left: ts.Expression; - right?: ts.Expression; - }) => - create_add({ - binaries, - left: next.left, - right: next.right, - exact: next.exact, - default: props.input, - }); - const getConstantValue = (value: number | string | bigint | boolean) => { - if (typeof value === "string") - return ts.factory.createStringLiteral(value); - else if (typeof value === "bigint") - return ExpressionFactory.bigint(value); - return ts.factory.createIdentifier(value.toString()); - }; - - //---- - // CHECK OPTIONAL - //---- - // @todo -> should be elaborated - const checkOptional: boolean = - props.metadata.empty() || props.metadata.isUnionBucket(); - - // NULLABLE - if (checkOptional || props.metadata.nullable) - if (props.metadata.nullable) - add({ - exact: props.metadata.nullable, - left: ValueFactory.NULL(), - }); - else - create_add({ - binaries: top, - default: props.input, - exact: props.metadata.nullable, - left: ValueFactory.NULL(), - }); - - // UNDEFINDABLE - if (checkOptional || !props.metadata.isRequired()) - if (props.metadata.isRequired()) - create_add({ - binaries: top, - default: props.input, - exact: false, - left: ValueFactory.UNDEFINED(), - }); - else - add({ - exact: true, - left: ValueFactory.UNDEFINED(), - }); - - // FUNCTIONAL - if (props.metadata.functions.length) - if ( - OptionPredicator.functional(props.context.options) || - props.metadata.size() !== 1 - ) - add({ - exact: true, - left: ts.factory.createStringLiteral("function"), - right: ValueFactory.TYPEOF(props.input), - }); - else - binaries.push({ - combined: false, - expression: props.config.success, - }); - - //---- - // VALUES - //---- - // CONSTANT VALUES - const constants: MetadataConstant[] = props.metadata.constants.filter((c) => - AtomicPredicator.constant({ - metadata: props.metadata, - name: c.type, - }), - ); - const constantLength: number = constants - .map((c) => c.values.length) - .reduce((a, b) => a + b, 0); - if (constantLength >= 10) { - const values: Array = constants - .map((c) => c.values.map((v) => v.value)) - .flat(); - add({ - exact: true, - left: ts.factory.createTrue(), - right: ts.factory.createCallExpression( - IdentifierFactory.access( - props.functor.emplaceVariable( - `${props.config.prefix}v${props.functor.increment()}`, - ts.factory.createNewExpression( - ts.factory.createIdentifier("Set"), - undefined, - [ - ts.factory.createArrayLiteralExpression( - values.map((v) => - typeof v === "boolean" - ? v === true - ? ts.factory.createTrue() - : ts.factory.createFalse() - : typeof v === "bigint" - ? ExpressionFactory.bigint(v) - : typeof v === "number" - ? ExpressionFactory.number(v) - : ts.factory.createStringLiteral(v.toString()), - ), - ), - ], - ), - ), - "has", - ), - undefined, - [props.input], - ), - }); - } else - for (const c of constants) - if ( - AtomicPredicator.constant({ - metadata: props.metadata, - name: c.type, - }) - ) - for (const v of c.values) - add({ - exact: true, - left: getConstantValue(v.value), - }); - - // ATOMIC VALUES - for (const atom of props.metadata.atomics) - if ( - AtomicPredicator.atomic({ - metadata: props.metadata, - name: atom.type, - }) === false - ) - continue; - else if (atom.type === "number") - binaries.push({ - expression: props.config.atomist({ - explore: props.explore, - entry: check_number({ - context: props.context, - numeric: props.config.numeric, - atomic: atom, - input: props.input, - }), - input: props.input, - }), - combined: false, - }); - else if (atom.type === "bigint") - binaries.push({ - expression: props.config.atomist({ - explore: props.explore, - entry: check_bigint({ - context: props.context, - atomic: atom, - input: props.input, - }), - input: props.input, - }), - combined: false, - }); - else if (atom.type === "string") - binaries.push({ - expression: props.config.atomist({ - explore: props.explore, - entry: check_string({ - context: props.context, - atomic: atom, - input: props.input, - }), - input: props.input, - }), - combined: false, - }); - else - add({ - exact: true, - left: ts.factory.createStringLiteral(atom.type), - right: ValueFactory.TYPEOF(props.input), - }); - - // TEMPLATE LITERAL VALUES - if (props.metadata.templates.length) - if (AtomicPredicator.template(props.metadata)) - binaries.push({ - expression: props.config.atomist({ - explore: props.explore, - entry: check_template({ - templates: props.metadata.templates, - input: props.input, - }), - input: props.input, - }), - combined: false, - }); - - // NATIVE CLASSES - for (const native of props.metadata.natives) - binaries.push({ - expression: check_native({ - name: native.name, - input: props.input, - }), - combined: false, - }); - - //---- - // INSTANCES - //---- - interface IInstance { - head: ts.Expression; - body: ts.Expression | null; - expected: string; - } - const instances: IInstance[] = []; - const prepare = (next: IInstance) => instances.push(next); - - // SETS - if (props.metadata.sets.length) { - const install = (body: ts.Expression | null) => - prepare({ - head: check_native({ - name: "Set", - input: props.input, - }), - expected: props.metadata.sets - .map((elem) => `Set<${elem.getName()}>`) - .join(" | "), - body, - }); - if (props.metadata.sets.some((elem) => elem.value.any)) install(null); - else - install( - explore_sets({ - config: props.config, - context: props.context, - functor: props.functor, - sets: props.metadata.sets, - input: props.input, - explore: { - ...props.explore, - from: "array", - }, - }), - ); - } - - // MAPS - if (props.metadata.maps.length) { - const install = (body: ts.Expression | null) => - prepare({ - head: check_native({ - name: "Map", - input: props.input, - }), - expected: props.metadata.maps - .map(({ key, value }) => `Map<${key}, ${value}>`) - .join(" | "), - body, - }); - if (props.metadata.maps.some((elem) => elem.key.any && elem.value.any)) - install(null); - else - install( - explore_maps({ - config: props.config, - context: props.context, - functor: props.functor, - maps: props.metadata.maps, - input: props.input, - explore: { - ...props.explore, - from: "array", - }, - }), - ); - } - - // ARRAYS AND TUPLES - if (props.metadata.tuples.length + props.metadata.arrays.length > 0) { - const install = (body: ts.Expression | null) => - prepare({ - head: props.config.atomist({ - explore: props.explore, - entry: { - expected: [ - ...props.metadata.tuples.map((t) => t.type.name), - ...props.metadata.arrays.map((a) => a.getName()), - ].join(" | "), - expression: ExpressionFactory.isArray(props.input), - conditions: [], - }, - input: props.input, - }), - expected: [...props.metadata.tuples, ...props.metadata.arrays] - .map((elem) => elem.type.name) - .join(" | "), - body, - }); - if (props.metadata.arrays.length === 0) - if (props.metadata.tuples.length === 1) - install( - decode_tuple({ - config: props.config, - context: props.context, - functor: props.functor, - tuple: props.metadata.tuples[0]!, - input: props.input, - explore: { - ...props.explore, - from: "array", - }, - }), - ); - // TUPLE ONLY - else - install( - explore_tuples({ - config: props.config, - context: props.context, - functor: props.functor, - tuples: props.metadata.tuples, - input: props.input, - explore: { - ...props.explore, - from: "array", - }, - }), - ); - else if (props.metadata.arrays.some((elem) => elem.type.value.any)) - install(null); - else if (props.metadata.tuples.length === 0) - if (props.metadata.arrays.length === 1) - // ARRAY ONLY - install( - decode_array({ - config: props.config, - context: props.context, - functor: props.functor, - array: props.metadata.arrays[0]!, - input: props.input, - explore: { - ...props.explore, - from: "array", - }, - }), - ); - else - install( - explore_arrays({ - config: props.config, - context: props.context, - functor: props.functor, - arrays: props.metadata.arrays, - input: props.input, - explore: { - ...props.explore, - from: "array", - }, - }), - ); - else - install( - explore_arrays_and_tuples({ - config: props.config, - context: props.context, - functor: props.functor, - definitions: [...props.metadata.tuples, ...props.metadata.arrays], - input: props.input, - explore: props.explore, - }), - ); - } - - // OBJECT - if (props.metadata.objects.length > 0) - prepare({ - head: ExpressionFactory.isObject({ - checkNull: true, - checkArray: props.metadata.objects.some((obj) => - obj.type.properties.every( - (prop) => !prop.key.isSoleLiteral() || !prop.value.isRequired(), - ), - ), - input: props.input, - }), - expected: props.metadata.objects - .map((obj) => obj.type.name) - .join(" | "), - body: explore_objects({ - config: props.config, - functor: props.functor, - metadata: props.metadata, - input: props.input, - explore: { - ...props.explore, - from: "object", - }, - }), - }); - - if (instances.length) { - const transformer = - (merge: (x: ts.Expression, y: ts.Expression) => ts.Expression) => - (instance: IInstance) => - instance.body - ? { - expression: merge(instance.head, instance.body), - combined: true, - } - : { - expression: instance.head, - combined: false, - }; - if (instances.length === 1) - binaries.push( - transformer((head, body) => - props.config.combiner({ - explore: props.explore, - logic: "and", - input: props.input, - binaries: [head, body].map((expression) => ({ - expression, - combined: expression !== head, - })), - expected: props.metadata.getName(), - }), - )(instances[0]!), - ); - else - binaries.push({ - expression: props.config.combiner({ - explore: props.explore, - logic: "or", - input: props.input, - binaries: instances.map(transformer(ts.factory.createLogicalAnd)), - expected: props.metadata.getName(), - }), - combined: true, - }); - } - - // ESCAPED CASE - if (props.metadata.escaped !== null) - binaries.push({ - combined: false, - expression: - props.metadata.escaped.original.size() === 1 && - props.metadata.escaped.original.natives.length === 1 - ? check_native({ - name: props.metadata.escaped.original.natives[0]!.name, - input: props.input, - }) - : ts.factory.createLogicalAnd( - decode({ - context: props.context, - config: props.config, - functor: props.functor, - metadata: props.metadata.escaped.original, - input: props.input, - explore: props.explore, - }), - ts.factory.createLogicalAnd( - IsProgrammer.decode_to_json({ - checkNull: false, - input: props.input, - }), - decode_escaped({ - config: props.config, - context: props.context, - functor: props.functor, - metadata: props.metadata.escaped.returns, - input: props.input, - explore: props.explore, - }), - ), - ), - }); - - //---- - // COMBINE CONDITIONS - //---- - return top.length && binaries.length - ? props.config.combiner({ - explore: props.explore, - logic: "and", - input: props.input, - binaries: [ - ...top, - { - expression: props.config.combiner({ - explore: props.explore, - logic: "or", - input: props.input, - binaries, - expected: props.metadata.getName(), - }), - combined: true, - }, - ], - expected: props.metadata.getName(), - }) - : binaries.length - ? props.config.combiner({ - explore: props.explore, - logic: "or", - input: props.input, - binaries, - expected: props.metadata.getName(), - }) - : props.config.success; - }; - - export const decode_object = (props: { - config: IConfig; - functor: FunctionProgrammer; - object: MetadataObjectType; - input: ts.Expression; - explore: IExplore; - }) => { - props.object.validated ||= true; - return FeatureProgrammer.decode_object(props); - }; - - const decode_array = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - array: MetadataArray; - input: ts.Expression; - explore: IExplore; - }) => { - if (props.array.type.recursive === false) return decode_array_inline(props); - - const arrayExplore: IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createLogicalOr( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}a${props.array.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...arrayExplore, - source: "function", - from: "array", - }, - input: props.input, - }), - ), - props.config.joiner.failure({ - input: props.input, - expected: props.array.type.name, - explore: arrayExplore, - }), - ); - }; - - const decode_array_inline = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - array: MetadataArray; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => { - const length: ICheckEntry = check_array_length({ - context: props.context, - array: props.array, - input: props.input, - }); - const main: ts.Expression = FeatureProgrammer.decode_array({ - config: { - prefix: props.config.prefix, - trace: props.config.trace, - path: props.config.path, - decoder: (next) => - decode({ - ...props, - ...next, - }), - }, - functor: props.functor, - combiner: props.config.joiner.array, - array: props.array, - input: props.input, - explore: props.explore, - }); - return length.expression === null && length.conditions.length === 0 - ? main - : ts.factory.createLogicalAnd( - props.config.atomist({ - explore: props.explore, - input: props.input, - entry: length, - }), - main, - ); - }; - - const decode_tuple = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - tuple: MetadataTuple; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => { - if (props.tuple.type.recursive === false) - return decode_tuple_inline({ - ...props, - tuple: props.tuple.type, - }); - const arrayExplore: IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createLogicalOr( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}t${props.tuple.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...arrayExplore, - source: "function", - }, - input: props.input, - }), - ), - props.config.joiner.failure({ - input: props.input, - expected: props.tuple.type.name, - explore: arrayExplore, - }), - ); - }; - - const decode_tuple_inline = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - tuple: MetadataTupleType; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => { - const binaries: ts.Expression[] = props.tuple.elements - .filter((metadata) => metadata.rest === null) - .map((metadata, index) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createElementAccessExpression(props.input, index), - metadata, - explore: { - ...props.explore, - from: "array", - postfix: props.explore.postfix.length - ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` - : `"[${index}]"`, - }, - }), - ); - const rest: ts.Expression | null = - props.tuple.elements.length && props.tuple.elements.at(-1)!.rest !== null - ? decode({ - config: props.config, - context: props.context, - functor: props.functor, - input: ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "slice"), - undefined, - [ExpressionFactory.number(props.tuple.elements.length - 1)], - ), - metadata: wrap_metadata_rest_tuple( - props.tuple.elements.at(-1)!.rest!, - ), - explore: { - ...props.explore, - start: props.tuple.elements.length - 1, - }, - }) - : null; - const arrayLength = ts.factory.createPropertyAccessExpression( - props.input, - "length", - ); - return props.config.combiner({ - explore: props.explore, - logic: "and", - input: props.input, - binaries: [ - ...(rest === null - ? props.tuple.elements.every((t) => t.optional === false) - ? [ - { - combined: false, - expression: ts.factory.createStrictEquality( - arrayLength, - ExpressionFactory.number(props.tuple.elements.length), - ), - }, - ] - : [ - { - combined: false, - expression: ts.factory.createLogicalAnd( - ts.factory.createLessThanEquals( - ExpressionFactory.number( - props.tuple.elements.filter((t) => t.optional === false) - .length, - ), - arrayLength, - ), - ts.factory.createGreaterThanEquals( - ExpressionFactory.number(props.tuple.elements.length), - arrayLength, - ), - ), - }, - ] - : []), - ...(props.config.joiner.tuple - ? [ - { - expression: props.config.joiner.tuple(binaries), - combined: true, - }, - ] - : binaries.map((expression) => ({ - expression, - combined: true, - }))), - ...(rest !== null - ? [ - { - expression: rest, - combined: true, - }, - ] - : []), - ], - expected: `[${props.tuple.elements.map((t) => t.getName()).join(", ")}]`, - }); - }; - - const decode_escaped = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - metadata: MetadataSchema; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => - ts.factory.createCallExpression( - ts.factory.createParenthesizedExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - undefined, - undefined, - decode({ - ...props, - input: ts.factory.createIdentifier("input"), - }), - ), - ), - undefined, - [ - ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "toJSON"), - undefined, - [], - ), - ], - ); - - /* ----------------------------------------------------------- - UNION TYPE EXPLORERS - ----------------------------------------------------------- */ - const explore_sets = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - sets: MetadataSet[]; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => - ts.factory.createCallExpression( - UnionExplorer.set({ - config: { - checker: (v) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: v.input, - metadata: v.definition, - explore: v.explore, - }), - decoder: (v) => - decode_array({ - config: props.config, - context: props.context, - functor: props.functor, - array: v.definition, - input: v.input, - explore: v.explore, - }), - empty: props.config.success, - success: props.config.success, - failure: (v) => - ts.factory.createReturnStatement(props.config.joiner.failure(v)), - }, - parameters: [], - input: props.input, - sets: props.sets, - explore: props.explore, - }), - undefined, - undefined, - ); - - const explore_maps = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - maps: MetadataMap[]; - explore: IExplore; - }): ts.Expression => - ts.factory.createCallExpression( - UnionExplorer.map({ - config: { - checker: (v) => - ts.factory.createLogicalAnd( - decode({ - config: props.config, - context: props.context, - functor: props.functor, - input: ts.factory.createElementAccessExpression(v.input, 0), - metadata: v.definition[0], - explore: { - ...v.explore, - postfix: `${v.explore.postfix}[0]`, - }, - }), - decode({ - config: props.config, - context: props.context, - functor: props.functor, - input: ts.factory.createElementAccessExpression(v.input, 1), - metadata: v.definition[1], - explore: { - ...v.explore, - postfix: `${v.explore.postfix}[1]`, - }, - }), - ), - decoder: (v) => - decode_array({ - context: props.context, - config: props.config, - functor: props.functor, - array: v.definition, - input: v.input, - explore: v.explore, - }), - empty: props.config.success, - success: props.config.success, - failure: (v) => - ts.factory.createReturnStatement(props.config.joiner.failure(v)), - }, - parameters: [], - input: props.input, - maps: props.maps, - explore: props.explore, - }), - undefined, - undefined, - ); - - const explore_tuples = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - tuples: MetadataTuple[]; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => - explore_array_like_union_types({ - config: props.config, - functor: props.functor, - factory: (next) => - UnionExplorer.tuple({ - config: { - checker: (v) => - decode_tuple({ - context: props.context, - config: props.config, - functor: props.functor, - input: v.input, - tuple: v.definition, - explore: v.explore, - }), - decoder: (v) => - decode_tuple({ - context: props.context, - config: props.config, - functor: props.functor, - tuple: v.definition, - input: v.input, - explore: v.explore, - }), - empty: props.config.success, - success: props.config.success, - failure: (v) => - ts.factory.createReturnStatement(props.config.joiner.failure(v)), - }, - parameters: next.parameters, - tuples: next.definitions, - input: next.input, - explore: next.explore, - }), - definitions: props.tuples, - input: props.input, - explore: props.explore, - }); - - const explore_arrays = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - arrays: MetadataArray[]; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => - explore_array_like_union_types({ - config: props.config, - functor: props.functor, - factory: (next) => - UnionExplorer.array({ - config: { - checker: (v) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - metadata: v.definition, - input: v.input, - explore: v.explore, - }), - decoder: (v) => - decode_array({ - context: props.context, - config: props.config, - functor: props.functor, - array: v.definition, - input: v.input, - explore: v.explore, - }), - empty: props.config.success, - success: props.config.success, - failure: (v) => - ts.factory.createReturnStatement(props.config.joiner.failure(v)), - }, - parameters: next.parameters, - arrays: next.definitions, - input: next.input, - explore: next.explore, - }), - definitions: props.arrays, - input: props.input, - explore: props.explore, - }); - - const explore_arrays_and_tuples = (props: { - config: IConfig; - context: ITypiaContext; - functor: FunctionProgrammer; - definitions: Array; - input: ts.Expression; - explore: IExplore; - }): ts.Expression => - explore_array_like_union_types({ - config: props.config, - functor: props.functor, - factory: (next) => - UnionExplorer.array_or_tuple({ - config: { - checker: (v) => - v.definition instanceof MetadataTuple - ? decode_tuple({ - config: props.config, - context: props.context, - functor: props.functor, - input: v.input, - tuple: v.definition, - explore: v.explore, - }) - : props.config.atomist({ - explore: v.explore, - entry: { - expected: props.definitions - .map((elem) => - elem instanceof MetadataArray - ? elem.getName() - : elem.type.name, - ) - .join(" | "), - expression: decode({ - functor: props.functor, - context: props.context, - config: props.config, - metadata: v.definition, - input: v.input, - explore: v.explore, - }), - conditions: [], - }, - input: v.container, - }), - decoder: (v) => - v.definition instanceof MetadataTuple - ? decode_tuple({ - context: props.context, - config: props.config, - functor: props.functor, - input: v.input, - tuple: v.definition, - explore: v.explore, - }) - : decode_array({ - context: props.context, - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - empty: props.config.success, - success: props.config.success, - failure: (v) => - ts.factory.createReturnStatement(props.config.joiner.failure(v)), - }, - parameters: next.parameters, - definitions: next.definitions, - input: next.input, - explore: next.explore, - }), - input: props.input, - definitions: props.definitions, - explore: props.explore, - }); - - const explore_array_like_union_types = < - T extends MetadataArray | MetadataTuple, - >(props: { - config: IConfig; - functor: FunctionProgrammer; - factory: (next: { - parameters: ts.ParameterDeclaration[]; - definitions: T[]; - input: ts.Expression; - explore: IExplore; - }) => ts.ArrowFunction; - input: ts.Expression; - definitions: T[]; - explore: IExplore; - }): ts.Expression => { - const arrow = (next: { - parameters: ts.ParameterDeclaration[]; - explore: IExplore; - input: ts.Expression; - }): ts.ArrowFunction => - props.factory({ - parameters: next.parameters, - definitions: props.definitions, - input: next.input, - explore: next.explore, - }); - if (props.definitions.every((e) => e.type.recursive === false)) - ts.factory.createCallExpression( - arrow({ - explore: props.explore, - input: props.input, - parameters: [], - }), - undefined, - [], - ); - const arrayExplore: IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createLogicalOr( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.emplaceUnion( - props.config.prefix, - props.definitions.map((e) => e.type.name).join(" | "), - () => - arrow({ - parameters: FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - explore: { - ...arrayExplore, - postfix: "", - }, - input: ts.factory.createIdentifier("input"), - }), - ), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ), - props.config.joiner.failure({ - input: props.input, - expected: props.definitions.map((e) => e.type.name).join(" | "), - explore: arrayExplore, - }), - ); - }; - - const explore_objects = (props: { - config: IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: IExplore; - }) => - props.metadata.objects.length === 1 - ? decode_object({ - config: props.config, - functor: props.functor, - object: props.metadata.objects[0]!.type, - input: props.input, - explore: props.explore, - }) - : ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}u${props.metadata.union_index!}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ); -} - -const create_add = (props: { - binaries: CheckerProgrammer.IBinary[]; - default: ts.Expression; - exact: boolean; - left: ts.Expression; - right?: ts.Expression; -}) => { - const factory = props.exact - ? ts.factory.createStrictEquality - : ts.factory.createStrictInequality; - props.binaries.push({ - expression: factory(props.left, props.right ?? props.default), - combined: false, - }); -}; diff --git a/packages/core/src/programmers/internal/FeatureProgrammer.ts b/packages/core/src/programmers/internal/FeatureProgrammer.ts deleted file mode 100644 index 93fa0a389c8..00000000000 --- a/packages/core/src/programmers/internal/FeatureProgrammer.ts +++ /dev/null @@ -1,602 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValueFactory } from "../../factories/ValueFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { IExpressionEntry } from "../helpers/IExpressionEntry"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { feature_object_entries } from "../iterate/feature_object_entries"; -import { CheckerProgrammer } from "./CheckerProgrammer"; - -export namespace FeatureProgrammer { - /* ----------------------------------------------------------- - PARAMETERS - ----------------------------------------------------------- */ - export interface IConfig { - types: IConfig.ITypes; - - /** Prefix name of internal functions for specific types. */ - prefix: string; - - /** Whether to archive access path or not. */ - path: boolean; - - /** Whether to trace exception or not. */ - trace: boolean; - - addition?: undefined | ((collection: MetadataCollection) => ts.Statement[]); - - /** Initializer of metadata. */ - initializer: (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - }) => { - collection: MetadataCollection; - metadata: MetadataSchema; - }; - - /** Decoder, station of every types. */ - decoder: (props: { - metadata: MetadataSchema; - input: ts.Expression; - explore: IExplore; - }) => Output; - - /** Object configurator. */ - objector: IConfig.IObjector; - - /** Generator of functions for object types. */ - generator: IConfig.IGenerator; - } - export namespace IConfig { - export interface ITypes { - input: (type: ts.Type, name?: undefined | string) => ts.TypeNode; - output: (type: ts.Type, name?: undefined | string) => ts.TypeNode; - } - - export interface IObjector { - /** Type checker when union object type comes. */ - checker: (props: { - metadata: MetadataSchema; - input: ts.Expression; - explore: IExplore; - }) => ts.Expression; - - /** Decoder, function call expression generator of specific typed objects. */ - decoder: (props: { - input: ts.Expression; - object: MetadataObjectType; - explore: IExplore; - }) => ts.Expression; - - /** Joiner of expressions from properties. */ - joiner(props: { - entries: IExpressionEntry[]; - input?: ts.Expression; - object?: MetadataObjectType; - }): ts.ConciseBody; - - /** - * Union type specificator. - * - * Expression of an algorithm specifying object type and calling the - * `decoder` function of the specified object type. - */ - unionizer: (props: { - objects: MetadataObjectType[]; - input: ts.Expression; - explore: IExplore; - }) => ts.Expression; - - /** - * Handler of union type specification failure. - * - * @param props Properties of failure - * @returns Statement of failure - */ - failure(props: { - input: ts.Expression; - expected: string; - explore?: undefined | IExplore; - }): ts.Statement; - - /** - * Transformer of type checking expression by discrimination. - * - * When an object type has been specified by a discrimination without full - * iteration, the `unionizer` will decode the object instance after the - * last type checking. - * - * In such circumtance, you can transform the last type checking function. - * - * @deprecated - * @param exp Current expression about type checking - * @returns Transformed expression - */ - is?: undefined | ((exp: ts.Expression) => ts.Expression); - - /** - * Transformer of non-undefined type checking by discrimination. - * - * When specifying an union type of objects, `typia` tries to find - * discrimination way just by checking only one property type. If - * succeeded to find the discrimination way, `typia` will check the target - * property type and in the checking, non-undefined type checking would be - * done. - * - * In such process, you can transform the non-undefined type checking. - * - * @deprecated - * @param exp - * @returns Transformed expression - */ - required?: undefined | ((exp: ts.Expression) => ts.Expression); - - /** - * Condition wrapper when unable to specify any object type. - * - * When failed to specify an object type through discrimination, full - * iteration type checking would be happened. In such circumstance, you - * can wrap the condition with additional function. - * - * @param props Properties of condition - * @returns The wrapper expression - */ - full?: - | undefined - | ((props: { - condition: ts.Expression; - input: ts.Expression; - expected: string; - explore: IExplore; - }) => ts.Expression); - - /** Return type. */ - type?: undefined | ts.TypeNode; - } - export interface IGenerator { - objects?: - | undefined - | ((collection: MetadataCollection) => ts.VariableStatement[]); - unions?: - | undefined - | ((collection: MetadataCollection) => ts.VariableStatement[]); - arrays: (collection: MetadataCollection) => ts.VariableStatement[]; - tuples: (collection: MetadataCollection) => ts.VariableStatement[]; - } - } - - export interface IExplore { - tracable: boolean; - source: "top" | "function"; - from: "top" | "array" | "object"; - postfix: string; - start?: undefined | number; - } - - export type Decoder< - T, - Output extends ts.ConciseBody = ts.ConciseBody, - > = (props: { - input: ts.Expression; - definition: T; - explore: IExplore; - }) => Output; - - /* ----------------------------------------------------------- - GENERATORS - ----------------------------------------------------------- */ - export interface IComposed { - body: ts.ConciseBody; - parameters: ts.ParameterDeclaration[]; - functions: Record; - statements: ts.Statement[]; - response: ts.TypeNode; - } - export interface IDecomposed { - functions: Record; - statements: ts.Statement[]; - arrow: ts.ArrowFunction; - } - - export const compose = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): IComposed => { - const { collection, metadata } = props.config.initializer(props); - return { - body: props.config.decoder({ - input: ValueFactory.INPUT(), - metadata, - explore: { - tracable: props.config.path || props.config.trace, - source: "top", - from: "top", - postfix: '""', - }, - }), - statements: props.config.addition - ? props.config.addition(collection) - : [], - functions: { - ...Object.fromEntries( - ( - props.config.generator.objects?.(collection) ?? - write_object_functions({ - ...props, - collection, - }) - ).map((v, i) => [`${props.config.prefix}o${i}`, v]), - ), - ...Object.fromEntries( - ( - props.config.generator.unions?.(collection) ?? - write_union_functions({ - config: props.config, - collection, - }) - ).map((v, i) => [`${props.config.prefix}u${i}`, v]), - ), - ...Object.fromEntries( - props.config.generator - .arrays(collection) - .map((v, i) => [`${props.config.prefix}a${i}`, v]), - ), - ...Object.fromEntries( - props.config.generator - .tuples(collection) - .map((v, i) => [`${props.config.prefix}t${i}`, v]), - ), - }, - parameters: parameterDeclarations({ - config: props.config, - type: props.config.types.input(props.type, props.name), - input: ValueFactory.INPUT(), - }), - response: props.config.types.output(props.type, props.name), - }; - }; - - export const writeDecomposed = (props: { - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - result: IDecomposed; - returnWrapper?: (arrow: ts.ArrowFunction) => ts.Expression; - }): ts.CallExpression => - ts.factory.createCallExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - ts.factory.createBlock([ - ...props.functor.declare(), - ...Object.entries(props.result.functions) - .filter(([k]) => props.functor.hasLocal(k)) - .map(([_k, v]) => v), - ...props.result.statements, - ts.factory.createReturnStatement( - props.returnWrapper - ? props.returnWrapper(props.result.arrow) - : props.result.arrow, - ), - ]), - ), - undefined, - undefined, - ); - - export const write = (props: { - context: ITypiaContext; - config: IConfig; - functor: FunctionProgrammer; - type: ts.Type; - name?: string | undefined; - }): ts.ArrowFunction => { - // ITERATE OVER ALL METADATA - const { collection, metadata } = props.config.initializer(props); - const output: ts.ConciseBody = props.config.decoder({ - metadata, - input: ValueFactory.INPUT(), - explore: { - tracable: props.config.path || props.config.trace, - source: "top", - from: "top", - postfix: '""', - }, - }); - - // RETURNS THE OPTIMAL ARROW FUNCTION - const functions = { - objects: - props.config.generator.objects?.(collection) ?? - write_object_functions({ - config: props.config, - context: props.context, - collection, - }), - unions: - props.config.generator.unions?.(collection) ?? - write_union_functions({ - config: props.config, - collection, - }), - arrays: props.config.generator.arrays(collection), - tuples: props.config.generator.tuples(collection), - }; - const added: ts.Statement[] = (props.config.addition ?? (() => []))( - collection, - ); - - return ts.factory.createArrowFunction( - undefined, - undefined, - parameterDeclarations({ - config: props.config, - type: props.config.types.input(props.type, props.name), - input: ValueFactory.INPUT(), - }), - props.config.types.output(props.type, props.name), - undefined, - ts.factory.createBlock( - [ - ...added, - ...functions.objects.filter((_, i) => - props.functor.hasLocal(`${props.config.prefix}o${i}`), - ), - ...functions.unions.filter((_, i) => - props.functor.hasLocal(`${props.config.prefix}u${i}`), - ), - ...functions.arrays.filter((_, i) => - props.functor.hasLocal(`${props.config.prefix}a${i}`), - ), - ...functions.tuples.filter((_, i) => - props.functor.hasLocal(`${props.config.prefix}t${i}`), - ), - ...(ts.isBlock(output) - ? output.statements - : [ts.factory.createReturnStatement(output)]), - ], - true, - ), - ); - }; - - export const write_object_functions = (props: { - config: IConfig; - context: ITypiaContext; - collection: MetadataCollection; - }) => - props.collection.objects().map((object) => - StatementFactory.constant({ - name: `${props.config.prefix}o${object.index}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ValueFactory.INPUT(), - }), - props.config.objector.type ?? TypeFactory.keyword("any"), - undefined, - props.config.objector.joiner({ - input: ts.factory.createIdentifier("input"), - entries: feature_object_entries({ - config: props.config, - context: props.context, - input: ts.factory.createIdentifier("input"), - object, - }), - object, - }), - ), - }), - ); - - export const write_union_functions = (props: { - config: IConfig; - collection: MetadataCollection; - }) => - props.collection.unions().map((union, i) => - StatementFactory.constant({ - name: `${props.config.prefix}u${i}`, - value: write_union({ - config: props.config, - objects: union, - }), - }), - ); - - const write_union = (props: { - config: IConfig; - objects: MetadataObjectType[]; - }) => - ts.factory.createArrowFunction( - undefined, - undefined, - parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ValueFactory.INPUT(), - }), - TypeFactory.keyword("any"), - undefined, - UnionExplorer.object({ - config: props.config, - objects: props.objects, - input: ValueFactory.INPUT(), - explore: { - tracable: props.config.path || props.config.trace, - source: "function", - from: "object", - postfix: "", - }, - }), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - export const decode_array = (props: { - config: Pick; - functor: FunctionProgrammer; - combiner: (next: { - input: ts.Expression; - arrow: ts.ArrowFunction; - }) => ts.Expression; - array: MetadataArray; - input: ts.Expression; - explore: IExplore; - }) => { - const rand: string = props.functor.increment().toString(); - const tail = - props.config.path || props.config.trace - ? [ - IdentifierFactory.parameter( - "_index" + rand, - TypeFactory.keyword("number"), - ), - ] - : []; - const arrow: ts.ArrowFunction = ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("elem", TypeFactory.keyword("any")), - ...tail, - ], - undefined, - undefined, - props.config.decoder({ - input: ValueFactory.INPUT("elem"), - metadata: props.array.type.value, - explore: { - tracable: props.explore.tracable, - source: props.explore.source, - from: "array", - postfix: index({ - start: props.explore.start ?? null, - postfix: props.explore.postfix, - rand, - }), - }, - }), - ); - return props.combiner({ - input: props.input, - arrow, - }); - }; - - export const decode_object = (props: { - config: Pick; - functor: FunctionProgrammer; - object: MetadataObjectType; - input: ts.Expression; - explore: IExplore; - }) => - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(`${props.config.prefix}o${props.object.index}`), - ), - undefined, - argumentsArray(props), - ); - - /* ----------------------------------------------------------- - UTILITIES FOR INTERNAL FUNCTIONS - ----------------------------------------------------------- */ - export const index = (props: { - start: number | null; - postfix: string; - rand: string; - }) => { - const tail: string = - props.start !== null - ? `"[" + (${props.start} + _index${props.rand}) + "]"` - : `"[" + _index${props.rand} + "]"`; - if (props.postfix === "") return tail; - else if (props.postfix[props.postfix.length - 1] === `"`) - return ( - props.postfix.substring(0, props.postfix.length - 1) + tail.substring(1) - ); - return props.postfix + ` + ${tail}`; - }; - - export const argumentsArray = (props: { - config: Pick; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; - }) => { - const tail: ts.Expression[] = - props.config.path === false && props.config.trace === false - ? [] - : props.config.path === true && props.config.trace === true - ? [ - ts.factory.createIdentifier( - props.explore.postfix - ? `_path + ${props.explore.postfix}` - : "_path", - ), - props.explore.source === "function" - ? ts.factory.createIdentifier( - `${props.explore.tracable} && _exceptionable`, - ) - : props.explore.tracable - ? ts.factory.createTrue() - : ts.factory.createFalse(), - ] - : props.config.path === true - ? [ - ts.factory.createIdentifier( - props.explore.postfix - ? `_path + ${props.explore.postfix}` - : "_path", - ), - ] - : [ - props.explore.source === "function" - ? ts.factory.createIdentifier( - `${props.explore.tracable} && _exceptionable`, - ) - : props.explore.tracable - ? ts.factory.createTrue() - : ts.factory.createFalse(), - ]; - return [props.input, ...tail]; - }; - - export const parameterDeclarations = (props: { - config: Pick; - type: ts.TypeNode; - input: ts.Identifier; - }) => { - const tail: ts.ParameterDeclaration[] = []; - if (props.config.path) - tail.push( - IdentifierFactory.parameter("_path", TypeFactory.keyword("string")), - ); - if (props.config.trace) - tail.push( - IdentifierFactory.parameter( - "_exceptionable", - TypeFactory.keyword("boolean"), - ts.factory.createTrue(), - ), - ); - return [IdentifierFactory.parameter(props.input, props.type), ...tail]; - }; -} diff --git a/packages/core/src/programmers/iterate/check_array_length.ts b/packages/core/src/programmers/iterate/check_array_length.ts deleted file mode 100644 index 9c5f02d9d96..00000000000 --- a/packages/core/src/programmers/iterate/check_array_length.ts +++ /dev/null @@ -1,40 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { ICheckEntry } from "../helpers/ICheckEntry"; - -/** @internal */ -export const check_array_length = (props: { - context: ITypiaContext; - array: MetadataArray; - input: ts.Expression; -}): ICheckEntry => { - const conditions: ICheckEntry.ICondition[][] = check_array_type_tags(props); - return { - expected: props.array.getName(), - expression: null, - conditions, - }; -}; - -/** @internal */ -const check_array_type_tags = (props: { - context: ITypiaContext; - array: MetadataArray; - input: ts.Expression; -}): ICheckEntry.ICondition[][] => - props.array.tags - .map((row) => row.filter((tag) => !!tag.validate)) - .filter((row) => !!row.length) - .map((row) => - row.map((tag) => ({ - expected: `Array<> & ${tag.name}`, - expression: ExpressionFactory.transpile({ - transformer: props.context.transformer, - importer: props.context.importer, - script: tag.validate!, - })(props.input), - })), - ); diff --git a/packages/core/src/programmers/iterate/check_bigint.ts b/packages/core/src/programmers/iterate/check_bigint.ts deleted file mode 100644 index 4f40757e437..00000000000 --- a/packages/core/src/programmers/iterate/check_bigint.ts +++ /dev/null @@ -1,43 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { ICheckEntry } from "../helpers/ICheckEntry"; - -/** @internal */ -export const check_bigint = (props: { - context: ITypiaContext; - atomic: MetadataAtomic; - input: ts.Expression; -}): ICheckEntry => { - const conditions: ICheckEntry.ICondition[][] = check_bigint_type_tags(props); - return { - expected: props.atomic.getName(), - expression: ts.factory.createStrictEquality( - ts.factory.createStringLiteral("bigint"), - ts.factory.createTypeOfExpression(props.input), - ), - conditions, - }; -}; - -/** @internal */ -const check_bigint_type_tags = (props: { - context: ITypiaContext; - atomic: MetadataAtomic; - input: ts.Expression; -}): ICheckEntry.ICondition[][] => - props.atomic.tags - .map((row) => row.filter((tag) => !!tag.validate)) - .filter((row) => !!row.length) - .map((row) => - row.map((tag) => ({ - expected: `bigint & ${tag.name}`, - expression: ExpressionFactory.transpile({ - transformer: props.context.transformer, - importer: props.context.importer, - script: tag.validate!, - })(props.input), - })), - ); diff --git a/packages/core/src/programmers/iterate/check_dynamic_key.ts b/packages/core/src/programmers/iterate/check_dynamic_key.ts deleted file mode 100644 index 134b924e0f7..00000000000 --- a/packages/core/src/programmers/iterate/check_dynamic_key.ts +++ /dev/null @@ -1,195 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { ICheckEntry } from "../helpers/ICheckEntry"; -import { check_bigint } from "./check_bigint"; -import { check_number } from "./check_number"; -import { check_string } from "./check_string"; -import { check_template } from "./check_template"; - -/** @internal */ -export const check_dynamic_key = (props: { - context: ITypiaContext; - metadata: MetadataSchema; - input: ts.Expression; -}): ts.Expression => { - // IF PURE STRING EXISTS, THEN SKIP VALIDATION - if ( - (props.metadata.atomics.length !== 0 && - props.metadata.atomics.some( - (a) => - a.type === "string" && - a.tags.filter((row) => row.every((t) => t.validate !== undefined)) - .length === 0, - )) || - (props.metadata.natives.length !== 0 && - props.metadata.natives.some((native) => native.name === "String")) - ) - return ts.factory.createTrue(); - - const conditions: ts.Expression[] = []; - - // NULLISH COALESCING - if (props.metadata.nullable === true) - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("null"), - props.input, - ), - ); - if (props.metadata.isRequired() === false) - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("undefined"), - props.input, - ), - ); - - // ATOMICS - for (const atom of props.metadata.atomics) - if (atom.type === "boolean") - conditions.push( - ts.factory.createLogicalOr( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("false"), - props.input, - ), - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("true"), - props.input, - ), - ), - ); - else if (atom.type === "bigint") - conditions.push( - ts.factory.createLogicalAnd( - ts.factory.createCallExpression( - props.context.importer.internal("isBigintString"), - undefined, - [props.input], - ), - atomist( - check_bigint({ - context: props.context, - atomic: atom, - input: ts.factory.createCallExpression( - ts.factory.createIdentifier("BigInt"), - undefined, - [props.input], - ), - }), - ), - ), - ); - else if (atom.type === "number") - conditions.push( - atomist( - check_number({ - context: props.context, - numeric: true, - atomic: atom, - input: ts.factory.createCallExpression( - ts.factory.createIdentifier("Number"), - undefined, - [props.input], - ), - }), - ), - ); - else - conditions.push( - atomist( - check_string({ - context: props.context, - atomic: atom, - input: props.input, - }), - ), - ); - - // CONSTANTS - for (const constant of props.metadata.constants) - for (const { value } of constant.values) - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral(String(value)), - props.input, - ), - ); - - // TEMPLATES - if (!!props.metadata.templates.length) - conditions.push( - atomist( - check_template({ - templates: props.metadata.templates, - input: props.input, - }), - ), - ); - - // NATIVES - for (const native of props.metadata.natives) - if (native.name === "Boolean") - conditions.push( - ts.factory.createLogicalOr( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("false"), - props.input, - ), - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("true"), - props.input, - ), - ), - ); - else if (native.name === "BigInt") - conditions.push( - ts.factory.createCallExpression( - props.context.importer.internal("isBigintString"), - undefined, - [props.input], - ), - ); - else if (native.name === "Number") - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createFalse(), - ts.factory.createCallExpression( - ts.factory.createIdentifier("Number.isNaN"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("Number"), - undefined, - [props.input], - ), - ], - ), - ), - ); - - return conditions.length === 0 - ? ts.factory.createTrue() - : conditions.length === 1 - ? conditions[0]! - : conditions.reduce(ts.factory.createLogicalOr); -}; - -/** @internal */ -const atomist = (entry: ICheckEntry) => - [ - ...(entry.expression ? [entry.expression] : []), - ...(entry.conditions.length === 0 - ? [] - : [ - entry.conditions - .map((set) => - set - .map((s) => s.expression) - .reduce((a, b) => ts.factory.createLogicalAnd(a, b)), - ) - .reduce((a, b) => ts.factory.createLogicalOr(a, b)), - ]), - ].reduce((x, y) => ts.factory.createLogicalAnd(x, y)); diff --git a/packages/core/src/programmers/iterate/check_dynamic_properties.ts b/packages/core/src/programmers/iterate/check_dynamic_properties.ts deleted file mode 100644 index a6ea0cec6f0..00000000000 --- a/packages/core/src/programmers/iterate/check_dynamic_properties.ts +++ /dev/null @@ -1,229 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { IExpressionEntry } from "../helpers/IExpressionEntry"; -import { check_dynamic_key } from "./check_dynamic_key"; -import { check_everything } from "./check_everything"; -import { check_object } from "./check_object"; - -/** @internal */ -export const check_dynamic_properties = (props: { - config: check_object.IConfig; - context: ITypiaContext; - regular: IExpressionEntry[]; - dynamic: IExpressionEntry[]; - input: ts.Expression; -}): ts.Expression => { - const length = IdentifierFactory.access( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.keys"), - undefined, - [props.input], - ), - "length", - ); - const left: ts.Expression | null = - props.config.equals === true && props.dynamic.length === 0 - ? props.config.undefined === true || - props.regular.every((r) => r.meta.isRequired()) - ? ts.factory.createStrictEquality( - ExpressionFactory.number( - props.regular.filter((r) => r.meta.isRequired()).length, - ), - length, - ) - : ts.factory.createCallExpression( - props.context.importer.internal("isBetween"), - [], - [ - length, - ExpressionFactory.number( - props.regular.filter((r) => r.meta.isRequired()).length, - ), - ExpressionFactory.number(props.regular.length), - ], - ) - : null; - if ( - left !== null && - props.config.undefined === false && - props.regular.every((r) => r.meta.isRequired()) - ) - return left; - - const criteria: ts.CallExpression = props.config.entries - ? ts.factory.createCallExpression(props.config.entries, undefined, [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.keys"), - undefined, - [props.input], - ), - check_dynamic_property(props), - ]) - : ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.keys"), - undefined, - [props.input], - ), - props.config.assert ? "every" : "map", - ), - undefined, - [check_dynamic_property(props)], - ); - const right: ts.Expression = (props.config.halt || ((elem) => elem))( - props.config.assert ? criteria : check_everything(criteria), - ); - return left - ? (props.config.undefined - ? ts.factory.createLogicalOr - : ts.factory.createLogicalAnd)(left, right) - : right; -}; - -/** @internal */ -const check_dynamic_property = (props: { - config: check_object.IConfig; - context: ITypiaContext; - regular: IExpressionEntry[]; - dynamic: IExpressionEntry[]; - input: ts.Expression; -}) => { - //---- - // IF CONDITIONS - //---- - // PREPARE ASSETS - const key = ts.factory.createIdentifier("key"); - const value = ts.factory.createIdentifier("value"); - - const statements: ts.Statement[] = []; - const add = (expression: ts.Expression, output: ts.Expression) => - statements.push( - ts.factory.createIfStatement( - expression, - ts.factory.createReturnStatement(output), - ), - ); - const broken = { value: false }; - - // GATHER CONDITIONS - if (props.regular.length) - add(is_regular_property(props.regular), props.config.positive); - statements.push( - StatementFactory.constant({ - name: "value", - value: ts.factory.createElementAccessExpression(props.input, key), - }), - ); - if (props.config.undefined === true) - add( - ts.factory.createStrictEquality( - ts.factory.createIdentifier("undefined"), - value, - ), - props.config.positive, - ); - - for (const entry of props.dynamic) { - const condition: ts.Expression = check_dynamic_key({ - context: props.context, - metadata: entry.key, - input: key, - }); - if (condition.kind === ts.SyntaxKind.TrueKeyword) { - statements.push(ts.factory.createReturnStatement(entry.expression)); - broken.value = true; - break; - } else add(condition, entry.expression); - } - - //---- - // FUNCTION BODY - //---- - // CLOSURE BLOCK - const block: ts.Block = ts.factory.createBlock( - [ - ...statements, - ...(broken.value - ? [] - : [ - ts.factory.createReturnStatement( - props.config.equals === true - ? props.config.superfluous( - value, - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createArrayLiteralExpression( - [ - ts.factory.createTemplateExpression( - ts.factory.createTemplateHead("The property `"), - [ - ts.factory.createTemplateSpan( - ts.factory.createIdentifier("key"), - ts.factory.createTemplateTail( - "` is not defined in the object type.", - ), - ), - ], - ), - ts.factory.createStringLiteral(""), - ts.factory.createStringLiteral( - "Please remove the property next time.", - ), - ], - true, - ), - "join", - ), - undefined, - [ts.factory.createStringLiteral("\n")], - ), - ) - : props.config.positive, - ), - ]), - ], - true, - ); - - // RETURNS - return ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("key")], - undefined, - undefined, - block, - ); -}; - -/** @internal */ -const is_regular_property = (regular: IExpressionEntry[]) => - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createArrayLiteralExpression( - regular.map((entry) => - ts.factory.createStringLiteral(entry.key.getSoleLiteral()!), - ), - ), - "some", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("prop")], - undefined, - undefined, - ts.factory.createStrictEquality( - ts.factory.createIdentifier("key"), - ts.factory.createIdentifier("prop"), - ), - ), - ], - ); diff --git a/packages/core/src/programmers/iterate/check_everything.ts b/packages/core/src/programmers/iterate/check_everything.ts deleted file mode 100644 index 539d2f1a46a..00000000000 --- a/packages/core/src/programmers/iterate/check_everything.ts +++ /dev/null @@ -1,21 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; - -/** @internal */ -export const check_everything = (array: ts.Expression) => - ts.factory.createCallExpression( - IdentifierFactory.access(array, "every"), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("flag", TypeFactory.keyword("boolean"))], - undefined, - undefined, - ts.factory.createIdentifier("flag"), - ), - ], - ); diff --git a/packages/core/src/programmers/iterate/check_native.ts b/packages/core/src/programmers/iterate/check_native.ts deleted file mode 100644 index d6d9f318437..00000000000 --- a/packages/core/src/programmers/iterate/check_native.ts +++ /dev/null @@ -1,23 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ExpressionFactory } from "../../factories/ExpressionFactory"; - -/** @internal */ -export const check_native = (props: { - name: string; - input: ts.Expression; -}): ts.Expression => { - const instanceOf = ExpressionFactory.isInstanceOf(props.name, props.input); - return ATOMIC_LIKE.has(props.name) - ? ts.factory.createLogicalOr( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral(props.name.toLowerCase()), - ts.factory.createTypeOfExpression(props.input), - ), - instanceOf, - ) - : instanceOf; -}; - -/** @internal */ -const ATOMIC_LIKE = new Set(["Boolean", "Number", "String"]); diff --git a/packages/core/src/programmers/iterate/check_number.ts b/packages/core/src/programmers/iterate/check_number.ts deleted file mode 100644 index ebd9d1ea61d..00000000000 --- a/packages/core/src/programmers/iterate/check_number.ts +++ /dev/null @@ -1,105 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { ICheckEntry } from "../helpers/ICheckEntry"; -import { OptionPredicator } from "../helpers/OptionPredicator"; - -/** @internal */ -export const check_number = (props: { - numeric: boolean; - context: ITypiaContext; - atomic: MetadataAtomic; - input: ts.Expression; -}): ICheckEntry => { - const base: ts.BinaryExpression = ts.factory.createStrictEquality( - ts.factory.createStringLiteral("number"), - ts.factory.createTypeOfExpression(props.input), - ); - const addition: ts.Expression | null = - props.numeric === true - ? OptionPredicator.finite(props.context.options) - ? ts.factory.createCallExpression( - ts.factory.createIdentifier("Number.isFinite"), - undefined, - [props.input], - ) - : OptionPredicator.numeric(props.context.options) - ? ts.factory.createLogicalNot( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Number.isNaN"), - undefined, - [props.input], - ), - ) - : null - : null; - const conditions: ICheckEntry.ICondition[][] = check_numeric_type_tags({ - context: props.context, - atomic: props.atomic, - input: props.input, - addition, - }); - return { - expected: props.atomic.getName(), - expression: - addition !== null && conditions.length === 0 - ? ts.factory.createLogicalAnd(base, addition) - : base, - conditions, - }; -}; - -/** @internal */ -const check_numeric_type_tags = (props: { - context: ITypiaContext; - atomic: MetadataAtomic; - addition: ts.Expression | null; - input: ts.Expression; -}): ICheckEntry.ICondition[][] => - props.atomic.tags - .map((row) => row.filter((tag) => !!tag.validate)) - .filter((row) => !!row.length) - .map((row) => [ - ...(props.addition === null - ? [] - : row.some( - (tag) => - tag.kind === "type" && - (tag.value === "int32" || - tag.value === "uint32" || - tag.value === "int64" || - tag.value === "uint64" || - tag.value === "float"), - ) || - row.some( - (tag) => - tag.kind === "multipleOf" && typeof tag.value === "number", - ) || - (row.some( - (tag) => - (tag.kind === "minimum" || tag.kind === "exclusiveMinimum") && - typeof tag.value === "number", - ) && - row.some( - (tag) => - (tag.kind === "maximum" || tag.kind === "exclusiveMaximum") && - typeof tag.value === "number", - )) - ? [] - : [ - { - expected: "number", - expression: props.addition!, - }, - ]), - ...row.map((tag) => ({ - expected: `number & ${tag.name}`, - expression: ExpressionFactory.transpile({ - transformer: props.context.transformer, - importer: props.context.importer, - script: tag.validate!, - })(props.input), - })), - ]); diff --git a/packages/core/src/programmers/iterate/check_object.ts b/packages/core/src/programmers/iterate/check_object.ts deleted file mode 100644 index 54858f7f8b5..00000000000 --- a/packages/core/src/programmers/iterate/check_object.ts +++ /dev/null @@ -1,71 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IExpressionEntry } from "../helpers/IExpressionEntry"; -import { check_dynamic_properties } from "./check_dynamic_properties"; -import { check_everything } from "./check_everything"; - -/** @internal */ -export const check_object = (props: { - config: check_object.IConfig; - context: ITypiaContext; - input: ts.Expression; - entries: IExpressionEntry[]; -}) => { - // PREPARE ASSETS - const regular = props.entries.filter((entry) => entry.key.isSoleLiteral()); - const dynamic = props.entries.filter((entry) => !entry.key.isSoleLiteral()); - const flags: ts.Expression[] = regular.map((entry) => entry.expression); - - // REGULAR WITHOUT DYNAMIC PROPERTIES - if (props.config.equals === false && dynamic.length === 0) - return regular.length === 0 - ? props.config.positive - : reduce({ - config: props.config, - expressions: flags, - }); - - // CHECK DYNAMIC PROPERTIES - flags.push( - check_dynamic_properties({ - config: props.config, - context: props.context, - input: props.input, - regular, - dynamic, - }), - ); - return reduce({ - config: props.config, - expressions: flags, - }); -}; - -/** @internal */ -export namespace check_object { - export interface IConfig { - equals: boolean; - assert: boolean; - undefined: boolean; - halt?: undefined | ((exp: ts.Expression) => ts.Expression); - reduce: (a: ts.Expression, b: ts.Expression) => ts.Expression; - positive: ts.Expression; - superfluous: ( - value: ts.Expression, - description?: ts.Expression, - ) => ts.Expression; - entries?: undefined | ts.Identifier; - } -} - -/** @internal */ -const reduce = (props: { - config: check_object.IConfig; - expressions: ts.Expression[]; -}) => - props.config.assert - ? props.expressions.reduce(props.config.reduce) - : check_everything( - ts.factory.createArrayLiteralExpression(props.expressions), - ); diff --git a/packages/core/src/programmers/iterate/check_string.ts b/packages/core/src/programmers/iterate/check_string.ts deleted file mode 100644 index 2bdc653757e..00000000000 --- a/packages/core/src/programmers/iterate/check_string.ts +++ /dev/null @@ -1,43 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { ICheckEntry } from "../helpers/ICheckEntry"; - -/** @internal */ -export const check_string = (props: { - context: ITypiaContext; - atomic: MetadataAtomic; - input: ts.Expression; -}): ICheckEntry => { - const conditions: ICheckEntry.ICondition[][] = check_string_type_tags(props); - return { - expected: props.atomic.getName(), - expression: ts.factory.createStrictEquality( - ts.factory.createStringLiteral("string"), - ts.factory.createTypeOfExpression(props.input), - ), - conditions, - }; -}; - -/** @internal */ -const check_string_type_tags = (props: { - context: ITypiaContext; - atomic: MetadataAtomic; - input: ts.Expression; -}): ICheckEntry.ICondition[][] => - props.atomic.tags - .map((row) => row.filter((tag) => !!tag.validate)) - .filter((row) => !!row.length) - .map((row) => - row.map((tag) => ({ - expected: `string & ${tag.name}`, - expression: ExpressionFactory.transpile({ - transformer: props.context.transformer, - importer: props.context.importer, - script: tag.validate!, - })(props.input), - })), - ); diff --git a/packages/core/src/programmers/iterate/check_template.ts b/packages/core/src/programmers/iterate/check_template.ts deleted file mode 100644 index 28d8c56f2c3..00000000000 --- a/packages/core/src/programmers/iterate/check_template.ts +++ /dev/null @@ -1,45 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataTemplate } from "../../schemas/metadata/MetadataTemplate"; -import { ICheckEntry } from "../helpers/ICheckEntry"; -import { template_to_pattern } from "./template_to_pattern"; - -/** @internal */ -export const check_template = (props: { - templates: MetadataTemplate[]; - input: ts.Expression; -}): ICheckEntry => { - // TYPEOF STRING & TAGS - const conditions: ts.Expression[] = [ - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("string"), - ts.factory.createTypeOfExpression(props.input), - ), - ]; - - // TEMPLATES - const internal: ts.Expression[] = props.templates.map((tpl) => - ts.factory.createCallExpression( - ts.factory.createIdentifier( - `RegExp(/${template_to_pattern({ - top: true, - template: tpl.row, - })}/).test`, - ), - undefined, - [props.input], - ), - ); - conditions.push( - internal.length === 1 - ? internal[0]! - : internal.reduce((x, y) => ts.factory.createLogicalOr(x, y)), - ); - - // COMBINATION - return { - expression: conditions.reduce((x, y) => ts.factory.createLogicalAnd(x, y)), - conditions: [], - expected: props.templates.map((tpl) => tpl.getName()).join(" | "), - }; -}; diff --git a/packages/core/src/programmers/iterate/check_union_array_like.ts b/packages/core/src/programmers/iterate/check_union_array_like.ts deleted file mode 100644 index 60a59fd1312..00000000000 --- a/packages/core/src/programmers/iterate/check_union_array_like.ts +++ /dev/null @@ -1,329 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataMap } from "../../schemas/metadata/MetadataMap"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { CheckerProgrammer } from "../internal/CheckerProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -/** @internal */ -export const check_union_array_like = < - Origin extends MetadataSchema | MetadataArray | MetadataTuple | MetadataMap, - Category extends MetadataArray | MetadataTuple, - Element, ->(props: { - config: check_union_array_like.IConfig; - accessor: check_union_array_like.IAccessor; - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - definitions: Origin[]; - explore: FeatureProgrammer.IExplore; -}): ts.ArrowFunction => { - // ONLY ONE TYPE - const targets: Array = props.definitions.map( - props.accessor.transform, - ); - if (targets.length === 1) - return ts.factory.createArrowFunction( - undefined, - undefined, - props.parameters, - undefined, - undefined, - props.config.decoder({ - input: props.accessor.array(props.input), - definition: targets[0]!, - explore: props.explore, - }), - ); - - const array = ts.factory.createIdentifier("array"); - const top = ts.factory.createIdentifier("top"); - - const statements: ts.Statement[] = []; - const tupleList: MetadataTuple[] = targets.filter( - (t) => t instanceof MetadataTuple, - ) as MetadataTuple[]; - const arrayList: MetadataArray[] = targets.filter( - (t) => t instanceof MetadataArray, - ) as MetadataArray[]; - - const predicate = (meta: Category): ts.Expression => - ts.factory.createAsExpression( - ts.factory.createArrayLiteralExpression( - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "top", - meta instanceof MetadataArrayType - ? TypeFactory.keyword("any") - : ts.factory.createTypeReferenceNode("any[]"), - ), - ], - TypeFactory.keyword("any"), - undefined, - props.config.checker({ - input: ts.factory.createIdentifier("top"), - definition: props.accessor.element(meta), - explore: { - ...props.explore, - tracable: false, - postfix: meta instanceof MetadataArrayType ? `"[0]"` : "", - }, - container: array, - }), - ), - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "entire", - ts.factory.createTypeReferenceNode("any[]"), - ), - ], - TypeFactory.keyword("any"), - undefined, - props.config.decoder({ - input: ts.factory.createIdentifier("entire"), - definition: meta, - explore: { - ...props.explore, - tracable: true, - }, - }), - ), - ], - true, - ), - ts.factory.createTypeReferenceNode("const"), - ); - const iterate = (props: { - init: string; - from: ts.Expression; - if: ts.IfStatement; - }): ts.ForOfStatement => - ts.factory.createForOfStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration(props.init)], - ts.NodeFlags.Const, - ), - props.from, - props.if, - ); - - if (tupleList.length) - statements.push( - StatementFactory.constant({ - name: "array", - value: props.accessor.array(props.input), - }), - StatementFactory.constant({ - name: "tuplePredicators", - value: ts.factory.createArrayLiteralExpression( - tupleList.map((x) => predicate(x as Category)), - true, - ), - }), - iterate({ - init: "pred", - from: ts.factory.createIdentifier("tuplePredicators"), - if: ts.factory.createIfStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("pred[0]"), - undefined, - [array], - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier(`pred[1]`), - undefined, - [array], - ), - ), - ), - }), - ); - if (arrayList.length) { - if (tupleList.length === 0) - statements.push( - StatementFactory.constant({ - name: "array", - value: props.accessor.array(props.input), - }), - ); - statements.push( - StatementFactory.constant({ - name: "top", - value: props.accessor.front(props.input), - }), - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ExpressionFactory.number(0), - props.accessor.size(props.input), - ), - ts.isReturnStatement(props.config.empty) - ? props.config.empty - : ts.factory.createReturnStatement(props.config.empty), - ), - StatementFactory.constant({ - name: "arrayPredicators", - value: ts.factory.createArrayLiteralExpression( - arrayList.map((x) => predicate(x as Category)), - true, - ), - }), - StatementFactory.constant({ - name: "passed", - value: ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createIdentifier("arrayPredicators"), - "filter", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("pred")], - undefined, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("pred[0]"), - undefined, - [top], - ), - ), - ], - ), - }), - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ExpressionFactory.number(1), - ts.factory.createIdentifier("passed.length"), - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createElementAccessExpression( - ts.factory.createNonNullExpression( - ts.factory.createIdentifier("passed[0]"), - ), - 1, - ), - undefined, - [array], - ), - ), - ts.factory.createIfStatement( - ts.factory.createLessThan( - ExpressionFactory.number(1), - ts.factory.createIdentifier("passed.length"), - ), - iterate({ - init: "pred", - from: ts.factory.createIdentifier("passed"), - if: ts.factory.createIfStatement( - ts.factory.createCallExpression( - IdentifierFactory.access(array, "every"), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "value", - TypeFactory.keyword("any"), - ), - ], - undefined, - undefined, - ts.factory.createStrictEquality( - props.config.success, - ts.factory.createCallExpression( - ts.factory.createIdentifier("pred[0]"), - undefined, - [ts.factory.createIdentifier("value")], - ), - ), - ), - ], - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier(`pred[1]`), - undefined, - [ts.factory.createIdentifier("array")], - ), - ), - ), - }), - ), - ), - ); - } - statements.push( - props.config.failure({ - input: props.input, - expected: `(${targets - .map((t) => props.accessor.name(t, props.accessor.element(t))) - .join(" | ")})`, - explore: props.explore, - }), - ); - return ts.factory.createArrowFunction( - undefined, - undefined, - props.parameters, - undefined, - undefined, - ts.factory.createBlock(statements, true), - ); -}; - -/** @internal */ -export namespace check_union_array_like { - export interface IConfig< - Category extends MetadataArray | MetadataTuple, - Element, - > { - checker(props: { - input: ts.Expression; - definition: Element; - explore: FeatureProgrammer.IExplore; - container: ts.Expression; - }): ts.Expression; - decoder: UnionExplorer.Decoder; - empty: ts.ReturnStatement | ts.Expression; - success: ts.Expression; - failure(props: { - input: ts.Expression; - expected: string; - explore: CheckerProgrammer.IExplore; - }): ts.Statement; - } - - export interface IAccessor< - Origin, - Category extends MetadataArray | MetadataTuple, - Element, - > { - transform(origin: Origin): Category; - element(meta: Category): Element; - name(meta: Category, elem: Element): string; - front(input: ts.Expression): ts.Expression; - array(input: ts.Expression): ts.Expression; - size(input: ts.Expression): ts.Expression; - } -} diff --git a/packages/core/src/programmers/iterate/decode_union_object.ts b/packages/core/src/programmers/iterate/decode_union_object.ts deleted file mode 100644 index b020a1c9e46..00000000000 --- a/packages/core/src/programmers/iterate/decode_union_object.ts +++ /dev/null @@ -1,109 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -/** @internal */ -export const decode_union_object = (props: { - checker: (next: { - input: ts.Expression; - object: MetadataObjectType; - explore: FeatureProgrammer.IExplore; - }) => ts.Expression; - decoder: (next: { - input: ts.Expression; - object: MetadataObjectType; - explore: FeatureProgrammer.IExplore; - }) => ts.Expression; - success: (exp: ts.Expression) => ts.Expression; - escaper: (next: { input: ts.Expression; expected: string }) => ts.Statement; - objects: MetadataObjectType[]; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; -}): ts.CallExpression => - ts.factory.createCallExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - iterate({ - escaper: props.escaper, - input: props.input, - unions: props.objects.map((object) => ({ - type: "object", - is: () => - props.success( - props.checker({ - input: props.input, - explore: props.explore, - object, - }), - ), - value: () => - props.decoder({ - input: props.input, - explore: props.explore, - object, - }), - })), - expected: `(${props.objects.map((t) => t.name).join(" | ")})`, - }), - ), - undefined, - undefined, - ); - -/** @internal */ -const iterate = (props: { - escaper: (next: { input: ts.Expression; expected: string }) => ts.Statement; - unions: IUnion[]; - expected: string; - input: ts.Expression; -}) => { - interface IBranch { - condition: null | ts.Expression; - value: ts.Expression; - } - const branches: IBranch[] = []; - - for (const u of props.unions) { - const condition: ts.Expression = u.is(); - if (condition.kind === ts.SyntaxKind.TrueKeyword) { - branches.push({ - condition: null, - value: u.value(), - }); - break; - } - branches.push({ - condition, - value: u.value(), - }); - } - if (branches.length === 0) - return ts.factory.createBlock([props.escaper(props)], true); - else if (branches.length === 1 && branches[0]!.condition === null) - return branches[0]!.value; - - const statements: ts.Statement[] = branches.map((b) => - b.condition !== null - ? ts.factory.createIfStatement( - b.condition, - ts.factory.createReturnStatement(b.value), - undefined, - ) - : ts.factory.createReturnStatement(b.value), - ); - if (branches.at(-1)!.condition !== null) - statements.push(props.escaper(props)); - return ts.factory.createBlock(statements, true); -}; - -/** @internal */ -interface IUnion { - type: string; - is: () => ts.Expression; - value: () => ts.Expression; -} diff --git a/packages/core/src/programmers/iterate/feature_object_entries.ts b/packages/core/src/programmers/iterate/feature_object_entries.ts deleted file mode 100644 index 6d206c698db..00000000000 --- a/packages/core/src/programmers/iterate/feature_object_entries.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -/** @internal */ -export const feature_object_entries = (props: { - config: Pick, "decoder" | "path" | "trace">; - context: ITypiaContext; - object: MetadataObjectType; - input: ts.Expression; - from?: "object" | "top" | "array"; -}) => - props.object.properties.map((next) => { - const sole: string | null = next.key.getSoleLiteral(); - const propInput = - sole === null - ? ts.factory.createIdentifier("value") - : NamingConvention.variable(sole) - ? ts.factory.createPropertyAccessExpression( - props.input, - ts.factory.createIdentifier(sole), - ) - : ts.factory.createElementAccessExpression( - props.input, - ts.factory.createStringLiteral(sole), - ); - return { - input: propInput, - key: next.key, - meta: next.value, - expression: props.config.decoder({ - input: propInput, - metadata: next.value, - explore: { - tracable: props.config.path || props.config.trace, - source: "function", - from: props.from ?? "object", - postfix: props.config.trace - ? sole !== null - ? IdentifierFactory.postfix(sole) - : (() => { - props.context.importer.internal(ACCESSOR); - return `${props.context.importer.getInternalText(ACCESSOR)}(key)`; - })() - : "", - }, - }), - }; - }); - -const ACCESSOR = "accessExpressionAsString"; diff --git a/packages/core/src/programmers/iterate/json_schema_alias.ts b/packages/core/src/programmers/iterate/json_schema_alias.ts deleted file mode 100644 index f7c5a008ec3..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_alias.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataAlias } from "../../schemas/metadata/MetadataAlias"; -import { json_schema_description } from "./json_schema_description"; -import { json_schema_jsDocTags } from "./json_schema_jsDocTags"; -import { json_schema_object } from "./json_schema_object"; -import { json_schema_station } from "./json_schema_station"; -import { json_schema_title } from "./json_schema_title"; - -export const json_schema_alias = (props: { - blockNever: BlockNever; - components: OpenApi.IComponents; - alias: MetadataAlias; -}): OpenApi.IJsonSchema.IReference[] => { - if ( - props.alias.type.value.size() === 1 && - props.alias.type.value.objects.length === 1 - ) - return json_schema_object({ - components: props.components, - object: props.alias.type.value.objects[0]!, - }) as OpenApi.IJsonSchema.IReference[]; - - const $ref: string = `#/components/schemas/${props.alias.type.name}`; - if (props.components.schemas?.[props.alias.type.name] === undefined) { - // TEMPORARY ASSIGNMENT - props.components.schemas ??= {}; - props.components.schemas[props.alias.type.name] = {}; - - // GENERATE SCHEMA - const schema: OpenApi.IJsonSchema | null = json_schema_station({ - blockNever: props.blockNever, - components: props.components, - attribute: { - deprecated: - props.alias.type.jsDocTags.some((tag) => tag.name === "deprecated") || - undefined, - title: json_schema_title(props.alias.type), - description: json_schema_description(props.alias.type), - }, - metadata: props.alias.type.value, - }); - if (schema !== null) { - json_schema_jsDocTags(schema, props.alias.type.jsDocTags); - Object.assign(props.components.schemas[props.alias.type.name]!, schema); - } - } - return [{ $ref }]; -}; diff --git a/packages/core/src/programmers/iterate/json_schema_array.ts b/packages/core/src/programmers/iterate/json_schema_array.ts deleted file mode 100644 index a82a47d8ce0..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_array.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { json_schema_plugin } from "./json_schema_plugin"; -import { json_schema_station } from "./json_schema_station"; - -export const json_schema_array = (props: { - components: OpenApi.IComponents; - array: MetadataArray; -}): Array => { - const factory = (): OpenApi.IJsonSchema.IArray[] => - json_schema_plugin({ - schema: { - type: "array", - items: json_schema_station({ - blockNever: false, - components: props.components, - metadata: props.array.type.value, - attribute: {}, - }), - } satisfies OpenApi.IJsonSchema.IArray, - tags: props.array.tags, - }); - if (props.array.type.recursive === true) { - const out = () => [ - { - $ref: `#/components/schemas/${props.array.type.name}`, - }, - ]; - if (props.components.schemas?.[props.array.type.name] !== undefined) - return out(); - - props.components.schemas ??= {}; - props.components.schemas[props.array.type.name] ??= {}; - - const oneOf: OpenApi.IJsonSchema.IArray[] = factory(); - Object.assign( - props.components.schemas[props.array.type.name]!, - oneOf.length === 1 ? oneOf[0] : { oneOf }, - ); - return out(); - } - return factory(); -}; diff --git a/packages/core/src/programmers/iterate/json_schema_bigint.ts b/packages/core/src/programmers/iterate/json_schema_bigint.ts deleted file mode 100644 index 16c9a40ff18..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_bigint.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { json_schema_plugin } from "./json_schema_plugin"; - -export const json_schema_bigint = ( - atomic: MetadataAtomic, -): OpenApi.IJsonSchema.IInteger[] => - json_schema_plugin({ - schema: { - type: "integer", - } satisfies OpenApi.IJsonSchema.IInteger, - tags: atomic.tags, - }); diff --git a/packages/core/src/programmers/iterate/json_schema_boolean.ts b/packages/core/src/programmers/iterate/json_schema_boolean.ts deleted file mode 100644 index 1b72376c780..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_boolean.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { json_schema_plugin } from "./json_schema_plugin"; - -export const json_schema_boolean = ( - atomic: MetadataAtomic, -): OpenApi.IJsonSchema.IBoolean[] => - json_schema_plugin({ - schema: { - type: "boolean", - } satisfies OpenApi.IJsonSchema.IBoolean, - tags: atomic.tags, - }); diff --git a/packages/core/src/programmers/iterate/json_schema_constant.ts b/packages/core/src/programmers/iterate/json_schema_constant.ts deleted file mode 100644 index f77ca32cbaa..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_constant.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataConstant } from "../../schemas/metadata/MetadataConstant"; -import { json_schema_description } from "./json_schema_description"; -import { json_schema_plugin } from "./json_schema_plugin"; -import { json_schema_title } from "./json_schema_title"; - -export const json_schema_constant = ( - constant: MetadataConstant, -): OpenApi.IJsonSchema.IConstant[] => - constant.values - .map((value) => - json_schema_plugin({ - schema: { - const: - typeof value.value === "bigint" - ? Number(value.value) - : (value.value as boolean | number | string), - title: json_schema_title(value), - description: json_schema_description(value), - } satisfies OpenApi.IJsonSchema.IConstant, - tags: value.tags ?? [], - }), - ) - .flat(); diff --git a/packages/core/src/programmers/iterate/json_schema_description.ts b/packages/core/src/programmers/iterate/json_schema_description.ts deleted file mode 100644 index 959a5c4edaf..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_description.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IJsDocTagInfo } from "@typia/interface"; - -export const json_schema_description = (props: { - description?: string | null | undefined; - jsDocTags?: IJsDocTagInfo[]; -}): string | undefined => - props.jsDocTags - ?.find((tag) => tag.name === "description") - ?.text?.[0]?.text?.split("\r\n") - .join("\n") ?? - props.description ?? - undefined; diff --git a/packages/core/src/programmers/iterate/json_schema_discriminator.ts b/packages/core/src/programmers/iterate/json_schema_discriminator.ts deleted file mode 100644 index a2c79ec5bb4..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_discriminator.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { UnionPredicator } from "../helpers/UnionPredicator"; - -export const json_schema_discriminator = ( - metadata: MetadataSchema, -): OpenApi.IJsonSchema.IOneOf.IDiscriminator | undefined => { - if ( - metadata.size() === 0 || - metadata.size() !== metadata.objects.length || - metadata.objects.some((o) => o.type.isLiteral()) === true - ) - return undefined; - const specialized: UnionPredicator.ISpecialized[] = UnionPredicator.object( - metadata.objects.map((o) => o.type), - ); - const meet: boolean = - specialized.length === metadata.objects.length && - specialized.every( - (s) => s.property.key.isSoleLiteral() && s.property.value.isSoleLiteral(), - ) && - new Set(specialized.map((s) => s.property.key.getSoleLiteral())).size === 1; - if (meet === false) return undefined; - return { - propertyName: specialized[0]!.property.key.getSoleLiteral()!, - mapping: Object.fromEntries( - specialized.map((s) => [ - s.property.value.getSoleLiteral()!, - `#/components/schemas/${s.object.name}`, - ]), - ), - }; -}; diff --git a/packages/core/src/programmers/iterate/json_schema_escaped.ts b/packages/core/src/programmers/iterate/json_schema_escaped.ts deleted file mode 100644 index 686cdc928db..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_escaped.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { OpenApi } from "@typia/interface"; -import { OpenApiTypeChecker } from "@typia/utils"; - -import { MetadataEscaped } from "../../schemas/metadata/MetadataEscaped"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { json_schema_station } from "./json_schema_station"; - -/** @internal */ -export const json_schema_escaped = (props: { - components: OpenApi.IComponents; - escaped: MetadataEscaped; -}): OpenApi.IJsonSchema[] => { - const output: OpenApi.IJsonSchema | null = json_schema_station({ - blockNever: false, - components: props.components, - metadata: props.escaped.returns, - attribute: {}, - }); - if (output === null) return []; - - if ( - is_date({ - visited: new Set(), - metadata: props.escaped.original, - }) - ) { - const string: OpenApi.IJsonSchema.IString | undefined = - OpenApiTypeChecker.isString(output) - ? output - : OpenApiTypeChecker.isOneOf(output) - ? (output.oneOf.find( - OpenApiTypeChecker.isString, - ) as OpenApi.IJsonSchema.IString) - : undefined; - if ( - string !== undefined && - string.format !== "date" && - string.format !== "date-time" - ) - string.format = "date-time"; - } - return OpenApiTypeChecker.isOneOf(output) - ? (output.oneOf as OpenApi.IJsonSchema[]) - : [output]; -}; - -/** @internal */ -const is_date = (props: { - visited: Set; - metadata: MetadataSchema; -}): boolean => { - if (props.visited.has(props.metadata)) return false; - props.visited.add(props.metadata); - - return ( - props.metadata.natives.some((native) => native.name === "Date") || - props.metadata.arrays.some((array) => - is_date({ - visited: props.visited, - metadata: array.type.value, - }), - ) || - props.metadata.tuples.some((tuple) => - tuple.type.elements.some((e) => - is_date({ - visited: props.visited, - metadata: e, - }), - ), - ) || - props.metadata.aliases.some((alias) => - is_date({ - visited: props.visited, - metadata: alias.type.value, - }), - ) - ); -}; diff --git a/packages/core/src/programmers/iterate/json_schema_jsDocTags.ts b/packages/core/src/programmers/iterate/json_schema_jsDocTags.ts deleted file mode 100644 index 946031b477c..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_jsDocTags.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { IJsDocTagInfo, OpenApi } from "@typia/interface"; - -export const json_schema_jsDocTags = ( - schema: Schema, - jsDocTags: IJsDocTagInfo[], -): Schema => { - for (const tag of jsDocTags) { - if (tag.name.startsWith("x-") === false) continue; - - const value: string | undefined = tag.text - ?.filter((s) => s.kind === "text") - .map((s) => s.text.trim().split("\r\n").join("\n"))[0]; - if (value === undefined) continue; - else (schema as any)[tag.name] = cast(value); - } - return schema; -}; - -const cast = (value: string): string | number | boolean | null => { - if (value === "true") return true; - if (value === "false") return false; - const num: number = Number(value); - if (Number.isNaN(num) === false) return num; - return value === "null" ? null : value; -}; diff --git a/packages/core/src/programmers/iterate/json_schema_native.ts b/packages/core/src/programmers/iterate/json_schema_native.ts deleted file mode 100644 index e207e19c068..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_native.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataNative } from "../../schemas/metadata/MetadataNative"; -import { json_schema_plugin } from "./json_schema_plugin"; - -export const json_schema_native = (props: { - components: OpenApi.IComponents; - native: MetadataNative; -}): OpenApi.IJsonSchema[] => { - if (props.native.name === "Blob" || props.native.name === "File") - return json_schema_plugin({ - schema: { - type: "string", - format: "binary", - }, - tags: props.native.tags, - }); - if (props.components.schemas?.[props.native.name] === undefined) { - props.components.schemas ??= {}; - props.components.schemas[props.native.name] ??= { - type: "object", - properties: {}, - required: [], - }; - } - return json_schema_plugin({ - schema: { - $ref: `#/components/schemas/${props.native.name}`, - }, - tags: props.native.tags, - }); -}; diff --git a/packages/core/src/programmers/iterate/json_schema_number.ts b/packages/core/src/programmers/iterate/json_schema_number.ts deleted file mode 100644 index 6a8d9526aba..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_number.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { json_schema_plugin } from "./json_schema_plugin"; - -export const json_schema_number = ( - atomic: MetadataAtomic, -): Array => - json_schema_plugin({ - schema: { - type: "number", - } satisfies OpenApi.IJsonSchema.INumber, - tags: atomic.tags, - }); diff --git a/packages/core/src/programmers/iterate/json_schema_object.ts b/packages/core/src/programmers/iterate/json_schema_object.ts deleted file mode 100644 index df5fc53178d..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_object.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { IJsDocTagInfo, OpenApi } from "@typia/interface"; - -import { CommentFactory } from "../../factories/CommentFactory"; -import { MetadataObject } from "../../schemas/metadata/MetadataObject"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { PatternUtil } from "../../utils/PatternUtil"; -import { json_schema_description } from "./json_schema_description"; -import { json_schema_jsDocTags } from "./json_schema_jsDocTags"; -import { json_schema_plugin } from "./json_schema_plugin"; -import { json_schema_station } from "./json_schema_station"; -import { json_schema_title } from "./json_schema_title"; -import { metadata_to_pattern } from "./metadata_to_pattern"; - -/** @internal */ -export const json_schema_object = (props: { - components: OpenApi.IComponents; - object: MetadataObject; -}): Array => - json_schema_plugin({ - schema: emplace_object(props), - tags: props.object.tags, - }); - -const emplace_object = (props: { - components: OpenApi.IComponents; - object: MetadataObject; -}): OpenApi.IJsonSchema.IReference | OpenApi.IJsonSchema.IObject => { - if (props.object.type.isLiteral() === true) - return create_object_schema(props); - - const key: string = props.object.type.name; - const $ref: string = `#/components/schemas/${key}`; - if (props.components.schemas?.[key] !== undefined) return { $ref }; - - const lazy: OpenApi.IJsonSchema = {}; - props.components.schemas ??= {}; - props.components.schemas[key] = lazy; - Object.assign(lazy, create_object_schema(props)); - return { $ref }; -}; - -/** @internal */ -const create_object_schema = (props: { - components: OpenApi.IComponents; - object: MetadataObject; -}): OpenApi.IJsonSchema.IObject => { - // ITERATE PROPERTIES - const properties: Record = {}; - const extraMeta: ISuperfluous = { - patternProperties: {}, - additionalProperties: undefined, - }; - const required: string[] = []; - - for (const property of props.object.type.properties) { - if ( - // FUNCTIONAL TYPE - property.value.functions.length && - property.value.nullable === false && - property.value.isRequired() === true && - property.value.size() === 0 - ) - continue; - else if ( - property.jsDocTags.find( - (tag) => - tag.name === "hidden" || - tag.name === "ignore" || - tag.name === "internal", - ) - ) - continue; // THE HIDDEN/IGNORE/INTERNAL TAGS - - const value: OpenApi.IJsonSchema | null = json_schema_station({ - blockNever: true, - components: props.components, - attribute: { - deprecated: - property.jsDocTags.some((tag) => tag.name === "deprecated") || - undefined, - title: json_schema_title(property), - description: json_schema_description(property), - readOnly: property.mutability === "readonly" ? true : undefined, - }, - metadata: property.value, - }); - if (value === null) continue; - else json_schema_jsDocTags(value, property.jsDocTags); - - const key: string | null = property.key.getSoleLiteral(); - if (key !== null) { - properties[key] = value; - if (property.value.isRequired() === true) required.push(key); - } else { - const pattern: string = metadata_to_pattern({ - top: true, - metadata: property.key, - }); - if (pattern === PatternUtil.STRING) - extraMeta.additionalProperties = [property.value, value]; - else extraMeta.patternProperties[pattern] = [property.value, value]; - } - } - - return json_schema_jsDocTags( - { - type: "object", - properties, - required, - title: (() => { - const info: IJsDocTagInfo | undefined = - props.object.type.jsDocTags.find((tag) => tag.name === "title"); - return info?.text?.length ? CommentFactory.merge(info.text) : undefined; - })(), - description: json_schema_description(props.object.type), - additionalProperties: join({ - components: props.components, - extra: extraMeta, - }), - }, - props.object.type.jsDocTags, - ); -}; - -/** @internal */ -const join = (props: { - components: OpenApi.IComponents; - extra: ISuperfluous; -}): OpenApi.IJsonSchema | undefined => { - // LIST UP METADATA - const elements: [MetadataSchema, OpenApi.IJsonSchema][] = Object.values( - props.extra.patternProperties || {}, - ); - if (props.extra.additionalProperties) - elements.push(props.extra.additionalProperties); - - // SHORT RETURN - if (elements.length === 0) return undefined; - else if (elements.length === 1) return elements[0]![1]!; - - // MERGE METADATA AND GENERATE VULNERABLE SCHEMA - const metadata: MetadataSchema = elements - .map((tuple) => tuple[0]) - .reduce((x, y) => MetadataSchema.merge(x, y)); - return ( - json_schema_station({ - blockNever: true, - components: props.components, - attribute: {}, - metadata, - }) ?? undefined - ); -}; - -/** @internal */ -interface ISuperfluous { - additionalProperties?: undefined | [MetadataSchema, OpenApi.IJsonSchema]; - patternProperties: Record; -} diff --git a/packages/core/src/programmers/iterate/json_schema_plugin.ts b/packages/core/src/programmers/iterate/json_schema_plugin.ts deleted file mode 100644 index 8ace107c047..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_plugin.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IMetadataTypeTag, OpenApi } from "@typia/interface"; - -export const json_schema_plugin = (props: { - schema: Schema; - tags: IMetadataTypeTag[][]; -}): Schema[] => { - const plugins: IMetadataTypeTag[][] = props.tags - .map((row) => row.filter((t) => t.schema !== undefined)) - .filter((row) => row.length !== 0); - if (plugins.length === 0) return [props.schema]; - return plugins.map((row) => { - const base: Schema = { ...props.schema }; - for (const tag of row) Object.assign(base, tag.schema); - return base; - }); -}; diff --git a/packages/core/src/programmers/iterate/json_schema_station.ts b/packages/core/src/programmers/iterate/json_schema_station.ts deleted file mode 100644 index 9fa80ac889b..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_station.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { IJsonSchemaAttribute, OpenApi } from "@typia/interface"; -import { OpenApiExclusiveEmender } from "@typia/utils"; - -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { MetadataNative } from "../../schemas/metadata/MetadataNative"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { AtomicPredicator } from "../helpers/AtomicPredicator"; -import { json_schema_alias } from "./json_schema_alias"; -import { json_schema_array } from "./json_schema_array"; -import { json_schema_bigint } from "./json_schema_bigint"; -import { json_schema_boolean } from "./json_schema_boolean"; -import { json_schema_constant } from "./json_schema_constant"; -import { json_schema_discriminator } from "./json_schema_discriminator"; -import { json_schema_escaped } from "./json_schema_escaped"; -import { json_schema_native } from "./json_schema_native"; -import { json_schema_number } from "./json_schema_number"; -import { json_schema_object } from "./json_schema_object"; -import { json_schema_string } from "./json_schema_string"; -import { json_schema_templates } from "./json_schema_template"; -import { json_schema_tuple } from "./json_schema_tuple"; - -export const json_schema_station = (props: { - blockNever: BlockNever; - components: OpenApi.IComponents; - attribute: IJsonSchemaAttribute; - metadata: MetadataSchema; -}): BlockNever extends true - ? OpenApi.IJsonSchema | null - : OpenApi.IJsonSchema => { - if (props.metadata.any === true) - return { - ...props.attribute, - type: undefined, - }; - - //---- - // GATHER UNION SCHEMAS - //---- - const union: OpenApi.IJsonSchema[] = []; - const insert = (schema: OpenApi.IJsonSchema) => union.push(schema); - - // NULLABLE - if (props.metadata.nullable === true) - insert({ - type: "null", - }); - - // toJSON() METHOD - if (props.metadata.escaped !== null) - json_schema_escaped({ - components: props.components, - escaped: props.metadata.escaped, - }).forEach(insert); - - // ATOMIC TYPES - if ( - props.metadata.templates.length && - AtomicPredicator.template(props.metadata) - ) - json_schema_templates(props.metadata).forEach(insert); - for (const constant of props.metadata.constants) - if ( - AtomicPredicator.constant({ - metadata: props.metadata, - name: constant.type, - }) === false - ) - continue; - else json_schema_constant(constant).forEach(insert); - for (const a of props.metadata.atomics) - if (a.type === "boolean") json_schema_boolean(a).forEach(insert); - else if (a.type === "bigint") json_schema_bigint(a).forEach(insert); - else if (a.type === "number") - json_schema_number(a).map(OpenApiExclusiveEmender.emend).forEach(insert); - else if (a.type === "string") json_schema_string(a).forEach(insert); - - // ARRAY - for (const array of props.metadata.arrays) - json_schema_array({ - components: props.components, - array, - }).forEach(insert); - - // TUPLE - for (const tuple of props.metadata.tuples) - insert( - json_schema_tuple({ - components: props.components, - tuple, - }), - ); - - // NATIVES - for (const native of props.metadata.natives) - if (AtomicPredicator.native(native.name)) { - const type: string = native.name.toLowerCase(); - if (props.metadata.atomics.some((a) => a.type === type)) continue; - else if (type === "boolean") - json_schema_boolean( - MetadataAtomic.create({ - type: "boolean", - tags: [], - }), - ).map(insert); - else if (type === "bigint") - json_schema_bigint( - MetadataAtomic.create({ - type: "bigint", - tags: [], - }), - ).map(insert); - else if (type === "number") - json_schema_number( - MetadataAtomic.create({ - type: "number", - tags: [], - }), - ).map(insert); - else if (type === "string") - json_schema_string( - MetadataAtomic.create({ - type: "string", - tags: [], - }), - ).map(insert); - } else - json_schema_native({ - components: props.components, - native, - }).forEach(insert); - if (props.metadata.sets.length) - json_schema_native({ - native: MetadataNative.create({ - name: "Set", - tags: [], - }), - components: props.components, - }).forEach(insert); - if (props.metadata.maps.length) - json_schema_native({ - native: MetadataNative.create({ - name: "Map", - tags: [], - }), - components: props.components, - }).forEach(insert); - - // OBJECT - for (const object of props.metadata.objects) - json_schema_object({ - components: props.components, - object, - }).forEach(insert); - - // ALIASES - for (const alias of props.metadata.aliases) - json_schema_alias({ - alias, - blockNever: props.blockNever, - components: props.components, - }).forEach(insert); - - //---- - // RETURNS - //---- - if (union.length === 0 && props.blockNever === true) return null!; - const schema: OpenApi.IJsonSchema = - union.length === 0 - ? { type: undefined } - : union.length === 1 - ? union[0]! - : { - oneOf: union, - discriminator: json_schema_discriminator(props.metadata), - }; - return { - ...schema, - ...props.attribute, - title: props.attribute.title ?? schema.title, - description: props.attribute.description ?? schema.description, - deprecated: props.attribute.deprecated ?? schema.deprecated, - }; -}; diff --git a/packages/core/src/programmers/iterate/json_schema_string.ts b/packages/core/src/programmers/iterate/json_schema_string.ts deleted file mode 100644 index f938f8488f2..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_string.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { json_schema_plugin } from "./json_schema_plugin"; - -export const json_schema_string = ( - atomic: MetadataAtomic, -): OpenApi.IJsonSchema[] => - json_schema_plugin({ - schema: { - type: "string", - } satisfies OpenApi.IJsonSchema, - tags: atomic.tags, - }); diff --git a/packages/core/src/programmers/iterate/json_schema_template.ts b/packages/core/src/programmers/iterate/json_schema_template.ts deleted file mode 100644 index 3ecdd9adccb..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_template.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { IMetadataTypeTag, OpenApi } from "@typia/interface"; - -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataTemplate } from "../../schemas/metadata/MetadataTemplate"; -import { json_schema_plugin } from "./json_schema_plugin"; -import { metadata_to_pattern } from "./metadata_to_pattern"; - -export const json_schema_templates = ( - metadata: MetadataSchema, -): OpenApi.IJsonSchema[] => { - const pureTemplates: MetadataTemplate[] = metadata.templates.filter( - (t) => isPure(t.tags ?? []) === true, - ); - const taggedTemplates: MetadataTemplate[] = metadata.templates.filter( - (t) => isPure(t.tags ?? []) === false, - ); - - const output: OpenApi.IJsonSchema[] = []; - if (pureTemplates.length) - output.push({ - type: "string", - pattern: metadata_to_pattern({ - top: true, - metadata: MetadataSchema.create({ - ...MetadataSchema.initialize(), - templates: pureTemplates, - }), - }), - }); - for (const tpl of taggedTemplates) - output.push( - ...json_schema_plugin({ - schema: { - type: "string", - pattern: metadata_to_pattern({ - top: false, - metadata: MetadataSchema.create({ - ...MetadataSchema.initialize(), - templates: [tpl], - }), - }), - }, - tags: tpl.tags ?? [], - }), - ); - return output; -}; - -const isPure = (matrix: IMetadataTypeTag[][]) => - matrix.every((tags) => filter(tags).length === 0); - -const filter = (tags: IMetadataTypeTag[]) => - tags.filter((t) => t.schema !== undefined); diff --git a/packages/core/src/programmers/iterate/json_schema_title.ts b/packages/core/src/programmers/iterate/json_schema_title.ts deleted file mode 100644 index da7ca0b158d..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_title.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IJsDocTagInfo } from "@typia/interface"; - -import { CommentFactory } from "../../factories/CommentFactory"; - -export const json_schema_title = (schema: { - description?: string | null | undefined; - jsDocTags?: IJsDocTagInfo[] | undefined; -}): string | undefined => { - const info: IJsDocTagInfo | undefined = schema.jsDocTags?.find( - (tag) => tag.name === "title", - ); - return !!info?.text?.length ? CommentFactory.merge(info.text) : undefined; -}; diff --git a/packages/core/src/programmers/iterate/json_schema_tuple.ts b/packages/core/src/programmers/iterate/json_schema_tuple.ts deleted file mode 100644 index a41c4f113b5..00000000000 --- a/packages/core/src/programmers/iterate/json_schema_tuple.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { OpenApi } from "@typia/interface"; - -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { json_schema_station } from "./json_schema_station"; - -export const json_schema_tuple = (props: { - components: OpenApi.IComponents; - tuple: MetadataTuple; -}): OpenApi.IJsonSchema.ITuple => { - const tail: MetadataSchema | null = - props.tuple.type.elements.at(-1)?.rest ?? null; - const prefixItems: MetadataSchema[] = props.tuple.type.isRest() - ? props.tuple.type.elements.slice(0, -1) - : props.tuple.type.elements; - return { - type: "array", - prefixItems: prefixItems.map((metadata) => - json_schema_station({ - blockNever: false, - components: props.components, - metadata, - attribute: {}, - }), - ), - additionalItems: tail - ? json_schema_station({ - blockNever: false, - components: props.components, - metadata: tail, - attribute: {}, - }) - : false, - }; -}; diff --git a/packages/core/src/programmers/iterate/metadata_to_pattern.ts b/packages/core/src/programmers/iterate/metadata_to_pattern.ts deleted file mode 100644 index ab18c24c9f7..00000000000 --- a/packages/core/src/programmers/iterate/metadata_to_pattern.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { PatternUtil } from "../../utils/PatternUtil"; -import { template_to_pattern } from "./template_to_pattern"; - -/** @internal */ -export const metadata_to_pattern = (props: { - top: boolean; - metadata: MetadataSchema; -}): string => { - if (props.metadata.atomics.find((a) => a.type === "string") !== undefined) - return "(.*)"; - - const values: string[] = props.metadata.constants - .map((c) => { - if (c.type !== "string") return c.values.map((v) => v.toString()); - return (c.values.map((v) => v.value) as string[]).map((str) => - PatternUtil.escape(str), - ); - }) - .flat(); - for (const a of props.metadata.atomics) - if (a.type === "number" || a.type === "bigint") - values.push(PatternUtil.NUMBER); - else if (a.type === "boolean") values.push(PatternUtil.BOOLEAN); - for (const { row } of props.metadata.templates) - values.push( - "(" + - template_to_pattern({ - top: false, - template: row, - }) + - ")", - ); - - const pattern: string = - values.length === 1 ? values[0]! : "(" + values.join("|") + ")"; - return props.top ? PatternUtil.fix(pattern) : pattern; -}; diff --git a/packages/core/src/programmers/iterate/postfix_of_tuple.ts b/packages/core/src/programmers/iterate/postfix_of_tuple.ts deleted file mode 100644 index 34338703371..00000000000 --- a/packages/core/src/programmers/iterate/postfix_of_tuple.ts +++ /dev/null @@ -1,3 +0,0 @@ -/** @internal */ -export const postfix_of_tuple = (str: string): string => - str.endsWith('"') ? str.slice(0, -1) : `${str} + "`; diff --git a/packages/core/src/programmers/iterate/prune_object_properties.ts b/packages/core/src/programmers/iterate/prune_object_properties.ts deleted file mode 100644 index a68fcc7c9a0..00000000000 --- a/packages/core/src/programmers/iterate/prune_object_properties.ts +++ /dev/null @@ -1,68 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { metadata_to_pattern } from "./metadata_to_pattern"; - -/** @internal */ -export const prune_object_properties = (object: MetadataObjectType) => { - const input: ts.Expression = ts.factory.createIdentifier("input"); - const key: ts.Expression = ts.factory.createIdentifier("key"); - - const condition: ts.Expression[] = object.properties.map((prop) => { - const name: string | null = prop.key.getSoleLiteral(); - if (name !== null) - return ts.factory.createStrictEquality( - ts.factory.createStringLiteral(name), - ts.factory.createIdentifier("key"), - ); - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - `RegExp(/${metadata_to_pattern({ - top: true, - metadata: prop.key, - })}/).test`, - ), - undefined, - [key], - ); - }); - - const statements: ts.Statement[] = []; - if (condition.length) - statements.push( - ts.factory.createIfStatement( - condition.reduce((a, b) => ts.factory.createLogicalOr(a, b)), - ts.factory.createContinueStatement(), - ), - ); - statements.push( - ts.factory.createExpressionStatement( - ts.factory.createDeleteExpression( - ts.factory.createElementAccessExpression(input, key), - ), - ), - ); - - return ts.factory.createForOfStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier("key"), - undefined, - undefined, - undefined, - ), - ], - ts.NodeFlags.Const, - ), - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.keys"), - undefined, - [input], - ), - statements.length === 1 - ? statements[0]! - : ts.factory.createBlock(statements, true), - ); -}; diff --git a/packages/core/src/programmers/iterate/stringify_dynamic_properties.ts b/packages/core/src/programmers/iterate/stringify_dynamic_properties.ts deleted file mode 100644 index e752102baab..00000000000 --- a/packages/core/src/programmers/iterate/stringify_dynamic_properties.ts +++ /dev/null @@ -1,157 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { TemplateFactory } from "../../factories/TemplateFactory"; -import { IExpressionEntry } from "../helpers/IExpressionEntry"; -import { metadata_to_pattern } from "./metadata_to_pattern"; - -/** @internal */ -export const stringify_dynamic_properties = ( - dynamic: IExpressionEntry[], - regular: string[], -): ts.Expression => { - // BASIC STATMEMENT, CHECK UNDEFINED - const statements: ts.Statement[] = [ - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ts.factory.createIdentifier("undefined"), - ts.factory.createIdentifier("value"), - ), - ts.factory.createReturnStatement(ts.factory.createStringLiteral("")), - ), - ]; - - // PREPARE RETURN FUNCTION - const output = () => { - const mapped = ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.entries"), - undefined, - [ts.factory.createIdentifier("input")], - ), - "map", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - ts.factory.createArrayBindingPattern([ - ts.factory.createBindingElement(undefined, undefined, "key"), - ts.factory.createBindingElement(undefined, undefined, "value"), - ]), - ts.factory.createTypeReferenceNode("[string, any]"), - ), - ], - undefined, - undefined, - ts.factory.createBlock(statements), - ), - ], - ); - const filtered = ts.factory.createCallExpression( - IdentifierFactory.access(mapped, "filter"), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("str")], - undefined, - undefined, - ts.factory.createStrictInequality( - ts.factory.createStringLiteral(""), - ts.factory.createIdentifier("str"), - ), - ), - ], - ); - return ts.factory.createCallExpression( - IdentifierFactory.access(filtered, "join"), - undefined, - [ts.factory.createStringLiteral(",")], - ); - }; - - // WHEN REGULAR PROPERTY EXISTS - if (regular.length) - statements.push( - ts.factory.createIfStatement( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createArrayLiteralExpression( - regular.map((key) => ts.factory.createStringLiteral(key)), - ), - "some", - ), - undefined, - [ - ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("regular")], - undefined, - undefined, - ts.factory.createStrictEquality( - ts.factory.createIdentifier("regular"), - ts.factory.createIdentifier("key"), - ), - ), - ], - ), - ts.factory.createReturnStatement(ts.factory.createStringLiteral("")), - ), - ); - - // ONLY STRING TYPED KEY EXISTS - const simple: boolean = - dynamic.length === 1 && - dynamic[0]!.key.size() === 1 && - dynamic[0]!.key.atomics[0]?.type === "string"; - if (simple === true) { - statements.push(stringify(dynamic[0]!)); - return output(); - } - - // COMPOSITE TEMPLATE LITERAL TYPES - for (const entry of dynamic) { - const condition: ts.IfStatement = ts.factory.createIfStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - `RegExp(/${metadata_to_pattern({ - top: true, - metadata: entry.key, - })}/).test`, - ), - undefined, - [ts.factory.createIdentifier("key")], - ), - stringify(entry), - ); - statements.push(condition); - } - statements.push( - ts.factory.createReturnStatement(ts.factory.createStringLiteral("")), - ); - - return output(); -}; - -/** @internal */ -const stringify = ( - entry: IExpressionEntry, -): ts.ReturnStatement => - ts.factory.createReturnStatement( - TemplateFactory.generate([ - ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.stringify"), - [], - [ts.factory.createIdentifier("key")], - ), - ts.factory.createStringLiteral(":"), - entry.expression, - ]), - ); diff --git a/packages/core/src/programmers/iterate/stringify_native.ts b/packages/core/src/programmers/iterate/stringify_native.ts deleted file mode 100644 index b9053312ed3..00000000000 --- a/packages/core/src/programmers/iterate/stringify_native.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ts from "@typescript/native-preview"; - -/** @internal */ -export const stringify_native = (): ts.Expression => - ts.factory.createStringLiteral("{}"); diff --git a/packages/core/src/programmers/iterate/stringify_regular_properties.ts b/packages/core/src/programmers/iterate/stringify_regular_properties.ts deleted file mode 100644 index ea636ed840b..00000000000 --- a/packages/core/src/programmers/iterate/stringify_regular_properties.ts +++ /dev/null @@ -1,75 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { TemplateFactory } from "../../factories/TemplateFactory"; -import { ValueFactory } from "../../factories/ValueFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { IExpressionEntry } from "../helpers/IExpressionEntry"; - -/** @internal */ -export const stringify_regular_properties = (props: { - regular: IExpressionEntry[]; - dynamic: IExpressionEntry[]; -}): ts.Expression[] => { - const output: ts.Expression[] = []; - - props.regular.sort((x, y) => sequence(x.meta) - sequence(y.meta)); - props.regular.forEach((entry, index) => { - // BASE ELEMENTS - const key: string = entry.key.getSoleLiteral()!; - const base: ts.Expression[] = [ - ts.factory.createStringLiteral(`${JSON.stringify(key)}:`), - entry.expression, - ]; - if (index !== props.regular.length - 1 || props.dynamic.length !== 0) - base.push(ts.factory.createStringLiteral(`,`)); - - const empty: boolean = - (entry.meta.isRequired() === false && - entry.meta.nullable === false && - entry.meta.size() === 0) || - (!!entry.meta.functions.length && - entry.meta.nullable === false && - entry.meta.size() === 1); - - if (empty === true) return; - else if ( - entry.meta.isRequired() === false || - entry.meta.functions.length || - entry.meta.any === true - ) - output.push( - ts.factory.createConditionalExpression( - (() => { - const conditions: ts.BinaryExpression[] = []; - if (entry.meta.isRequired() === false || entry.meta.any) - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createIdentifier("undefined"), - entry.input, - ), - ); - if (entry.meta.functions.length || entry.meta.any) - conditions.push( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("function"), - ValueFactory.TYPEOF(entry.input), - ), - ); - return conditions.length === 1 - ? conditions[0]! - : conditions.reduce((x, y) => ts.factory.createLogicalOr(x, y)); - })(), - undefined, - ts.factory.createStringLiteral(""), - undefined, - TemplateFactory.generate(base), - ), - ); - else output.push(...base); - }); - return output; -}; - -/** @internal */ -const sequence = (meta: MetadataSchema): number => - meta.any || !meta.isRequired() || meta.functions.length ? 0 : 1; diff --git a/packages/core/src/programmers/iterate/template_to_pattern.ts b/packages/core/src/programmers/iterate/template_to_pattern.ts deleted file mode 100644 index 8f163a53b66..00000000000 --- a/packages/core/src/programmers/iterate/template_to_pattern.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { PatternUtil } from "../../utils/PatternUtil"; -import { metadata_to_pattern } from "./metadata_to_pattern"; - -/** @internal */ -export const template_to_pattern = (props: { - top: boolean; - template: MetadataSchema[]; -}) => { - const pattern: string = props.template - .map((meta) => - metadata_to_pattern({ - top: false, - metadata: meta, - }), - ) - .join(""); - return props.top ? PatternUtil.fix(pattern) : pattern; -}; diff --git a/packages/core/src/programmers/iterate/wrap_metadata_rest_tuple.ts b/packages/core/src/programmers/iterate/wrap_metadata_rest_tuple.ts deleted file mode 100644 index 5ebc1591c34..00000000000 --- a/packages/core/src/programmers/iterate/wrap_metadata_rest_tuple.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataArrayType } from "../../schemas/metadata/MetadataArrayType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -/** @internal */ -export const wrap_metadata_rest_tuple = (rest: MetadataSchema) => { - const wrapper: MetadataSchema = MetadataSchema.initialize(); - wrapper.arrays.push( - MetadataArray.create({ - type: MetadataArrayType.create({ - name: `...${rest.getName()}`, - value: rest, - nullables: [], - recursive: false, - index: null, - }), - tags: [], - }), - ); - return wrapper; -}; diff --git a/packages/core/src/programmers/json/JsonApplicationProgrammer.ts b/packages/core/src/programmers/json/JsonApplicationProgrammer.ts deleted file mode 100644 index 114ec21b75e..00000000000 --- a/packages/core/src/programmers/json/JsonApplicationProgrammer.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { IJsDocTagInfo, IJsonSchemaApplication } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { MetadataFunction } from "../../schemas/metadata/MetadataFunction"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { JsonSchemasProgrammer } from "./JsonSchemasProgrammer"; - -export namespace JsonApplicationProgrammer { - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - if (props.explore.top === false) - return JsonSchemasProgrammer.validate(props); - - const output: string[] = []; - const valid: boolean = - props.metadata.size() === 1 && - props.metadata.objects.length === 1 && - props.metadata.isRequired() === true && - props.metadata.nullable === false; - if (valid === false) - output.push( - "JSON application's generic argument must be a class/interface type.", - ); - - const object: MetadataObjectType | undefined = - props.metadata.objects[0]?.type; - if (object !== undefined) { - if (object.properties.some((p) => p.key.isSoleLiteral() === false)) - output.push("JSON application does not allow dynamic keys."); - let least: boolean = false; - for (const p of object.properties) { - const value: MetadataSchema = p.value; - if (value.functions.length) { - least ||= true; - if (valid === false) { - if (value.functions.length !== 1 || value.size() !== 1) - output.push( - "JSON application's function type does not allow union type.", - ); - if (value.isRequired() === false) - output.push("JSON application's function type must be required."); - if (value.nullable === true) - output.push( - "JSON application's function type must not be nullable.", - ); - } - } - } - if (least === false) - output.push( - "JSON application's target type must have at least a function type.", - ); - } - return output; - }; - - export interface IWriteProps { - context: ITypiaContext; - version: Version; - metadata: MetadataSchema; - filter?: (prop: MetadataProperty) => boolean; - } - - export const write = ( - props: IWriteProps, - ): ts.Expression => { - const app: IJsonSchemaApplication = writeApplication({ - version: props.version, - metadata: props.metadata, - filter: props.filter, - }); - - return LiteralFactory.write(app); - }; - - export const writeApplication = (props: { - version: Version; - metadata: MetadataSchema; - filter?: (prop: MetadataProperty) => boolean; - }): IJsonSchemaApplication => { - const object: MetadataObjectType = props.metadata.objects[0]!.type; - const definitions: MetadataSchema[] = []; - const setters: Array< - (schema: IJsonSchemaApplication.Schema) => void - > = []; - const collect = ( - metadata: MetadataSchema, - setter: (schema: IJsonSchemaApplication.Schema) => void, - ): void => { - definitions.push(metadata); - setters.push(setter); - }; - - const functions: IJsonSchemaApplication.IFunction< - IJsonSchemaApplication.Schema - >[] = object.properties - .filter( - (p) => - p.key.isSoleLiteral() && - p.value.size() === 1 && - p.value.nullable === false && - p.value.isRequired() === true && - MetadataSchema.unalias(p.value).functions.length === 1, - ) - .filter( - (p) => - p.jsDocTags.find( - (tag) => - tag.name === "hidden" || - tag.name === "ignore" || - tag.name === "internal", - ) === undefined && - (props.filter === undefined || props.filter(p) === true), - ) - .map((r) => - collectFunction({ - version: props.version, - name: r.key.getSoleLiteral()!, - function: MetadataSchema.unalias(r.value).functions[0]!, - description: r.description, - jsDocTags: r.jsDocTags, - collect, - }), - ); - const { components, schemas } = JsonSchemasProgrammer.writeSchemas({ - version: props.version, - metadatas: definitions, - }); - schemas.forEach((s, i) => - setters[i]?.(s as IJsonSchemaApplication.Schema), - ); - return { - version: props.version, - components: components as any, - functions, - }; - }; - - export const writeDescription = (props: { - description: string | null; - jsDocTags: IJsDocTagInfo[]; - kind: Kind; - }): Kind extends "summary" - ? { summary?: string; description?: string } - : { title?: string; description?: string } => { - const title: string | undefined = (() => { - const [explicit] = getJsDocTexts({ - jsDocTags: props.jsDocTags, - name: props.kind, - }); - if (explicit?.length) return explicit; - else if (!props.description?.length) return undefined; - - const index: number = props.description.indexOf("\n"); - const top: string = ( - index === -1 ? props.description : props.description.substring(0, index) - ).trim(); - return top.endsWith(".") ? top.substring(0, top.length - 1) : undefined; - })(); - return { - [props.kind]: title, - description: props.description?.length ? props.description : undefined, - } as any; - }; - - const collectFunction = (props: { - version: Version; - name: string; - function: MetadataFunction; - description: string | null; - jsDocTags: IJsDocTagInfo[]; - collect: ( - metadata: MetadataSchema, - setter: (schema: IJsonSchemaApplication.Schema) => void, - ) => void; - }): IJsonSchemaApplication.IFunction< - IJsonSchemaApplication.Schema - > => { - const deprecated: boolean = props.jsDocTags.some( - (tag) => tag.name === "deprecated", - ); - const tags: string[] = props.jsDocTags - .map((tag) => - tag.name === "tag" - ? (tag.text?.filter((elem) => elem.kind === "text") ?? []) - : [], - ) - .flat() - .map((elem) => elem.text) - .map((str) => str.trim().split(" ")[0] ?? "") - .filter((str) => !!str.length); - return { - name: props.name, - async: props.function.async, - parameters: props.function.parameters.map((param) => { - const appParam: IJsonSchemaApplication.IParameter< - IJsonSchemaApplication.Schema - > = { - name: param.name, - ...writeDescription({ - description: - param.description ?? - param.jsDocTags.find((tag) => tag.name === "description") - ?.text?.[0]?.text ?? - props.jsDocTags - .find( - (tag) => - tag.name === "param" && tag.text?.[0]?.text === param.name, - ) - ?.text?.map((e) => e.text) - .join("") - .substring(param.name.length) ?? - null, - jsDocTags: props.jsDocTags, - kind: "title", - }), - required: param.type.isRequired(), - schema: null!, - }; - props.collect(param.type, (schema) => (appParam.schema = schema)); - return appParam; - }), - output: props.function.output.size() - ? (() => { - const appOutput: IJsonSchemaApplication.IOutput< - IJsonSchemaApplication.Schema - > = { - schema: null!, - required: props.function.output.isRequired(), - description: - writeDescriptionFromJsDocTag({ - jsDocTags: props.jsDocTags, - name: "return", - }) ?? - writeDescriptionFromJsDocTag({ - jsDocTags: props.jsDocTags, - name: "returns", - }) ?? - undefined, - }; - props.collect( - props.function.output, - (schema) => (appOutput.schema = schema), - ); - return appOutput; - })() - : undefined, - description: props.description ?? undefined, - deprecated: deprecated || undefined, - tags: tags.length ? tags : undefined, - }; - }; -} - -const writeDescriptionFromJsDocTag = (props: { - jsDocTags: IJsDocTagInfo[]; - name: string; - parameter?: string; -}): string | null => { - const parametric: (elem: IJsDocTagInfo) => boolean = props.parameter - ? (tag) => - tag.text!.find( - (elem) => - elem.kind === "parameterName" && elem.text === props.parameter, - ) !== undefined - : () => true; - const tag: IJsDocTagInfo | undefined = props.jsDocTags.find( - (tag) => tag.name === props.name && tag.text && parametric(tag), - ); - return tag && tag.text - ? (tag.text.find((elem) => elem.kind === "text")?.text ?? null) - : null; -}; - -const getJsDocTexts = (props: { - jsDocTags: IJsDocTagInfo[]; - name: string; -}): string[] => - props.jsDocTags - .filter( - (tag) => - tag.name === props.name && - tag.text && - tag.text.find((elem) => elem.kind === "text" && elem.text.length) !== - undefined, - ) - .map((tag) => tag.text!.find((elem) => elem.kind === "text")!.text); diff --git a/packages/core/src/programmers/json/JsonAssertParseProgrammer.ts b/packages/core/src/programmers/json/JsonAssertParseProgrammer.ts deleted file mode 100644 index 0822bde79bd..00000000000 --- a/packages/core/src/programmers/json/JsonAssertParseProgrammer.ts +++ /dev/null @@ -1,111 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { JsonMetadataFactory } from "../../factories/JsonMetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace JsonAssertParseProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - JsonMetadataFactory.analyze({ - method: props.functor.method, - checker: props.context.checker, - transformer: props.context.transformer, - type: props.type, - }); - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - return { - functions: assert.functions, - statements: [ - ...assert.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("string")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - props.context.importer.type({ - file: "typia", - name: "Primitive", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.parse"), - undefined, - [ts.factory.createIdentifier("input")], - ), - AssertProgrammer.Guardian.identifier(), - ], - ), - TypeFactory.keyword("any"), - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - functor, - type: props.type, - name: props.name, - init: props.init, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/json/JsonAssertStringifyProgrammer.ts b/packages/core/src/programmers/json/JsonAssertStringifyProgrammer.ts deleted file mode 100644 index d459c172728..00000000000 --- a/packages/core/src/programmers/json/JsonAssertStringifyProgrammer.ts +++ /dev/null @@ -1,113 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { JsonStringifyProgrammer } from "./JsonStringifyProgrammer"; - -export namespace JsonAssertStringifyProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - finite: true, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - const stringify: FeatureProgrammer.IDecomposed = - JsonStringifyProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...assert.functions, - ...stringify.functions, - }, - statements: [ - ...assert.statements, - ...stringify.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__stringify", - value: stringify.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - stringify.arrow.type, - undefined, - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createIdentifier("input"), - AssertProgrammer.Guardian.identifier(), - ], - ), - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__stringify"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - functor, - type: props.type, - name: props.name, - init: props.init, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/json/JsonIsParseProgrammer.ts b/packages/core/src/programmers/json/JsonIsParseProgrammer.ts deleted file mode 100644 index 147012a88bf..00000000000 --- a/packages/core/src/programmers/json/JsonIsParseProgrammer.ts +++ /dev/null @@ -1,112 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace JsonIsParseProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - return { - functions: is.functions, - statements: [ - ...is.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("string"))], - ts.factory.createUnionTypeNode([ - props.context.importer.type({ - file: "typia", - name: "Primitive", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock([ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("input"), - ts.SyntaxKind.EqualsToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.parse"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ), - ts.factory.createReturnStatement( - ts.factory.createConditionalExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - undefined, - ts.factory.createAsExpression( - ts.factory.createIdentifier("input"), - TypeFactory.keyword("any"), - ), - undefined, - ts.factory.createNull(), - ), - ), - ]), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - functor, - type: props.type, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/json/JsonIsStringifyProgrammer.ts b/packages/core/src/programmers/json/JsonIsStringifyProgrammer.ts deleted file mode 100644 index c2e90d69578..00000000000 --- a/packages/core/src/programmers/json/JsonIsStringifyProgrammer.ts +++ /dev/null @@ -1,106 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { JsonStringifyProgrammer } from "./JsonStringifyProgrammer"; - -export namespace JsonIsStringifyProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - finite: true, - }, - }, - config: { - equals: false, - }, - }); - const stringify: FeatureProgrammer.IDecomposed = - JsonStringifyProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: true, - }, - }, - validated: true, - }); - return { - functions: { - ...is.functions, - ...stringify.functions, - }, - statements: [ - ...is.statements, - ...stringify.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__stringify", - value: stringify.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - ts.factory.createUnionTypeNode([ - stringify.arrow.type ?? TypeFactory.keyword("string"), - ts.factory.createLiteralTypeNode(ts.factory.createNull()), - ]), - undefined, - ts.factory.createConditionalExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__stringify"), - undefined, - [ts.factory.createIdentifier("input")], - ), - undefined, - ts.factory.createNull(), - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/json/JsonSchemaProgrammer.ts b/packages/core/src/programmers/json/JsonSchemaProgrammer.ts deleted file mode 100644 index d62b9d65813..00000000000 --- a/packages/core/src/programmers/json/JsonSchemaProgrammer.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { IJsonSchemaUnit, OpenApi } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { MetadataFactory } from "../../factories"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { JsonSchemasProgrammer } from "./JsonSchemasProgrammer"; - -export namespace JsonSchemaProgrammer { - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => JsonSchemasProgrammer.validate(props); - - export interface IWriteProps { - context: ITypiaContext; - version: Version; - metadata: MetadataSchema; - } - - export const write = ( - props: IWriteProps, - ): ts.Expression => { - const schema: IJsonSchemaUnit = writeSchema({ - version: props.version, - metadata: props.metadata, - }); - - return ts.factory.createAsExpression( - LiteralFactory.write(schema), - props.context.importer.type({ - file: "typia", - name: "IJsonSchemaUnit", - arguments: [ - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral(props.version), - ), - ], - }), - ); - }; - - export const writeSchema = (props: { - version: Version; - metadata: MetadataSchema; - }): IJsonSchemaUnit => { - const collection = JsonSchemasProgrammer.writeSchemas({ - version: props.version, - metadatas: [props.metadata], - }); - return { - version: collection.version as "3.1", - components: collection.components as OpenApi.IComponents, - schema: collection.schemas[0] as OpenApi.IJsonSchema, - } as IJsonSchemaUnit; - }; -} diff --git a/packages/core/src/programmers/json/JsonSchemasProgrammer.ts b/packages/core/src/programmers/json/JsonSchemasProgrammer.ts deleted file mode 100644 index 18e93493bbf..00000000000 --- a/packages/core/src/programmers/json/JsonSchemasProgrammer.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { IJsonSchemaCollection, OpenApi, OpenApiV3 } from "@typia/interface"; -import { OpenApiConverter } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { AtomicPredicator } from "../helpers/AtomicPredicator"; -import { json_schema_station } from "../iterate/json_schema_station"; - -export namespace JsonSchemasProgrammer { - export const validate = (props: { - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const output: string[] = []; - if ( - props.metadata.atomics.some((a) => a.type === "bigint") || - props.metadata.constants.some((c) => c.type === "bigint") - ) - output.push("JSON schema does not support bigint type."); - if ( - props.metadata.tuples.some((t) => - t.type.elements.some((e) => e.isRequired() === false), - ) || - props.metadata.arrays.some((a) => a.type.value.isRequired() === false) - ) - output.push("JSON schema does not support undefined type in array."); - if (props.metadata.maps.length) - output.push("JSON schema does not support Map type."); - if (props.metadata.sets.length) - output.push("JSON schema does not support Set type."); - for (const native of props.metadata.natives) - if ( - AtomicPredicator.native(native.name) === false && - native.name !== "Date" && - native.name !== "Blob" && - native.name !== "File" - ) - output.push(`JSON schema does not support ${native.name} type.`); - return output; - }; - - export interface IWriteProps { - context: ITypiaContext; - version: Version; - metadatas: Array; - } - - export const write = ( - props: IWriteProps, - ): ts.Expression => { - const collection: IJsonSchemaCollection = writeSchemas({ - version: props.version, - metadatas: props.metadatas, - }); - - return ts.factory.createAsExpression( - LiteralFactory.write(collection), - props.context.importer.type({ - file: "typia", - name: "IJsonSchemaCollection", - arguments: [ - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral(props.version), - ), - ], - }), - ); - }; - - export const writeSchemas = (props: { - version: Version; - metadatas: Array; - }): IJsonSchemaCollection => - props.version === "3.0" - ? (writeV3_0(props.metadatas) as IJsonSchemaCollection) - : (writeV3_1(props.metadatas) as IJsonSchemaCollection); - - const writeV3_0 = ( - metadataList: Array, - ): IJsonSchemaCollection<"3.0"> => { - const collection: IJsonSchemaCollection<"3.1"> = writeV3_1(metadataList); - const downgraded: OpenApiV3.IComponents = - OpenApiConverter.downgradeComponents(collection.components, "3.0"); - const caster = (schema: OpenApi.IJsonSchema): OpenApiV3.IJsonSchema => - OpenApiConverter.downgradeSchema({ - version: "3.0", - components: collection.components, - schema, - downgraded, - }); - return { - version: "3.0", - components: downgraded, - schemas: collection.schemas.map(caster), - }; - }; - - const writeV3_1 = ( - metadataList: Array, - ): IJsonSchemaCollection<"3.1"> => { - const components: OpenApi.IComponents = { - schemas: {}, - }; - const generator = (metadata: MetadataSchema): OpenApi.IJsonSchema | null => - json_schema_station({ - blockNever: true, - components, - attribute: {}, - metadata, - }); - return { - version: "3.1", - components, - schemas: metadataList.map((meta, i) => { - const schema: OpenApi.IJsonSchema | null = generator(meta); - if (schema === null) - throw new TransformerError({ - code: "typia.json.schemas", - message: `invalid type on argument - (${meta.getName()}, ${i})`, - }); - return schema; - }), - }; - }; -} diff --git a/packages/core/src/programmers/json/JsonStringifyProgrammer.ts b/packages/core/src/programmers/json/JsonStringifyProgrammer.ts deleted file mode 100644 index be6c86980fa..00000000000 --- a/packages/core/src/programmers/json/JsonStringifyProgrammer.ts +++ /dev/null @@ -1,1129 +0,0 @@ -import { Atomic } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { JsonMetadataFactory } from "../../factories/JsonMetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValueFactory } from "../../factories/ValueFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; -import { IsProgrammer } from "../IsProgrammer"; -import { AtomicPredicator } from "../helpers/AtomicPredicator"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { OptionPredicator } from "../helpers/OptionPredicator"; -import { StringifyJoiner } from "../helpers/StringifyJoinder"; -import { StringifyPredicator } from "../helpers/StringifyPredicator"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { check_native } from "../iterate/check_native"; -import { decode_union_object } from "../iterate/decode_union_object"; -import { postfix_of_tuple } from "../iterate/postfix_of_tuple"; -import { wrap_metadata_rest_tuple } from "../iterate/wrap_metadata_rest_tuple"; - -export namespace JsonStringifyProgrammer { - /* ----------------------------------------------------------- - WRITER - ----------------------------------------------------------- */ - export const decompose = (props: { - validated: boolean; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const config: FeatureProgrammer.IConfig = configure(props); - if (props.validated === false) - config.addition = (collection) => - IsProgrammer.write_function_statements({ - context: props.context, - functor: props.functor, - collection, - }); - const composed: FeatureProgrammer.IComposed = FeatureProgrammer.compose({ - ...props, - config, - }); - return { - functions: composed.functions, - statements: composed.statements, - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - composed.response, - undefined, - composed.body, - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - validated: false, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const write_array_functions = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .arrays() - .filter((a) => a.recursive) - .map((type, i) => - StatementFactory.constant({ - name: `${props.config.prefix}a${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_array_inline({ - config: props.config, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - array: MetadataArray.create({ - type, - tags: [], - }), - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - const write_tuple_functions = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - validated: boolean; - }): ts.VariableStatement[] => - props.collection - .tuples() - .filter((t) => t.recursive) - .map((tuple, i) => - StatementFactory.constant({ - name: `${props.config.prefix}t${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_tuple_inline({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - tuple, - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - validated: props.validated, - }), - ), - }), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decode = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - validated: boolean; - }): ts.Expression => { - // ANY TYPE - if (props.metadata.any === true) - return wrap_required({ - input: props.input, - metadata: props.metadata, - explore: props.explore, - expression: wrap_functional({ - input: props.input, - metadata: props.metadata, - explore: props.explore, - expression: ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.stringify"), - undefined, - [props.input], - ), - }), - }); - - // ONLY NULL OR UNDEFINED - const size: number = props.metadata.size(); - if ( - size === 0 && - (props.metadata.isRequired() === false || - props.metadata.nullable === true) - ) { - if ( - props.metadata.isRequired() === false && - props.metadata.nullable === true - ) - return props.explore.from === "array" - ? ts.factory.createStringLiteral("null") - : ts.factory.createConditionalExpression( - ts.factory.createStrictEquality( - ts.factory.createNull(), - props.input, - ), - undefined, - ts.factory.createStringLiteral("null"), - undefined, - ts.factory.createIdentifier("undefined"), - ); - else if (props.metadata.isRequired() === false) - return props.explore.from === "array" - ? ts.factory.createStringLiteral("null") - : ts.factory.createIdentifier("undefined"); - else return ts.factory.createStringLiteral("null"); - } - - //---- - // LIST UP UNION TYPES - //---- - const unions: IUnion[] = []; - - // toJSON() METHOD - if (props.metadata.escaped !== null) - unions.push({ - type: "resolved", - is: - props.metadata.escaped.original.size() === 1 && - props.metadata.escaped.original.natives[0]?.name === "Date" - ? () => - check_native({ - name: "Date", - input: props.input, - }) - : () => - IsProgrammer.decode_to_json({ - checkNull: false, - input: props.input, - }), - value: () => - decode_to_json({ - ...props, - metadata: props.metadata.escaped!.returns, - }), - }); - else if (props.metadata.functions.length) - unions.push({ - type: "functional", - is: () => IsProgrammer.decode_functional(props.input), - value: () => decode_functional(props.explore), - }); - - // TEMPLATES - if (props.metadata.templates.length) - if (AtomicPredicator.template(props.metadata)) { - const partial = MetadataSchema.initialize(); - (partial.atomics.push( - MetadataAtomic.create({ type: "string", tags: [] }), - ), - unions.push({ - type: "template literal", - is: () => - IsProgrammer.decode({ - ...props, - metadata: partial, - }), - value: () => - decode_atomic({ - ...props, - type: "string", - }), - })); - } - - // CONSTANTS - for (const constant of props.metadata.constants) - if ( - AtomicPredicator.constant({ - metadata: props.metadata, - name: constant.type, - }) === false - ) - continue; - else if (constant.type !== "string") - unions.push({ - type: "atomic", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.atomics.push( - MetadataAtomic.create({ - type: constant.type, - tags: [], - }), - ); - return partial; - })(), - }), - value: () => - decode_atomic({ - ...props, - type: constant.type, - }), - }); - else if (props.metadata.templates.length === 0) - unions.push({ - type: "const string", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.atomics.push( - MetadataAtomic.create({ - type: "string", - tags: [], - }), - ); - return partial; - })(), - }), - value: () => - decode_constant_string({ - ...props, - values: [...constant.values.map((v) => v.value)] as string[], - }), - }); - - /// ATOMICS - for (const a of props.metadata.atomics) - if ( - AtomicPredicator.atomic({ - metadata: props.metadata, - name: a.type, - }) - ) - unions.push({ - type: "atomic", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.atomics.push(a); - return partial; - })(), - }), - value: () => - decode_atomic({ - ...props, - type: a.type, - }), - }); - - // TUPLES - for (const tuple of props.metadata.tuples) - unions.push({ - type: "tuple", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.tuples.push(tuple); - return partial; - })(), - }), - value: () => - decode_tuple({ - ...props, - tuple, - }), - }); - - // ARRAYS - if (props.metadata.arrays.length) { - const value: () => ts.Expression = - props.metadata.arrays.length === 1 - ? () => - decode_array({ - ...props, - array: props.metadata.arrays[0]!, - explore: { - ...props.explore, - from: "array", - }, - }) - : props.metadata.arrays.some((elem) => elem.type.value.any) - ? () => - ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.stringify"), - undefined, - [props.input], - ) - : () => - explore_arrays({ - ...props, - arrays: props.metadata.arrays, - explore: { - ...props.explore, - from: "array", - }, - }); - - unions.push({ - type: "array", - is: () => ExpressionFactory.isArray(props.input), - value, - }); - } - - // BUILT-IN CLASSES - if (props.metadata.natives.length) - for (const native of props.metadata.natives) - unions.push({ - type: "object", - is: () => - check_native({ - name: native.name, - input: props.input, - }), - value: () => - AtomicPredicator.native(native.name) - ? decode_atomic({ - ...props, - type: native.name.toLowerCase() as Atomic.Literal, - }) - : ts.factory.createStringLiteral("{}"), - }); - - // SETS - if (props.metadata.sets.length) - unions.push({ - type: "object", - is: () => ExpressionFactory.isInstanceOf("Set", props.input), - value: () => ts.factory.createStringLiteral("{}"), - }); - - // MAPS - if (props.metadata.maps.length) - unions.push({ - type: "object", - is: () => ExpressionFactory.isInstanceOf("Map", props.input), - value: () => ts.factory.createStringLiteral("{}"), - }); - - // OBJECTS - if (props.metadata.objects.length) - unions.push({ - type: "object", - is: () => - ExpressionFactory.isObject({ - checkNull: true, - checkArray: props.metadata.objects.some((object) => - object.type.properties.every( - (prop) => !prop.key.isSoleLiteral() || !prop.value.isRequired(), - ), - ), - input: props.input, - }), - value: () => - explore_objects({ - ...props, - explore: { - ...props.explore, - from: "object", - }, - }), - }); - - //---- - // RETURNS - //---- - // CHECK NULL AND UNDEFINED - const wrapper = (output: ts.Expression) => - wrap_required({ - input: props.input, - metadata: props.metadata, - explore: props.explore, - expression: wrap_nullable({ - input: props.input, - metadata: props.metadata, - expression: output, - }), - }); - - // DIRECT RETURN - if (unions.length === 0) - return ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.stringify"), - undefined, - [props.input], - ); - else if (unions.length === 1) return wrapper(unions[0]!.value()); - - // RETURN WITH TYPE CHECKING - return wrapper( - ts.factory.createCallExpression( - ts.factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - iterate({ - context: props.context, - functor: props.functor, - input: props.input, - expected: props.metadata.getName(), - unions, - }), - ), - undefined, - undefined, - ), - ); - }; - - const decode_object = (props: { - functor: FunctionProgrammer; - input: ts.Expression; - object: MetadataObjectType; - explore: FeatureProgrammer.IExplore; - }): ts.CallExpression => - FeatureProgrammer.decode_object({ - config: { - trace: false, - path: false, - prefix: PREFIX, - }, - functor: props.functor, - object: props.object, - input: props.input, - explore: props.explore, - }); - - const decode_array = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - props.array.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}a${props.array.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - input: props.input, - explore: { - ...props.explore, - source: "function", - from: "array", - }, - }), - ) - : decode_array_inline(props); - - const decode_array_inline = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - FeatureProgrammer.decode_array({ - config: props.config, - functor: props.functor, - combiner: StringifyJoiner.array, - array: props.array, - input: props.input, - explore: props.explore, - }); - - const decode_tuple = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - tuple: MetadataTuple; - explore: FeatureProgrammer.IExplore; - validated: boolean; - }): ts.Expression => - props.tuple.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}t${props.tuple.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - }, - input: props.input, - }), - ) - : decode_tuple_inline({ - ...props, - tuple: props.tuple.type, - }); - - const decode_tuple_inline = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - tuple: MetadataTupleType; - explore: FeatureProgrammer.IExplore; - validated: boolean; - }): ts.Expression => { - const elements: ts.Expression[] = props.tuple.elements - .filter((elem) => elem.rest === null) - .map((elem, index) => - decode({ - ...props, - input: ts.factory.createElementAccessExpression(props.input, index), - metadata: elem, - explore: { - ...props.explore, - from: "array", - postfix: props.explore.postfix.length - ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` - : `"[${index}]"`, - }, - }), - ); - const rest = (() => { - if (props.tuple.elements.length === 0) return null; - const last = props.tuple.elements.at(-1)!; - if (last.rest === null) return null; - - const code = decode({ - ...props, - input: ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "slice"), - undefined, - [ExpressionFactory.number(props.tuple.elements.length - 1)], - ), - metadata: wrap_metadata_rest_tuple(props.tuple.elements.at(-1)!.rest!), - explore: { - ...props.explore, - start: props.tuple.elements.length - 1, - }, - }); - return ts.factory.createCallExpression( - props.context.importer.internal("jsonStringifyRest"), - undefined, - [code], - ); - })(); - return StringifyJoiner.tuple({ - elements, - rest, - }); - }; - - const decode_atomic = (props: { - context: ITypiaContext; - input: ts.Expression; - type: string; - explore: FeatureProgrammer.IExplore; - validated: boolean; - }): ts.Expression => { - if (props.type === "string") - return ts.factory.createCallExpression( - props.context.importer.internal("jsonStringifyString"), - undefined, - [props.input], - ); - else if (props.type === "number") - props = { - ...props, - input: - props.validated && OptionPredicator.finite(props.context.options) - ? props.input - : ts.factory.createCallExpression( - props.context.importer.internal("jsonStringifyNumber"), - undefined, - [props.input], - ), - }; - - return ts.factory.createCallExpression( - ts.factory.createIdentifier("String"), - undefined, - [props.input], - ); - }; - - const decode_constant_string = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - input: ts.Expression; - values: string[]; - explore: FeatureProgrammer.IExplore; - validated: boolean; - }): ts.Expression => { - if (props.values.every((v) => !StringifyPredicator.require_escape(v))) - return [ - ts.factory.createStringLiteral('"'), - props.input, - ts.factory.createStringLiteral('"'), - ].reduce((x, y) => ts.factory.createAdd(x, y)); - return decode_atomic({ - ...props, - type: "string", - }); - }; - - const decode_to_json = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - validated: boolean; - }): ts.Expression => { - return decode({ - ...props, - input: ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "toJSON"), - undefined, - [], - ), - }); - }; - - const decode_functional = (explore: FeatureProgrammer.IExplore) => - explore.from === "array" - ? ts.factory.createStringLiteral("null") - : ts.factory.createIdentifier("undefined"); - - /* ----------------------------------------------------------- - EXPLORERS - ----------------------------------------------------------- */ - const explore_objects = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - }) => - props.metadata.objects.length === 1 - ? decode_object({ - functor: props.functor, - input: props.input, - object: props.metadata.objects[0]!.type, - explore: props.explore, - }) - : ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(`${PREFIX}u${props.metadata.union_index!}`), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ); - - const explore_arrays = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - arrays: MetadataArray[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - explore_array_like_union_types({ - ...props, - elements: props.arrays, - factory: (next) => - UnionExplorer.array({ - config: { - checker: (v) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - metadata: v.definition, - input: v.input, - explore: v.explore, - }), - decoder: (v) => - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - empty: ts.factory.createStringLiteral("[]"), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: next.parameters, - input: next.input, - arrays: next.definitions, - explore: next.explore, - }), - //(next.parameters)(next.input, next.elements, next.explore), - }); - - const explore_array_like_union_types = < - T extends MetadataArray | MetadataTuple, - >(props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - factory: (next: { - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - definitions: T[]; - explore: FeatureProgrammer.IExplore; - }) => ts.ArrowFunction; - input: ts.Expression; - elements: T[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - const arrow = (next: { - parameters: ts.ParameterDeclaration[]; - explore: FeatureProgrammer.IExplore; - input: ts.Expression; - }): ts.ArrowFunction => - props.factory({ - definitions: props.elements, - parameters: next.parameters, - input: next.input, - explore: next.explore, - }); - if (props.elements.every((e) => e.type.recursive === false)) - ts.factory.createCallExpression( - arrow({ - parameters: [], - explore: props.explore, - input: props.input, - }), - undefined, - [], - ); - - const arrayExplore: FeatureProgrammer.IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.emplaceUnion( - props.config.prefix, - props.elements.map((e) => e.type.name).join(" | "), - () => - arrow({ - parameters: FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - explore: { - ...arrayExplore, - postfix: "", - }, - input: ts.factory.createIdentifier("input"), - }), - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: arrayExplore, - input: props.input, - }), - ); - }; - - /* ----------------------------------------------------------- - RETURN SCRIPTS - ----------------------------------------------------------- */ - const wrap_required = (props: { - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - expression: ts.Expression; - }): ts.Expression => { - if (props.metadata.isRequired() === true && props.metadata.any === false) - return props.expression; - return ts.factory.createConditionalExpression( - ts.factory.createStrictInequality( - ts.factory.createIdentifier("undefined"), - props.input, - ), - undefined, - props.expression, - undefined, - props.explore.from === "array" - ? ts.factory.createStringLiteral("null") - : ts.factory.createIdentifier("undefined"), - ); - }; - - const wrap_nullable = (props: { - input: ts.Expression; - metadata: MetadataSchema; - expression: ts.Expression; - }): ts.Expression => { - if (props.metadata.nullable === false) return props.expression; - return ts.factory.createConditionalExpression( - ts.factory.createStrictInequality(ts.factory.createNull(), props.input), - undefined, - props.expression, - undefined, - ts.factory.createStringLiteral("null"), - ); - }; - - const wrap_functional = (props: { - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - expression: ts.Expression; - }): ts.Expression => { - if (props.metadata.functions.length === 0) return props.expression; - return ts.factory.createConditionalExpression( - ts.factory.createStrictInequality( - ts.factory.createStringLiteral("function"), - ValueFactory.TYPEOF(props.input), - ), - undefined, - props.expression, - undefined, - decode_functional(props.explore), - ); - }; - - const iterate = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - input: ts.Expression; - unions: IUnion[]; - expected: string; - }) => - ts.factory.createBlock( - [ - ...props.unions.map((u) => - ts.factory.createIfStatement( - u.is(), - ts.factory.createReturnStatement(u.value()), - ), - ), - create_throw_error(props), - ], - true, - ); - - /* ----------------------------------------------------------- - CONFIGURATIONS - ----------------------------------------------------------- */ - const PREFIX = "_s"; - - const configure = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - validated: boolean; - }): FeatureProgrammer.IConfig => { - const config: FeatureProgrammer.IConfig = { - types: { - input: (type, name) => - ts.factory.createTypeReferenceNode( - name ?? - TypeFactory.getFullName({ checker: props.context.checker, type }), - ), - output: () => TypeFactory.keyword("string"), - }, - prefix: PREFIX, - trace: false, - path: false, - initializer, - decoder: (next) => - decode({ - context: props.context, - functor: props.functor, - config, - metadata: next.metadata, - input: next.input, - explore: next.explore, - validated: props.validated, - }), - objector: { - checker: (next) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - metadata: next.metadata, - input: next.input, - explore: next.explore, - }), - decoder: (next) => - decode_object({ - functor: props.functor, - input: next.input, - object: next.object, - explore: next.explore, - }), - joiner: (next) => - StringifyJoiner.object({ - ...next, - context: props.context, - }), - unionizer: (next) => - decode_union_object({ - checker: (v) => - IsProgrammer.decode_object({ - context: props.context, - functor: props.functor, - input: v.input, - object: v.object, - explore: v.explore, - }), - decoder: (v) => - decode_object({ - functor: props.functor, - input: v.input, - object: v.object, - explore: v.explore, - }), - success: (exp) => exp, - escaper: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - objects: next.objects, - explore: next.explore, - input: next.input, - }), - failure: (next) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: next.expected, - input: next.input, - }), - }, - generator: { - arrays: (collection) => - write_array_functions({ - config, - functor: props.functor, - collection, - }), - tuples: (collection) => - write_tuple_functions({ - config, - context: props.context, - functor: props.functor, - collection, - validated: props.validated, - }), - }, - }; - return config; - }; - - const initializer: FeatureProgrammer.IConfig["initializer"] = (props) => - JsonMetadataFactory.analyze({ - method: props.functor.method, - checker: props.context.checker, - transformer: props.context.transformer, - type: props.type, - }); - - const create_throw_error = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - expected: string; - input: ts.Expression; - }) => - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - props.context.importer.internal("throwTypeGuardError"), - [], - [ - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "method", - ts.factory.createStringLiteral(props.functor.method), - ), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.input), - ], - true, - ), - ], - ), - ); -} - -interface IUnion { - type: string; - is: () => ts.Expression; - value: () => ts.Expression; -} diff --git a/packages/core/src/programmers/json/JsonValidateParseProgrammer.ts b/packages/core/src/programmers/json/JsonValidateParseProgrammer.ts deleted file mode 100644 index e4ce56e42b7..00000000000 --- a/packages/core/src/programmers/json/JsonValidateParseProgrammer.ts +++ /dev/null @@ -1,103 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace JsonValidateParseProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate: FeatureProgrammer.IDecomposed = - ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - return { - functions: validate.functions, - statements: [ - ...validate.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("string"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [ - props.context.importer.type({ - file: "typia", - name: "Primitive", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - ], - }), - undefined, - ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("JSON.parse"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ], - ), - ts.factory.createTypeReferenceNode("any"), - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - modulo: props.modulo, - functor, - type: props.type, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/json/JsonValidateStringifyProgrammer.ts b/packages/core/src/programmers/json/JsonValidateStringifyProgrammer.ts deleted file mode 100644 index 8be088c1680..00000000000 --- a/packages/core/src/programmers/json/JsonValidateStringifyProgrammer.ts +++ /dev/null @@ -1,122 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { JsonStringifyProgrammer } from "./JsonStringifyProgrammer"; - -export namespace JsonValidateStringifyProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate: FeatureProgrammer.IDecomposed = - ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - finite: true, - }, - }, - config: { - equals: false, - }, - }); - const stringify: FeatureProgrammer.IDecomposed = - JsonStringifyProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...validate.functions, - ...stringify.functions, - }, - statements: [ - ...validate.statements, - ...stringify.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__stringify", - value: stringify.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [ - stringify.arrow.type ?? - ts.factory.createTypeReferenceNode("string"), - ], - }), - undefined, - ts.factory.createBlock([ - StatementFactory.constant({ - name: "result", - value: ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ts.factory.createIdentifier("input")], - ), - TypeFactory.keyword("any"), - ), - }), - ts.factory.createIfStatement( - ts.factory.createIdentifier("result.success"), - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("result.data"), - ts.SyntaxKind.EqualsToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__stringify"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("result"), - ), - ]), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - modulo: props.modulo, - functor, - type: props.type, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/json/index.ts b/packages/core/src/programmers/json/index.ts deleted file mode 100644 index 5a2fd277a66..00000000000 --- a/packages/core/src/programmers/json/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from "./JsonApplicationProgrammer"; -export * from "./JsonAssertParseProgrammer"; -export * from "./JsonAssertStringifyProgrammer"; -export * from "./JsonIsParseProgrammer"; -export * from "./JsonIsStringifyProgrammer"; -export * from "./JsonSchemaProgrammer"; -export * from "./JsonSchemasProgrammer"; -export * from "./JsonStringifyProgrammer"; -export * from "./JsonValidateParseProgrammer"; -export * from "./JsonValidateStringifyProgrammer"; diff --git a/packages/core/src/programmers/llm/LlmApplicationProgrammer.ts b/packages/core/src/programmers/llm/LlmApplicationProgrammer.ts deleted file mode 100644 index c828675c3c2..00000000000 --- a/packages/core/src/programmers/llm/LlmApplicationProgrammer.ts +++ /dev/null @@ -1,523 +0,0 @@ -import { - IJsonSchemaApplication, - IJsonSchemaTransformError, - ILlmApplication, - ILlmFunction, - ILlmSchema, - IResult, - IValidation, - OpenApi, -} from "@typia/interface"; -import { LlmSchemaConverter } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataFunction } from "../../schemas/metadata/MetadataFunction"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataParameter } from "../../schemas/metadata/MetadataParameter"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { JsonApplicationProgrammer } from "../json/JsonApplicationProgrammer"; -import { LlmSchemaProgrammer } from "./LlmSchemaProgrammer"; - -/** - * Generates LLM function calling application from TypeScript class/interface. - * - * Converts TypeScript types to {@link ILlmApplication} with function schemas - * compatible with LLM function calling. Validates type constraints (single - * object parameter, no dynamic keys) and generates runtime validators. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace LlmApplicationProgrammer { - export interface IProps extends IProgrammerProps { - config?: Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - >; - } - - export interface IWriteProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - metadata: MetadataSchema; - config?: Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - >; - name?: string; - configArgument?: ts.Expression; - } - - export const write = (props: IWriteProps): ts.CallExpression => { - const application: ILlmApplication.__IPrimitive = writeApplication({ - context: props.context, - modulo: props.modulo, - config: props.config, - metadata: props.metadata, - name: props.name, - }); - - const typeNode: ts.ImportTypeNode = props.context.importer.type({ - file: "typia", - name: "ILlmApplication.__IPrimitive", - arguments: props.name - ? [ts.factory.createTypeReferenceNode(props.name)] - : undefined, - }); - - return ts.factory.createCallExpression( - props.context.importer.internal("llmApplicationFinalize"), - props.name ? [ts.factory.createTypeReferenceNode(props.name)] : undefined, - [ - ts.factory.createAsExpression( - ts.factory.createSatisfiesExpression( - LiteralFactory.write(application), - typeNode, - ), - typeNode, - ), - ...(props.configArgument ? [props.configArgument] : []), - ], - ); - }; - - export const validate = (props: { - config?: Partial; - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - top: MetadataSchema; - }): string[] => { - // the class - if (props.explore.top === false) - if ( - props.explore.object === props.top?.objects[0]?.type && - typeof props.explore.property === "string" && - props.metadata.size() === 1 && - props.metadata.nullable === false && - props.metadata.isRequired() === true && - props.metadata.functions.length === 1 - ) - return validateFunction( - props.explore.property, - props.metadata.functions[0]!, - ); - else return LlmSchemaProgrammer.validate(props); - - // top-level type must be a single non-nullable required object - const output: string[] = []; - const isValidType: boolean = - props.metadata.size() === 1 && - props.metadata.objects.length === 1 && - props.metadata.isRequired() === true && - props.metadata.nullable === false; - if (isValidType === false) - output.push( - "LLM application's generic argument must be a class/interface type.", - ); - - // validate each property of the class/interface - const object: MetadataObjectType | undefined = - props.metadata.objects[0]?.type; - if (object !== undefined) { - if (object.properties.some((p) => p.key.isSoleLiteral() === false)) - output.push( - "LLM application does not allow dynamic keys on class/interface type.", - ); - let least: boolean = false; // tracks whether at least one function exists - for (const p of object.properties) { - const rawName: string = p.key.getSoleLiteral()!; - const name: string = JSON.stringify(rawName); - const value: MetadataSchema = p.value; - if (value.functions.length) { - least ||= true; - if (isValidType === false) { - if (value.functions.length !== 1 || value.size() !== 1) - output.push( - `LLM application's function (${name}) type does not allow union type.`, - ); - if (value.isRequired() === false) - output.push( - `LLM application's function (${name}) type must be required.`, - ); - if (value.nullable === true) - output.push( - `LLM application's function (${name}) type must not be nullable.`, - ); - } - - // validate function name length and pattern - const prefix: string = `LLM application's function (${name})`; - if (/^[0-9]/.test(rawName[0] ?? "") === true) - output.push(`${prefix} name cannot start with a number.`); - if (/^[a-zA-Z0-9_-]+$/.test(rawName) === false) - output.push( - `${prefix} name must contain only alphanumeric characters, underscores, or hyphens.`, - ); - if (rawName.length > 64) - output.push(`${prefix} name cannot exceed 64 characters.`); - - const description: string | undefined = concatDescription( - JsonApplicationProgrammer.writeDescription({ - description: - p.description ?? - p.jsDocTags.find((tag) => tag.name === "description")?.text?.[0] - ?.text ?? - null, - jsDocTags: p.jsDocTags, - kind: "summary", - }), - ); - if (description !== undefined && description.length > 1_024) - output.push( - `LLM application's function (${name}) description must not exceed 1,024 characters.`, - ); - } - } - if (least === false) - output.push( - "LLM application's target type must have at least a function type.", - ); - } - return output; - }; - - /** - * Validates that a {@link MetadataSchema} represents a valid - * {@link ILlmSchema.IParameters} type: a single, non-nullable, non-optional - * object type without dynamic property keys. - */ - const validateObjectSchema = ( - prefix: string, - label: string, - schema: MetadataSchema, - ): string[] => { - const errors: string[] = []; - if (schema.isRequired() === false) - errors.push( - `${prefix} ${label} cannot be optional (union with undefined).`, - ); - if (schema.nullable === true) - errors.push(`${prefix} ${label} cannot be nullable.`); - if (schema.size() !== 1 || schema.objects.length !== 1) - errors.push(`${prefix} ${label} must be a single object type.`); - else if ( - schema.objects[0]!.type.properties.some( - (p) => p.key.isSoleLiteral() === false, - ) - ) - errors.push(`${prefix} ${label} cannot have dynamic property keys.`); - return errors; - }; - - const validateFunction = (name: string, func: MetadataFunction): string[] => { - const messages: string[] = []; - const prefix: string = `LLM application's function (${JSON.stringify(name)})`; - - // function name - if (/^[0-9]/.test(name[0] ?? "") === true) - messages.push(`${prefix} name cannot start with a number.`); - if (/^[a-zA-Z0-9_-]+$/.test(name) === false) - messages.push( - `${prefix} name must contain only alphanumeric characters, underscores, or hyphens.`, - ); - if (name.length > 64) - messages.push(`${prefix} name cannot exceed 64 characters.`); - - // output - if (func.output.size() !== 0) - messages.push( - ...validateObjectSchema(prefix, "return type", func.output), - ); - - // parameters - if (func.parameters.length !== 0 && func.parameters.length !== 1) - messages.push( - `${prefix} must have exactly one parameter or no parameters.`, - ); - if (func.parameters.length !== 0) - messages.push( - ...validateObjectSchema(prefix, "parameter", func.parameters[0]!.type), - ); - return messages; - }; - - export const writeApplication = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - metadata: MetadataSchema; - config?: Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - >; - name?: string; - }): ILlmApplication.__IPrimitive => { - const metadata: MetadataSchema = MetadataSchema.unalias(props.metadata); - - // collect the first parameter's MetadataParameter for each valid function, - // keyed by property name, for runtime validator generation - const functionParameters: Record = - Object.fromEntries( - metadata.objects[0]!.type.properties.filter( - (p) => - p.key.isSoleLiteral() && - p.value.size() === 1 && - p.value.nullable === false && - p.value.isRequired() === true && - MetadataSchema.unalias(p.value).functions.length === 1, - ) - .filter( - (p) => - p.jsDocTags.find( - (tag) => - tag.name === "hidden" || - tag.name === "ignore" || - tag.name === "internal", - ) === undefined, - ) - .map((p) => [ - p.key.getSoleLiteral()!, - MetadataSchema.unalias(p.value).functions[0]!.parameters[0]!, - ]), - ); - - // build JSON Schema application, filtering out @human-tagged parameters - const errorMessages: string[] = []; - const application: IJsonSchemaApplication<"3.1"> = - JsonApplicationProgrammer.writeApplication({ - version: "3.1", - metadata, - filter: (p) => - p.jsDocTags.some((tag) => tag.name === "human") === false, - }); - // convert each JSON Schema function to an LLM function - const functions: Array | null> = - application.functions.map((func) => - writeFunction({ - context: props.context, - modulo: props.modulo, - className: props.name, - config: props.config, - components: application.components, - function: func, - errors: errorMessages, - parameter: functionParameters[func.name] ?? null, - }), - ); - return { - functions: functions.filter((f) => f !== null), - }; - }; - - const writeFunction = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - components: OpenApi.IComponents; - function: IJsonSchemaApplication.IFunction; - parameter: MetadataParameter | null; - errors: string[]; - className?: string; - config: - | Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - > - | undefined; - }): Omit | null => { - const config: ILlmSchema.IConfig = LlmSchemaConverter.getConfig( - props.config, - ); - - // convert parameters and output schemas - const parameters: ILlmSchema.IParameters | null = writeParameters({ - ...props, - config, - accessor: `$input.${props.function.name}.parameters`, - }); - if (parameters === null) return null; - const output: ILlmSchema.IParameters | null | undefined = writeOutput({ - config, - components: props.components, - schema: props.function.output?.schema ?? null, - errors: props.errors, - accessor: `$input.${props.function.name}.output`, - }); - if (output === null) return null; - - // fall back output description from the function metadata - if ( - output && - output.description === undefined && - !!props.function.output?.description?.length - ) - output.description = props.function.output.description; - - // assemble the LLM function - return { - name: props.function.name, - parameters, - output: output ?? undefined, - description: concatDescription({ - summary: props.function.summary, - description: props.function.description, - }), - deprecated: props.function.deprecated, - tags: props.function.tags, - validate: writeValidator({ - context: props.context, - modulo: props.modulo, - parameter: props.parameter, - name: props.function.name, - className: props.className, - equals: props.config?.equals ?? false, - }), - }; - }; - - const writeParameters = (props: { - components: OpenApi.IComponents; - function: IJsonSchemaApplication.IFunction; - errors: string[]; - accessor: string; - config: ILlmSchema.IConfig; - }): ILlmSchema.IParameters | null => { - // extract the first parameter's object schema, defaulting to empty object - const schema: OpenApi.IJsonSchema.IObject | OpenApi.IJsonSchema.IReference = - (props.function.parameters[0]?.schema as - | OpenApi.IJsonSchema.IObject - | OpenApi.IJsonSchema.IReference) ?? { - type: "object", - properties: {}, - additionalProperties: false, - required: [], - }; - return convertParameters({ - config: props.config, - components: props.components, - schema: { - ...schema, - title: schema.title ?? props.function.parameters[0]?.title, - description: - schema.description ?? props.function.parameters[0]?.description, - }, - accessor: props.accessor, - errors: props.errors, - }); - }; - - const writeOutput = (props: { - components: OpenApi.IComponents; - config: ILlmSchema.IConfig; - schema: OpenApi.IJsonSchema | null; - errors: string[]; - accessor: string; - }): ILlmSchema.IParameters | null | undefined => { - if (props.schema === null) return undefined; - return convertParameters({ - config: props.config, - components: props.components, - schema: props.schema as - | OpenApi.IJsonSchema.IObject - | OpenApi.IJsonSchema.IReference, - accessor: props.accessor, - errors: props.errors, - }); - }; - - /** - * Converts an OpenAPI object schema to {@link ILlmSchema.IParameters}, - * collecting transformation errors into the provided array. - */ - const convertParameters = (props: { - config: ILlmSchema.IConfig; - components: OpenApi.IComponents; - schema: OpenApi.IJsonSchema.IObject | OpenApi.IJsonSchema.IReference; - accessor: string; - errors: string[]; - }): ILlmSchema.IParameters | null => { - const result: IResult = - LlmSchemaConverter.parameters({ - config: props.config, - components: props.components, - schema: props.schema, - accessor: props.accessor, - }); - if (result.success === false) { - props.errors.push( - ...result.error.reasons.map((r) => ` - ${r.accessor}: ${r.message}`), - ); - return null; - } - return result.value; - }; - - const writeValidator = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - parameter: MetadataParameter | null; - name: string; - equals: boolean; - className?: string; - }): ((props: unknown) => IValidation) => { - // no parameter metadata — generate a permissive `any` validator - if (props.parameter === null) - return ValidateProgrammer.write({ - ...props, - type: props.context.checker.getTypeFromTypeNode( - TypeFactory.keyword("any"), - ), - config: { - equals: props.equals, - }, - name: undefined, - }) as any; - - // unreachable — tsType is always set by the transformer - if (props.parameter.tsType === undefined) - throw new Error( - "Failed to write LLM application's function validator. You don't have to call `LlmApplicationOfValidator.write()` function by yourself, but only by the `typia.llm.applicationOfValidate()` function.", - ); - - // generate a typed validator from the parameter's TypeScript type - return ValidateProgrammer.write({ - ...props, - type: props.parameter.tsType!, - config: { - equals: props.equals, - }, - name: props.className - ? `Parameters<${props.className}[${JSON.stringify(props.name)}]>[0]` - : undefined, - }) satisfies ts.CallExpression as any as ( - props: unknown, - ) => IValidation; - }; -} - -/** - * Concatenates summary and description into a single string. - * - * If both are present, joins them with a period and double newline, avoiding - * duplication when the description already starts with the summary. - */ -const concatDescription = (p: { - summary?: string | undefined; - description?: string | undefined; -}): string | undefined => { - if (!p.summary?.length || !p.description?.length) - return p.summary || p.description; - const summary: string = p.summary.endsWith(".") - ? p.summary.slice(0, -1) - : p.summary; - return p.description.startsWith(summary) - ? p.description - : summary + ".\n\n" + p.description; -}; diff --git a/packages/core/src/programmers/llm/LlmCoerceProgrammer.ts b/packages/core/src/programmers/llm/LlmCoerceProgrammer.ts deleted file mode 100644 index e07846eeef4..00000000000 --- a/packages/core/src/programmers/llm/LlmCoerceProgrammer.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ILlmSchema } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { LlmParametersProgrammer } from "./LlmParametersProgrammer"; - -export namespace LlmCoerceProgrammer { - export interface IProps extends IProgrammerProps { - config?: Partial; - } - - export const decompose = (props: { - context: IProps["context"]; - config?: Partial; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - metadata: MetadataSchema; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - // Generate LLM schema from metadata - const schema: ILlmSchema.IParameters = - LlmParametersProgrammer.writeParameters({ - metadata: props.metadata, - config: props.config, - }); - - const typeName = props.name ?? "unknown"; - - return { - functions: {}, - statements: [ - StatementFactory.constant({ - name: "__schema", - type: props.context.importer.type({ - file: "typia", - name: ts.factory.createQualifiedName( - ts.factory.createIdentifier("ILlmSchema"), - ts.factory.createIdentifier("IParameters"), - ), - }), - value: LiteralFactory.write(schema), - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode(typeName), - ), - ], - ts.factory.createTypeReferenceNode(typeName), - undefined, - ts.factory.createCallExpression( - props.context.importer.internal("coerceLlmArguments"), - undefined, - [ - ts.factory.createIdentifier("input"), - ts.factory.createIdentifier("__schema"), - ], - ), - ), - }; - }; - - export interface IWriteProps { - context: IProps["context"]; - modulo: ts.LeftHandSideExpression; - metadata: MetadataSchema; - config?: Partial; - name?: string; - } - - export const write = (props: IWriteProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - config: props.config, - modulo: props.modulo, - functor, - metadata: props.metadata, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const validate = (props: { - config?: Partial; - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => LlmParametersProgrammer.validate(props); -} diff --git a/packages/core/src/programmers/llm/LlmControllerProgrammer.ts b/packages/core/src/programmers/llm/LlmControllerProgrammer.ts deleted file mode 100644 index 1314d40e09f..00000000000 --- a/packages/core/src/programmers/llm/LlmControllerProgrammer.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ILlmSchema } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { LlmApplicationProgrammer } from "./LlmApplicationProgrammer"; - -export namespace LlmControllerProgrammer { - export interface IWriteProps { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - metadata: MetadataSchema; - config?: Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - >; - className?: string; - node: ts.TypeNode; - nameArgument: ts.Expression; - executeArgument: ts.Expression; - configArgument?: ts.Expression; - } - - export const write = (props: IWriteProps): ts.Expression => { - const typeNode: ts.ImportTypeNode = props.context.importer.type({ - file: "typia", - name: "ILlmController", - arguments: [props.node], - }); - const value: ts.ObjectLiteralExpression = - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "protocol", - ts.factory.createStringLiteral("class"), - ), - ts.factory.createPropertyAssignment("name", props.nameArgument), - ts.factory.createPropertyAssignment("execute", props.executeArgument), - ts.factory.createPropertyAssignment( - "application", - LlmApplicationProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: props.metadata, - config: props.config, - name: props.className, - configArgument: props.configArgument, - }), - ), - ], - true, - ); - return ts.factory.createAsExpression( - ts.factory.createSatisfiesExpression(value, typeNode), - typeNode, - ); - }; -} diff --git a/packages/core/src/programmers/llm/LlmMetadataFactory.ts b/packages/core/src/programmers/llm/LlmMetadataFactory.ts deleted file mode 100644 index f37a8874ab1..00000000000 --- a/packages/core/src/programmers/llm/LlmMetadataFactory.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObject } from "../../schemas/metadata/MetadataObject"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace LlmMetadataFactory { - export const getConfig = (props: { - context: ITypiaContext; - method: string; - node: ts.TypeNode | undefined; - }): - | Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - > - | undefined => { - if (props.node === undefined) return undefined; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(props.node); - const collection: MetadataCollection = new MetadataCollection(); - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: true, - escape: false, - constant: true, - functional: false, - }, - components: collection, - type, - }); - if (result.success === false) - throw new TransformerError({ - code: `typia.llm.${props.method}`, - message: `Failed to analyze generic argument "Config".`, - }); - - const meta: MetadataSchema = result.data; - if ( - meta.size() !== 1 || - meta.objects.length !== 1 || - meta.nullable === true || - meta.isRequired() === false - ) - throw new TransformerError({ - code: `typia.llm.${props.method}`, - message: `Invalid generic argument "Config". It must be a literal object type.`, - }); - - const obj: MetadataObject = meta.objects[0]!; - if (obj.type.properties.some((p) => p.key.isSoleLiteral() === false)) - throw new TransformerError({ - code: `typia.llm.${props.method}`, - message: `Invalid generic argument "Config". It must be a literal object type. Do not allow dynamic properties.`, - }); - else if ( - obj.type.properties.some( - (p) => - p.value.size() !== 1 || - p.value.constants.length !== 1 || - p.value.nullable === true || - p.value.isRequired() === false, - ) - ) - throw new TransformerError({ - code: `typia.llm.${props.method}`, - message: `Invalid generic argument "Config". It must be a literal object type. Do not allow variable type.`, - }); - const config: Partial = {}; - for (const prop of obj.type.properties) { - const key: string = prop.key.getSoleLiteral()!; - const value: boolean | bigint | number | string = - prop.value.constants[0]!.values[0]!.value; - if (typeof value === "bigint") - throw new TransformerError({ - code: `typia.llm.${props.method}`, - message: `Invalid generic argument "Config". It must be a literal object type. Do not allow bigint.`, - }); - (config as any)[key] = value; - } - return config; - }; -} diff --git a/packages/core/src/programmers/llm/LlmParametersProgrammer.ts b/packages/core/src/programmers/llm/LlmParametersProgrammer.ts deleted file mode 100644 index f54d77b0787..00000000000 --- a/packages/core/src/programmers/llm/LlmParametersProgrammer.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - IJsonSchemaCollection, - IJsonSchemaTransformError, - ILlmSchema, - IResult, - OpenApi, -} from "@typia/interface"; -import { LlmSchemaConverter, OpenApiTypeChecker } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { TransformerError } from "../../context/TransformerError"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { JsonSchemasProgrammer } from "../json/JsonSchemasProgrammer"; -import { LlmSchemaProgrammer } from "./LlmSchemaProgrammer"; - -export namespace LlmParametersProgrammer { - export interface IProps extends IProgrammerProps { - config?: Partial; - } - - export interface IWriteProps { - context: IProps["context"]; - metadata: MetadataSchema; - config?: Partial; - } - - export const write = (props: IWriteProps): ts.Expression => { - const schema: ILlmSchema.IParameters = writeParameters({ - metadata: props.metadata, - config: props.config, - }); - - const typeNode: ts.ImportTypeNode = props.context.importer.type({ - file: "typia", - name: ts.factory.createQualifiedName( - ts.factory.createIdentifier("ILlmSchema"), - ts.factory.createIdentifier("IParameters"), - ), - }); - - return ts.factory.createAsExpression( - ts.factory.createSatisfiesExpression( - LiteralFactory.write(schema), - typeNode, - ), - typeNode, - ); - }; - - export const writeParameters = (props: { - metadata: MetadataSchema; - config?: Partial; - }): ILlmSchema.IParameters => { - const collection: IJsonSchemaCollection<"3.1"> = - JsonSchemasProgrammer.writeSchemas({ - version: "3.1", - metadatas: [props.metadata], - }); - const schema: OpenApi.IJsonSchema.IObject = (() => { - const schema: OpenApi.IJsonSchema = collection.schemas[0]!; - if (OpenApiTypeChecker.isObject(schema)) return schema; - else if (OpenApiTypeChecker.isReference(schema)) { - const last = - collection.components.schemas?.[schema.$ref.split("/").pop()!]; - if (last && OpenApiTypeChecker.isObject(last)) return last; - } - throw new Error("Unreachable code. Failed to find the object schema."); - })(); - - const result: IResult = - LlmSchemaConverter.parameters({ - config: LlmSchemaConverter.getConfig(props.config), - components: collection.components, - schema, - }); - if (result.success === false) - throw new TransformerError({ - code: "typia.llm.parameters", - message: - "failed to convert JSON schema to LLM schema.\n\n" + - result.error.reasons - .map((r) => ` - ${r.accessor}: ${r.message}`) - .join("\n"), - }); - return result.value; - }; - - export const validate = (props: { - config?: Partial; - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const output: string[] = []; - if (props.explore.top === true) { - if (props.metadata.objects.length === 0) - output.push("LLM parameters must be an object type."); - else if (props.metadata.objects.length !== 1 || props.metadata.size() > 1) - output.push("LLM parameters must be a single object type."); - else { - if ( - props.metadata.objects[0]!.type.properties.some( - (p) => p.key.isSoleLiteral() === false, - ) - ) - output.push("LLM parameters must not have dynamic keys."); - if (props.metadata.nullable) - output.push("LLM parameters must be a non-nullable object type."); - if (props.metadata.isRequired() === false) - output.push("LLM parameters must be a non-undefined object type."); - } - } - output.push(...LlmSchemaProgrammer.validate(props)); - return output; - }; -} diff --git a/packages/core/src/programmers/llm/LlmParseProgrammer.ts b/packages/core/src/programmers/llm/LlmParseProgrammer.ts deleted file mode 100644 index 9712f3b804c..00000000000 --- a/packages/core/src/programmers/llm/LlmParseProgrammer.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ILlmSchema } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { LlmParametersProgrammer } from "./LlmParametersProgrammer"; - -export namespace LlmParseProgrammer { - export interface IProps extends IProgrammerProps { - config?: Partial; - } - - export const decompose = (props: { - context: IProps["context"]; - config?: Partial; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - metadata: MetadataSchema; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - // Generate LLM schema from metadata - const schema: ILlmSchema.IParameters = - LlmParametersProgrammer.writeParameters({ - metadata: props.metadata, - config: props.config, - }); - - return { - functions: {}, - statements: [ - StatementFactory.constant({ - name: "__schema", - type: props.context.importer.type({ - file: "typia", - name: ts.factory.createQualifiedName( - ts.factory.createIdentifier("ILlmSchema"), - ts.factory.createIdentifier("IParameters"), - ), - }), - value: LiteralFactory.write(schema), - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("string"))], - props.context.importer.type({ - file: "typia", - name: "IJsonParseResult", - arguments: [ - ts.factory.createTypeReferenceNode(props.name ?? "unknown"), - ], - }), - undefined, - ts.factory.createCallExpression( - props.context.importer.internal("parseLlmArguments"), - undefined, - [ - ts.factory.createIdentifier("input"), - ts.factory.createIdentifier("__schema"), - ], - ), - ), - }; - }; - - export interface IWriteProps { - context: IProps["context"]; - modulo: ts.LeftHandSideExpression; - metadata: MetadataSchema; - config?: Partial; - name?: string; - } - - export const write = (props: IWriteProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - context: props.context, - config: props.config, - modulo: props.modulo, - functor, - metadata: props.metadata, - name: props.name, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const validate = (props: { - config?: Partial; - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => LlmParametersProgrammer.validate(props); -} diff --git a/packages/core/src/programmers/llm/LlmSchemaProgrammer.ts b/packages/core/src/programmers/llm/LlmSchemaProgrammer.ts deleted file mode 100644 index 6ea38ca690f..00000000000 --- a/packages/core/src/programmers/llm/LlmSchemaProgrammer.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { - IJsonSchemaCollection, - IJsonSchemaTransformError, - ILlmSchema, - IResult, -} from "@typia/interface"; -import { LlmSchemaConverter } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { TransformerError } from "../../context/TransformerError"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { AtomicPredicator } from "../helpers/AtomicPredicator"; -import { json_schema_bigint } from "../iterate/json_schema_bigint"; -import { json_schema_boolean } from "../iterate/json_schema_boolean"; -import { json_schema_native } from "../iterate/json_schema_native"; -import { json_schema_number } from "../iterate/json_schema_number"; -import { json_schema_string } from "../iterate/json_schema_string"; -import { JsonSchemasProgrammer } from "../json/JsonSchemasProgrammer"; - -export namespace LlmSchemaProgrammer { - export interface IProps extends IProgrammerProps { - config?: Partial; - } - - export interface IOutput { - schema: ILlmSchema; - $defs: Record; - } - - export interface IWriteProps { - context: IProps["context"]; - metadata: MetadataSchema; - config?: Partial; - } - - export const write = (props: IWriteProps): ts.Expression => { - const output: IOutput = writeSchema({ - metadata: props.metadata, - config: props.config, - }); - - const schemaTypeNode: ts.ImportTypeNode = props.context.importer.type({ - file: "typia", - name: "ILlmSchema", - }); - - const schemaLiteral: ts.Expression = ts.factory.createAsExpression( - ts.factory.createSatisfiesExpression( - LiteralFactory.write(output.schema), - schemaTypeNode, - ), - schemaTypeNode, - ); - - // If no $defs, just return the schema literal - if (Object.keys(output.$defs).length === 0) { - return schemaLiteral; - } - - // Has $defs - need to create a function that takes $defs parameter - return ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "$defs", - ts.factory.createTypeReferenceNode("Record", [ - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - schemaTypeNode, - ]), - undefined, - ), - ], - schemaTypeNode, - undefined, - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("Object.assign"), - undefined, - [ - ts.factory.createIdentifier("$defs"), - ts.factory.createAsExpression( - LiteralFactory.write(output.$defs), - ts.factory.createTypeReferenceNode("Record", [ - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.StringKeyword, - ), - schemaTypeNode, - ]), - ), - ], - ), - ), - ts.factory.createReturnStatement(schemaLiteral), - ], - true, - ), - ); - }; - - export const writeSchema = (props: { - metadata: MetadataSchema; - config?: Partial; - }): IOutput => { - const collection: IJsonSchemaCollection<"3.1"> = - JsonSchemasProgrammer.writeSchemas({ - version: "3.1", - metadatas: [props.metadata], - }); - - const $defs: Record = {}; - const result: IResult = - LlmSchemaConverter.schema({ - config: LlmSchemaConverter.getConfig(props.config), - components: collection.components, - schema: collection.schemas[0]!, - $defs, - }); - if (result.success === false) - throw new TransformerError({ - code: "typia.llm.schema", - message: - "failed to convert JSON schema to LLM schema.\n\n" + - result.error.reasons - .map((r) => ` - ${r.accessor}: ${r.message}`) - .join("\n"), - }); - return { - $defs, - schema: result.value, - }; - }; - - export const validate = (props: { - config?: Partial; - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => { - const output: string[] = []; - - // no additionalProperties in OpenAI strict mode - const config: ILlmSchema.IConfig = LlmSchemaConverter.getConfig( - props.config, - ); - if ( - config.strict === true && - props.metadata.objects.some((o) => - o.type.properties.some( - (p) => p.key.isSoleLiteral() === false && p.value.size() !== 0, - ), - ) - ) - output.push(`Strict mode does not allow dynamic property in object.`); - - // OpenAI strict mode even does not support the optional property - if ( - config.strict === true && - props.metadata.objects.some((o) => - o.type.properties.some((p) => p.value.isRequired() === false), - ) - ) - output.push(`Strict mode does not support optional property in object.`); - - // just JSON rule - if ( - props.metadata.atomics.some((a) => a.type === "bigint") || - props.metadata.constants.some((c) => c.type === "bigint") - ) - output.push("LLM schema does not support bigint type."); - if (props.metadata.tuples.length !== 0) - output.push("LLM schema does not support tuple type."); - if (props.metadata.arrays.some((a) => a.type.value.isRequired() === false)) - output.push("LLM schema does not support undefined type in array."); - if (props.metadata.maps.length) - output.push("LLM schema does not support Map type."); - if (props.metadata.sets.length) - output.push("LLM schema does not support Set type."); - for (const native of props.metadata.natives) - if ( - AtomicPredicator.native(native.name) === false && - native.name !== "Date" && - native.name !== "Blob" && - native.name !== "File" - ) - output.push(`LLM schema does not support ${native.name} type.`); - return output; - }; -} - -const size = (metadata: MetadataSchema): number => - (metadata.escaped ? size(metadata.escaped.returns) : 0) + - metadata.aliases.length + - metadata.objects.length + - metadata.arrays.length + - metadata.tuples.length + - (metadata.maps.length ? 1 : 0) + - (metadata.sets.length ? 1 : 0) + - metadata.atomics - .map((a) => - a.type === "boolean" - ? json_schema_boolean(a).length - : a.type === "bigint" - ? json_schema_bigint(a).length - : a.type === "number" - ? json_schema_number(a).length - : json_schema_string(a).length, - ) - .reduce((a, b) => a + b, 0) + - metadata.constants.filter( - (c) => metadata.atomics.some((a) => a.type === c.type) === false, - ).length + - metadata.templates.length + - metadata.natives - .filter( - (n) => - metadata.atomics.some((a) => a.type === n.name) === false && - metadata.constants.some((c) => c.type === n.name) === false, - ) - .map( - (n) => - json_schema_native({ - components: {}, - native: n, - }).length, - ) - .reduce((a, b) => a + b, 0); diff --git a/packages/core/src/programmers/llm/LlmStructuredOutputProgrammer.ts b/packages/core/src/programmers/llm/LlmStructuredOutputProgrammer.ts deleted file mode 100644 index 71c241d7ddc..00000000000 --- a/packages/core/src/programmers/llm/LlmStructuredOutputProgrammer.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { ILlmSchema } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { LiteralFactory } from "../../factories/LiteralFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { LlmParametersProgrammer } from "./LlmParametersProgrammer"; - -export namespace LlmStructuredOutputProgrammer { - export interface IProps extends IProgrammerProps { - config?: Partial; - } - - export interface IWriteProps { - context: IProps["context"]; - modulo: ts.LeftHandSideExpression; - type: ts.Type; - metadata: MetadataSchema; - config?: Partial; - name?: string; - } - - export const write = (props: IWriteProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - - // Generate LLM parameters schema - const schema: ILlmSchema.IParameters = - LlmParametersProgrammer.writeParameters({ - metadata: props.metadata, - config: props.config, - }); - - // Generate validate function - const equals: boolean = props.config?.equals ?? false; - const validateDecomposed: FeatureProgrammer.IDecomposed = - ValidateProgrammer.decompose({ - context: props.context, - modulo: props.modulo, - functor, - config: { equals }, - type: props.type, - name: props.name, - }); - - const typeName = props.name ?? "unknown"; - - const result: FeatureProgrammer.IDecomposed = { - functions: { - ...validateDecomposed.functions, - }, - statements: [ - // Schema constant - StatementFactory.constant({ - name: "__parameters", - type: props.context.importer.type({ - file: "typia", - name: ts.factory.createQualifiedName( - ts.factory.createIdentifier("ILlmSchema"), - ts.factory.createIdentifier("IParameters"), - ), - }), - value: LiteralFactory.write(schema), - }), - // Validate function statements - ...validateDecomposed.statements, - // Validate function - StatementFactory.constant({ - name: "__validate", - value: validateDecomposed.arrow, - }), - ], - // Return object literal directly, not a function - // Cast to ArrowFunction to satisfy FeatureProgrammer.IDecomposed type - arrow: ts.factory.createAsExpression( - ts.factory.createObjectLiteralExpression( - [ - // parameters - ts.factory.createPropertyAssignment( - "parameters", - ts.factory.createIdentifier("__parameters"), - ), - // parse - ts.factory.createPropertyAssignment( - "parse", - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - TypeFactory.keyword("string"), - ), - ], - props.context.importer.type({ - file: "typia", - name: "IJsonParseResult", - arguments: [ts.factory.createTypeReferenceNode(typeName)], - }), - undefined, - ts.factory.createCallExpression( - props.context.importer.internal("parseLlmArguments"), - undefined, - [ - ts.factory.createIdentifier("input"), - ts.factory.createIdentifier("__parameters"), - ], - ), - ), - ), - // coerce - ts.factory.createPropertyAssignment( - "coerce", - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode(typeName), - ), - ], - ts.factory.createTypeReferenceNode(typeName), - undefined, - ts.factory.createCallExpression( - props.context.importer.internal("coerceLlmArguments"), - undefined, - [ - ts.factory.createIdentifier("input"), - ts.factory.createIdentifier("__parameters"), - ], - ), - ), - ), - // validate - ts.factory.createPropertyAssignment( - "validate", - ts.factory.createIdentifier("__validate"), - ), - ], - true, - ), - props.context.importer.type({ - file: "typia", - name: "ILlmStructuredOutput", - arguments: [ts.factory.createTypeReferenceNode(typeName)], - }), - ) as any as ts.ArrowFunction, - }; - - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - export const validate = (props: { - config?: Partial; - metadata: MetadataSchema; - explore: MetadataFactory.IExplore; - }): string[] => LlmParametersProgrammer.validate(props); -} diff --git a/packages/core/src/programmers/llm/index.ts b/packages/core/src/programmers/llm/index.ts deleted file mode 100644 index d6a35fa2fe2..00000000000 --- a/packages/core/src/programmers/llm/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from "./LlmApplicationProgrammer"; -export * from "./LlmControllerProgrammer"; -export * from "./LlmCoerceProgrammer"; -export * from "./LlmMetadataFactory"; -export * from "./LlmParametersProgrammer"; -export * from "./LlmParseProgrammer"; -export * from "./LlmSchemaProgrammer"; -export * from "./LlmStructuredOutputProgrammer"; diff --git a/packages/core/src/programmers/misc/MiscAssertCloneProgrammer.ts b/packages/core/src/programmers/misc/MiscAssertCloneProgrammer.ts deleted file mode 100644 index 9a1ab384794..00000000000 --- a/packages/core/src/programmers/misc/MiscAssertCloneProgrammer.ts +++ /dev/null @@ -1,93 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { MiscCloneProgrammer } from "./MiscCloneProgrammer"; - -export namespace MiscAssertCloneProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - config: { - equals: false, - guard: false, - }, - }); - const clone: FeatureProgrammer.IDecomposed = MiscCloneProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...assert.functions, - ...clone.functions, - }, - statements: [ - ...assert.statements, - ...clone.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__clone", - value: clone.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - clone.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__clone"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createIdentifier("input"), - AssertProgrammer.Guardian.identifier(), - ], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/misc/MiscAssertPruneProgrammer.ts b/packages/core/src/programmers/misc/MiscAssertPruneProgrammer.ts deleted file mode 100644 index 97de7a4050c..00000000000 --- a/packages/core/src/programmers/misc/MiscAssertPruneProgrammer.ts +++ /dev/null @@ -1,114 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { MiscPruneProgrammer } from "./MiscPruneProgrammer"; - -export namespace MiscAssertPruneProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - config: { - equals: false, - guard: false, - }, - }); - const prune: FeatureProgrammer.IDecomposed = MiscPruneProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...assert.functions, - ...prune.functions, - }, - statements: [ - ...assert.statements, - ...prune.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__prune", - value: prune.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - undefined, - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("input"), - ts.SyntaxKind.EqualsToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createIdentifier("input"), - AssertProgrammer.Guardian.identifier(), - ], - ), - ), - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__prune"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("input"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/misc/MiscCloneProgrammer.ts b/packages/core/src/programmers/misc/MiscCloneProgrammer.ts deleted file mode 100644 index 92785084894..00000000000 --- a/packages/core/src/programmers/misc/MiscCloneProgrammer.ts +++ /dev/null @@ -1,1029 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataMap } from "../../schemas/metadata/MetadataMap"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataSet } from "../../schemas/metadata/MetadataSet"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; -import { IsProgrammer } from "../IsProgrammer"; -import { CloneJoiner } from "../helpers/CloneJoiner"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { decode_union_object } from "../iterate/decode_union_object"; -import { postfix_of_tuple } from "../iterate/postfix_of_tuple"; -import { wrap_metadata_rest_tuple } from "../iterate/wrap_metadata_rest_tuple"; - -export namespace MiscCloneProgrammer { - export const decompose = (props: { - validated: boolean; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const config: FeatureProgrammer.IConfig = configure(props); - if (props.validated === false) - config.addition = (collection) => - IsProgrammer.write_function_statements({ - context: props.context, - functor: props.functor, - collection, - }); - const composed: FeatureProgrammer.IComposed = FeatureProgrammer.compose({ - ...props, - config, - }); - return { - functions: composed.functions, - statements: composed.statements, - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - composed.response, - undefined, - composed.body, - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - validated: false, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const write_array_functions = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .arrays() - .filter((a) => a.recursive) - .map((type, i) => - StatementFactory.constant({ - name: `${props.config.prefix}a${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_array_inline({ - config: props.config, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - array: MetadataArray.create({ - type, - tags: [], - }), - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - const write_tuple_functions = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .tuples() - .filter((t) => t.recursive) - .map((tuple, i) => - StatementFactory.constant({ - name: `${props.config.prefix}t${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_tuple_inline({ - config: props.config, - context: props.context, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - tuple, - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decode = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - // ANY TYPE - if ( - props.metadata.any || - props.metadata.arrays.some((a) => a.type.value.any) || - props.metadata.tuples.some( - (t) => !!t.type.elements.length && t.type.elements.every((e) => e.any), - ) - ) - return ts.factory.createCallExpression( - props.context.importer.internal("miscCloneAny"), - undefined, - [props.input], - ); - - interface IUnion { - type: string; - is: () => ts.Expression; - value: () => ts.Expression; - } - const unions: IUnion[] = []; - - //---- - // LIST UP UNION TYPES - //---- - // FUNCTIONAL - if (props.metadata.functions.length) - unions.push({ - type: "functional", - is: () => - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("function"), - ts.factory.createTypeOfExpression(props.input), - ), - value: () => ts.factory.createIdentifier("undefined"), - }); - - // TUPLES - for (const tuple of props.metadata.tuples) - unions.push({ - type: "tuple", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.tuples.push(tuple); - return partial; - })(), - }), - value: () => - decode_tuple({ - ...props, - tuple, - }), - }); - - // ARRAYS - if (props.metadata.arrays.length) - unions.push({ - type: "array", - is: () => ExpressionFactory.isArray(props.input), - value: () => - explore_arrays({ - ...props, - arrays: props.metadata.arrays, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - - // NATIVE TYPES - if (props.metadata.sets.length) - unions.push({ - type: "set", - is: () => ExpressionFactory.isInstanceOf("Set", props.input), - value: () => - explore_sets({ - ...props, - sets: props.metadata.sets, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - if (props.metadata.maps.length) - unions.push({ - type: "map", - is: () => ExpressionFactory.isInstanceOf("Map", props.input), - value: () => - explore_maps({ - ...props, - maps: props.metadata.maps, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - for (const native of props.metadata.natives) - unions.push({ - type: "native", - is: () => ExpressionFactory.isInstanceOf(native.name, props.input), - value: () => - native.name === "Boolean" || - native.name === "Number" || - native.name === "String" - ? ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "valueOf"), - undefined, - undefined, - ) - : decode_native({ - type: native.name, - input: props.input, - }), - }); - - // OBJECTS - if (props.metadata.objects.length) - unions.push({ - type: "object", - is: () => - ExpressionFactory.isObject({ - checkNull: true, - checkArray: false, - input: props.input, - }), - value: () => - explore_objects({ - ...props, - explore: { - ...props.explore, - from: "object", - }, - }), - }); - - // COMPOSITION - if (unions.length === 0) return props.input; - else if (unions.length === 1 && props.metadata.size() === 1) { - const value: ts.Expression = - (props.metadata.nullable || props.metadata.isRequired() === false) && - is_instance(props.metadata) - ? ts.factory.createConditionalExpression( - props.input, - undefined, - unions[0]!.value(), - undefined, - props.input, - ) - : unions[0]!.value(); - return ts.factory.createAsExpression(value, TypeFactory.keyword("any")); - } else { - let last: ts.Expression = props.input; - for (const u of unions.reverse()) - last = ts.factory.createConditionalExpression( - u.is(), - undefined, - u.value(), - undefined, - last, - ); - return ts.factory.createAsExpression(last, TypeFactory.keyword("any")); - } - }; - - const decode_object = (props: { - functor: FunctionProgrammer; - input: ts.Expression; - object: MetadataObjectType; - explore: FeatureProgrammer.IExplore; - }) => - FeatureProgrammer.decode_object({ - config: { - trace: false, - path: false, - prefix: PREFIX, - }, - functor: props.functor, - input: props.input, - object: props.object, - explore: props.explore, - }); - - const decode_array = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - props.array.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}a${props.array.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - from: "array", - }, - input: props.input, - }), - ) - : decode_array_inline(props); - - const decode_array_inline = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - FeatureProgrammer.decode_array({ - config: props.config, - functor: props.functor, - combiner: CloneJoiner.array, - array: props.array, - input: props.input, - explore: props.explore, - }); - - const decode_tuple = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - tuple: MetadataTuple; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - props.tuple.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}t${props.tuple.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - }, - input: props.input, - }), - ) - : decode_tuple_inline({ - ...props, - tuple: props.tuple.type, - }); - - const decode_tuple_inline = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - tuple: MetadataTupleType; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - const elements: ts.Expression[] = props.tuple.elements - .filter((m) => m.rest === null) - .map((elem, index) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createElementAccessExpression(props.input, index), - metadata: elem, - explore: { - ...props.explore, - from: "array", - postfix: props.explore.postfix.length - ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` - : `"[${index}]"`, - }, - }), - ); - const rest = (() => { - if (props.tuple.elements.length === 0) return null; - - const last: MetadataSchema = props.tuple.elements.at(-1)!; - const rest: MetadataSchema | null = last.rest; - if (rest === null) return null; - - return decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "slice"), - undefined, - [ExpressionFactory.number(props.tuple.elements.length - 1)], - ), - metadata: wrap_metadata_rest_tuple(props.tuple.elements.at(-1)!.rest!), - explore: { - ...props.explore, - start: props.tuple.elements.length - 1, - }, - }); - })(); - return CloneJoiner.tuple({ - elements, - rest, - }); - }; - - /* ----------------------------------------------------------- - NATIVE CLASSES - ----------------------------------------------------------- */ - const decode_native = (props: { type: string; input: ts.Expression }) => - props.type === "Date" || - props.type === "Uint8Array" || - props.type === "Uint8ClampedArray" || - props.type === "Uint16Array" || - props.type === "Uint32Array" || - props.type === "BigUint64Array" || - props.type === "Int8Array" || - props.type === "Int16Array" || - props.type === "Int32Array" || - props.type === "BigInt64Array" || - props.type === "Float32Array" || - props.type === "Float64Array" || - props.type === "RegExp" - ? decode_native_copyable(props) - : props.type === "ArrayBuffer" || props.type === "SharedArrayBuffer" - ? decode_native_buffer({ - type: props.type, - input: props.input, - }) - : props.type === "DataView" - ? decode_native_data_view(props.input) - : ts.factory.createCallExpression( - ts.factory.createIdentifier(props.type), - undefined, - [], - ); - - const decode_native_copyable = (props: { - type: string; - input: ts.Expression; - }) => - ts.factory.createNewExpression( - ts.factory.createIdentifier(props.type), - undefined, - [props.input], - ); - - const decode_native_buffer = (props: { - type: "ArrayBuffer" | "SharedArrayBuffer"; - input: ts.Expression; - }) => - ExpressionFactory.selfCall( - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "buffer", - value: ts.factory.createNewExpression( - ts.factory.createIdentifier(props.type), - undefined, - [IdentifierFactory.access(props.input, "byteLength")], - ), - }), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createNewExpression( - ts.factory.createIdentifier("Uint8Array"), - undefined, - [ts.factory.createIdentifier("buffer")], - ), - "set", - ), - undefined, - [ - ts.factory.createNewExpression( - ts.factory.createIdentifier("Uint8Array"), - undefined, - [props.input], - ), - ], - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("buffer"), - ), - ], - true, - ), - ); - - const decode_native_data_view = (input: ts.Expression) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("DataView"), - undefined, - [IdentifierFactory.access(input, "buffer")], - ); - - /* ----------------------------------------------------------- - EXPLORERS FOR UNION TYPES - ----------------------------------------------------------- */ - const explore_sets = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - sets: MetadataSet[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - ts.factory.createCallExpression( - UnionExplorer.set({ - config: { - checker: (v) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: v.input, - metadata: v.definition, - explore: v.explore, - }), - decoder: (v) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Set"), - [TypeFactory.keyword("any")], - [ - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - ], - ), - empty: ts.factory.createNewExpression( - ts.factory.createIdentifier("Set"), - [TypeFactory.keyword("any")], - [], - ), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: [], - input: props.input, - sets: props.sets, - explore: props.explore, - }), - undefined, - undefined, - ); - - const explore_maps = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - maps: MetadataMap[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - ts.factory.createCallExpression( - UnionExplorer.map({ - config: { - checker: (v) => - ts.factory.createLogicalAnd( - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: ts.factory.createElementAccessExpression(v.input, 0), - metadata: v.definition[0], - explore: { - ...v.explore, - postfix: `${v.explore.postfix}[0]`, - }, - }), - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: ts.factory.createElementAccessExpression(v.input, 1), - metadata: v.definition[1], - explore: { - ...v.explore, - postfix: `${v.explore.postfix}[1]`, - }, - }), - ), - decoder: (v) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - [TypeFactory.keyword("any"), TypeFactory.keyword("any")], - [ - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - ], - ), - empty: ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - [TypeFactory.keyword("any"), TypeFactory.keyword("any")], - [], - ), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: [], - input: props.input, - maps: props.maps, - explore: props.explore, - }), - // ([])(props.input, props.maps, props.explore), - undefined, - undefined, - ); - - const explore_objects = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - }) => - props.metadata.objects.length === 1 - ? decode_object({ - ...props, - object: props.metadata.objects[0]!.type, - }) - : ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(`${PREFIX}u${props.metadata.union_index!}`), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ); - - const explore_arrays = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - arrays: MetadataArray[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - explore_array_like_union_types({ - ...props, - definitions: props.arrays, - factory: (next) => - UnionExplorer.array({ - config: { - checker: (v) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: v.input, - metadata: v.definition, - explore: v.explore, - }), - decoder: (v) => - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - empty: ts.factory.createIdentifier("[]"), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: next.parameters, - arrays: next.definitions, - input: next.input, - explore: next.explore, - }), - }); - - const explore_array_like_union_types = < - T extends MetadataArray | MetadataTuple, - >(props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - factory: (next: { - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - definitions: T[]; - explore: FeatureProgrammer.IExplore; - }) => ts.ArrowFunction; - input: ts.Expression; - definitions: T[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - const arrow = (next: { - parameters: ts.ParameterDeclaration[]; - explore: FeatureProgrammer.IExplore; - input: ts.Expression; - }): ts.ArrowFunction => - props.factory({ - definitions: props.definitions, - parameters: next.parameters, - input: next.input, - explore: next.explore, - }); - if (props.definitions.every((e) => e.type.recursive === false)) - ts.factory.createCallExpression( - arrow({ - parameters: [], - explore: props.explore, - input: props.input, - }), - undefined, - [], - ); - - const arrayExplore: FeatureProgrammer.IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.emplaceUnion( - props.config.prefix, - props.definitions.map((e) => e.type.name).join(" | "), - () => - arrow({ - parameters: FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - explore: { - ...arrayExplore, - postfix: "", - }, - input: ts.factory.createIdentifier("input"), - }), - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - input: props.input, - explore: arrayExplore, - }), - ); - }; - - /* ----------------------------------------------------------- - CONFIGURATIONS - ----------------------------------------------------------- */ - const PREFIX = "_c"; - - const configure = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - }): FeatureProgrammer.IConfig => { - const config: FeatureProgrammer.IConfig = { - types: { - input: (type, name) => - ts.factory.createTypeReferenceNode( - name ?? - TypeFactory.getFullName({ checker: props.context.checker, type }), - ), - output: (type, name) => - props.context.importer.type({ - file: "typia", - name: "Resolved", - arguments: [ - ts.factory.createTypeReferenceNode( - name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type, - }), - ), - ], - }), - }, - prefix: PREFIX, - trace: false, - path: false, - initializer, - decoder: (next) => - decode({ - context: props.context, - functor: props.functor, - config, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - objector: { - checker: (next) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - decoder: (next) => - decode_object({ - functor: props.functor, - input: next.input, - object: next.object, - explore: next.explore, - }), - joiner: CloneJoiner.object, - unionizer: (next) => - decode_union_object({ - checker: (v) => - IsProgrammer.decode_object({ - context: props.context, - functor: props.functor, - input: v.input, - object: v.object, - explore: v.explore, - }), - decoder: (v) => - decode_object({ - functor: props.functor, - input: v.input, - object: v.object, - explore: v.explore, - }), - success: (exp) => exp, - escaper: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - input: next.input, - objects: next.objects, - explore: next.explore, - }), - failure: (next) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: next.expected, - input: next.input, - }), - }, - generator: { - arrays: (collection) => - write_array_functions({ - functor: props.functor, - config, - collection, - }), - tuples: (collection) => - write_tuple_functions({ - context: props.context, - functor: props.functor, - config, - collection, - }), - }, - }; - return config; - }; - - const initializer: FeatureProgrammer.IConfig["initializer"] = (props) => { - const collection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - validate: ({ metadata }) => { - const output: string[] = []; - if (metadata.natives.some((native) => native.name === "WeakSet")) - output.push("unable to clone WeakSet"); - else if (metadata.natives.some((native) => native.name === "WeakMap")) - output.push("unable to clone WeakMap"); - return output; - }, - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - return { - collection, - metadata: result.data, - }; - }; - - const create_throw_error = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - expected: string; - input: ts.Expression; - }) => - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - props.context.importer.internal("throwTypeGuardError"), - [], - [ - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "method", - ts.factory.createStringLiteral(props.functor.method), - ), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.input), - ], - true, - ), - ], - ), - ); - - const is_instance = (metadata: MetadataSchema): boolean => - !!metadata.objects.length || - !!metadata.arrays.length || - !!metadata.tuples.length || - !!metadata.sets.length || - !!metadata.maps.length || - !!metadata.natives.length || - (metadata.rest !== null && is_instance(metadata.rest)); -} diff --git a/packages/core/src/programmers/misc/MiscIsCloneProgrammer.ts b/packages/core/src/programmers/misc/MiscIsCloneProgrammer.ts deleted file mode 100644 index 84ce247f9ec..00000000000 --- a/packages/core/src/programmers/misc/MiscIsCloneProgrammer.ts +++ /dev/null @@ -1,97 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { MiscCloneProgrammer } from "./MiscCloneProgrammer"; - -export namespace MiscIsCloneProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - config: { - equals: false, - }, - }); - const clone: FeatureProgrammer.IDecomposed = MiscCloneProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...is.functions, - ...clone.functions, - }, - statements: [ - ...is.statements, - ...clone.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__clone", - value: clone.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - ts.factory.createUnionTypeNode([ - clone.arrow.type ?? TypeFactory.keyword("any"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__clone"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/misc/MiscIsPruneProgrammer.ts b/packages/core/src/programmers/misc/MiscIsPruneProgrammer.ts deleted file mode 100644 index 75ed4830f15..00000000000 --- a/packages/core/src/programmers/misc/MiscIsPruneProgrammer.ts +++ /dev/null @@ -1,95 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { MiscPruneProgrammer } from "./MiscPruneProgrammer"; - -export namespace MiscIsPruneProgrammer { - export const decompose = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - config: { - equals: false, - }, - }); - const prune: FeatureProgrammer.IDecomposed = MiscPruneProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...is.functions, - ...prune.functions, - }, - statements: [ - ...is.statements, - ...prune.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__prune", - value: prune.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - is.arrow.type, - undefined, - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createEquality( - ts.factory.createFalse(), - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createFalse()), - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__prune"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createTrue()), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/misc/MiscLiteralsProgrammer.ts b/packages/core/src/programmers/misc/MiscLiteralsProgrammer.ts deleted file mode 100644 index cc1668de475..00000000000 --- a/packages/core/src/programmers/misc/MiscLiteralsProgrammer.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Atomic } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; - -export namespace MiscLiteralsProgrammer { - export interface IProps { - context: ITypiaContext; - type: ts.Type; - } - export const write = (props: IProps) => { - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: true, - constant: true, - absorb: true, - validate: ({ metadata }) => { - const length: number = - metadata.constants - .map((c) => c.values.length) - .reduce((a, b) => a + b, 0) + - metadata.atomics.filter((a) => a.type === "boolean").length; - if (0 === length) return [ErrorMessages.NO]; - else if (metadata.size() !== length) return [ErrorMessages.ONLY]; - return []; - }, - }, - components: new MetadataCollection(), - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: `typia.misc.literals`, - errors: result.errors, - }); - - const metadata: MetadataSchema = result.data; - const values: Set = new Set([ - ...metadata.constants.map((c) => c.values.map((v) => v.value)).flat(), - ...(metadata.atomics.filter((a) => a.type === "boolean").length - ? [true, false] - : []), - ...(metadata.nullable ? [null] : []), - ]); - return ts.factory.createAsExpression( - ts.factory.createArrayLiteralExpression( - [...values].map((v) => - v === null - ? ts.factory.createNull() - : typeof v === "boolean" - ? v - ? ts.factory.createTrue() - : ts.factory.createFalse() - : typeof v === "number" - ? ExpressionFactory.number(v) - : typeof v === "bigint" - ? ExpressionFactory.bigint(Number(v)) - : ts.factory.createStringLiteral(v), - ), - true, - ), - ts.factory.createTypeReferenceNode("const"), - ); - }; -} - -enum ErrorMessages { - NO = "no constant literal type found.", - ONLY = "only constant literal types are allowed.", -} diff --git a/packages/core/src/programmers/misc/MiscPruneProgrammer.ts b/packages/core/src/programmers/misc/MiscPruneProgrammer.ts deleted file mode 100644 index fe9d1574c57..00000000000 --- a/packages/core/src/programmers/misc/MiscPruneProgrammer.ts +++ /dev/null @@ -1,725 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { PruneJoiner } from "../helpers/PruneJoiner"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { decode_union_object } from "../iterate/decode_union_object"; -import { postfix_of_tuple } from "../iterate/postfix_of_tuple"; -import { wrap_metadata_rest_tuple } from "../iterate/wrap_metadata_rest_tuple"; - -export namespace MiscPruneProgrammer { - export const decompose = (props: { - validated: boolean; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const config = configure(props); - if (props.validated === false) - config.addition = (collection) => - IsProgrammer.write_function_statements({ - context: props.context, - functor: props.functor, - collection, - }); - const composed: FeatureProgrammer.IComposed = FeatureProgrammer.compose({ - ...props, - config, - }); - return { - functions: composed.functions, - statements: composed.statements, - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - composed.response, - undefined, - composed.body, - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - validated: false, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const write_array_functions = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .arrays() - .filter((a) => a.recursive) - .map((type, i) => - StatementFactory.constant({ - name: `${props.config.prefix}a${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_array_inline({ - config: props.config, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - array: MetadataArray.create({ - type, - tags: [], - }), - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - const write_tuple_functions = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .tuples() - .filter((t) => t.recursive) - .map((tuple, i) => - StatementFactory.constant({ - name: `${props.config.prefix}t${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_tuple_inline({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - tuple, - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decode = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - }): ts.ConciseBody => { - if (filter(props.metadata) === false) return ts.factory.createBlock([]); - - interface IUnion { - type: string; - is: () => ts.Expression; - value: () => ts.Expression | ts.Block | ts.ReturnStatement; - } - const unions: IUnion[] = []; - - //---- - // LIST UP UNION TYPES - //---- - // TUPLES - for (const tuple of props.metadata.tuples.filter((tuple) => - tuple.type.elements.some((e) => filter(e.rest ?? e)), - )) - unions.push({ - type: "tuple", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.tuples.push(tuple); - return partial; - })(), - }), - value: () => - decode_tuple({ - ...props, - tuple, - }), - }); - - // ARRAYS - if (props.metadata.arrays.filter((a) => filter(a.type.value)).length) - unions.push({ - type: "array", - is: () => ExpressionFactory.isArray(props.input), - value: () => - explore_arrays({ - ...props, - arrays: props.metadata.arrays, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - - // BUILT-IN CLASSES - if (props.metadata.natives.length) - for (const native of props.metadata.natives) - unions.push({ - type: "native", - is: () => ExpressionFactory.isInstanceOf(native.name, props.input), - value: () => ts.factory.createReturnStatement(), - }); - if (props.metadata.sets.length) - unions.push({ - type: "set", - is: () => ExpressionFactory.isInstanceOf("Set", props.input), - value: () => ts.factory.createReturnStatement(), - }); - if (props.metadata.maps.length) - unions.push({ - type: "map", - is: () => ExpressionFactory.isInstanceOf("Map", props.input), - value: () => ts.factory.createReturnStatement(), - }); - - // OBJECTS - if (props.metadata.objects.length) - unions.push({ - type: "object", - is: () => - ExpressionFactory.isObject({ - checkNull: true, - checkArray: false, - input: props.input, - }), - value: () => - explore_objects({ - ...props, - explore: { - ...props.explore, - from: "object", - }, - }), - }); - - //---- - // STATEMENTS - //---- - const converter = (v: ts.Expression | ts.Block | ts.ReturnStatement) => - ts.isReturnStatement(v) || ts.isBlock(v) - ? v - : ts.factory.createExpressionStatement(v); - - const statements: ts.Statement[] = unions.map((u) => - ts.factory.createIfStatement(u.is(), converter(u.value())), - ); - return ts.factory.createBlock(statements, true); - }; - - const decode_object = (props: { - functor: FunctionProgrammer; - input: ts.Expression; - object: MetadataObjectType; - explore: FeatureProgrammer.IExplore; - }) => - FeatureProgrammer.decode_object({ - config: { - trace: false, - path: false, - prefix: PREFIX, - }, - functor: props.functor, - object: props.object, - input: props.input, - explore: props.explore, - }); - - const decode_array = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - props.array.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}a${props.array.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - from: "array", - }, - input: props.input, - }), - ) - : decode_array_inline(props); - - const decode_array_inline = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - FeatureProgrammer.decode_array({ - config: props.config, - functor: props.functor, - combiner: PruneJoiner.array, - array: props.array, - input: props.input, - explore: props.explore, - }); - - const decode_tuple = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - tuple: MetadataTuple; - explore: FeatureProgrammer.IExplore; - }): ts.Expression | ts.Block => - props.tuple.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}t${props.tuple.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - }, - input: props.input, - }), - ) - : decode_tuple_inline({ - ...props, - tuple: props.tuple.type, - }); - - const decode_tuple_inline = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - tuple: MetadataTupleType; - explore: FeatureProgrammer.IExplore; - }): ts.Block => { - const elements: ts.ConciseBody[] = props.tuple.elements - .map((elem, index) => [elem, index] as const) - .filter(([elem]) => filter(elem) && elem.rest === null) - .map(([elem, index]) => - decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createElementAccessExpression(props.input, index), - metadata: elem, - explore: { - ...props.explore, - from: "array", - postfix: props.explore.postfix.length - ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` - : `"[${index}]"`, - }, - }), - ); - const rest = (() => { - if (props.tuple.elements.length === 0) return null; - - const last: MetadataSchema = props.tuple.elements.at(-1)!; - const rest: MetadataSchema | null = last.rest; - if (rest === null || filter(rest) === false) return null; - - return decode({ - context: props.context, - config: props.config, - functor: props.functor, - input: ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "slice"), - undefined, - [ExpressionFactory.number(props.tuple.elements.length - 1)], - ), - metadata: wrap_metadata_rest_tuple(props.tuple.elements.at(-1)!.rest!), - explore: { - ...props.explore, - start: props.tuple.elements.length - 1, - }, - }); - })(); - return PruneJoiner.tuple({ - elements, - rest, - }); - }; - - /* ----------------------------------------------------------- - UNION TYPE EXPLORERS - ----------------------------------------------------------- */ - const explore_objects = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - }) => { - if (props.metadata.objects.length === 1) - return decode_object({ - ...props, - object: props.metadata.objects[0]!.type, - }); - - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(`${PREFIX}u${props.metadata.union_index!}`), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ); - }; - - const explore_arrays = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - arrays: MetadataArray[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - explore_array_like_union_types({ - ...props, - elements: props.arrays, - factory: (next) => - UnionExplorer.array({ - config: { - checker: (v) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - metadata: v.definition, - input: v.input, - explore: v.explore, - }), - decoder: (v) => - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - empty: ts.factory.createStringLiteral("[]"), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: next.parameters, - input: next.input, - arrays: next.definitions, - explore: next.explore, - }), - }); - //(next.parameters)(next.input, next.elements, next.explore), - - const explore_array_like_union_types = < - T extends MetadataArray | MetadataTuple, - >(props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - factory: (next: { - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - definitions: T[]; - explore: FeatureProgrammer.IExplore; - }) => ts.ArrowFunction; - input: ts.Expression; - elements: T[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - const arrow = (next: { - parameters: ts.ParameterDeclaration[]; - explore: FeatureProgrammer.IExplore; - input: ts.Expression; - }): ts.ArrowFunction => - props.factory({ - definitions: props.elements, - parameters: next.parameters, - input: next.input, - explore: next.explore, - }); - if (props.elements.every((e) => e.type.recursive === false)) - ts.factory.createCallExpression( - arrow({ - parameters: [], - explore: props.explore, - input: props.input, - }), - undefined, - [], - ); - - const arrayExplore: FeatureProgrammer.IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.emplaceUnion( - props.config.prefix, - props.elements.map((e) => e.type.name).join(" | "), - () => - arrow({ - parameters: FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - explore: { - ...arrayExplore, - postfix: "", - }, - input: ts.factory.createIdentifier("input"), - }), - ), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ); - }; - - // @todo -> must filter out recursive visit - const filter = (metadata: MetadataSchema): boolean => - metadata.any === false && - (metadata.objects.length !== 0 || - metadata.tuples.some( - (t) => - !!t.type.elements.length && - t.type.elements.some((e) => filter(e.rest ?? e)), - ) || - metadata.arrays.some((e) => filter(e.type.value))); - - /* ----------------------------------------------------------- - CONFIGURATIONS - ----------------------------------------------------------- */ - const PREFIX = "_p"; - - const configure = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - }): FeatureProgrammer.IConfig => { - const config: FeatureProgrammer.IConfig = { - types: { - input: (type, name) => - ts.factory.createTypeReferenceNode( - name ?? - TypeFactory.getFullName({ checker: props.context.checker, type }), - ), - output: () => TypeFactory.keyword("void"), - }, - prefix: PREFIX, - trace: false, - path: false, - initializer, - decoder: (next) => - decode({ - context: props.context, - functor: props.functor, - config, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - objector: { - checker: (next) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - decoder: (next) => - decode_object({ - functor: props.functor, - input: next.input, - object: next.object, - explore: next.explore, - }), - joiner: PruneJoiner.object, - unionizer: (next) => - decode_union_object({ - checker: (v) => - IsProgrammer.decode_object({ - context: props.context, - functor: props.functor, - input: v.input, - object: v.object, - explore: v.explore, - }), - decoder: (v) => - decode_object({ - functor: props.functor, - input: v.input, - object: v.object, - explore: v.explore, - }), - success: (exp) => exp, - escaper: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - input: next.input, - objects: next.objects, - explore: next.explore, - }), - failure: (next) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: next.expected, - input: next.input, - }), - }, - generator: { - arrays: (collection) => - write_array_functions({ - config, - functor: props.functor, - collection, - }), - tuples: (collection) => - write_tuple_functions({ - config, - context: props.context, - functor: props.functor, - collection, - }), - }, - }; - return config; - }; - - const initializer: FeatureProgrammer.IConfig["initializer"] = (props) => { - const collection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - return { - collection, - metadata: result.data, - }; - }; - - const create_throw_error = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - expected: string; - input: ts.Expression; - }) => - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - props.context.importer.internal("throwTypeGuardError"), - [], - [ - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "method", - ts.factory.createStringLiteral(props.functor.method), - ), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.input), - ], - true, - ), - ], - ), - ); -} diff --git a/packages/core/src/programmers/misc/MiscValidateCloneProgrammer.ts b/packages/core/src/programmers/misc/MiscValidateCloneProgrammer.ts deleted file mode 100644 index e625e28231c..00000000000 --- a/packages/core/src/programmers/misc/MiscValidateCloneProgrammer.ts +++ /dev/null @@ -1,109 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { MiscCloneProgrammer } from "./MiscCloneProgrammer"; - -export namespace MiscValidateCloneProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - config: { - equals: false, - }, - }); - const clone = MiscCloneProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...validate.functions, - ...clone.functions, - }, - statements: [ - ...validate.statements, - ...clone.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__clone", - value: clone.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [clone.arrow.type ?? TypeFactory.keyword("any")], - }), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "result", - value: ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ts.factory.createIdentifier("input")], - ), - TypeFactory.keyword("any"), - ), - }), - ts.factory.createIfStatement( - ts.factory.createIdentifier("result.success"), - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("result.data"), - ts.SyntaxKind.EqualsToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__clone"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("result"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/misc/MiscValidatePruneProgrammer.ts b/packages/core/src/programmers/misc/MiscValidatePruneProgrammer.ts deleted file mode 100644 index 1d6a110bc3b..00000000000 --- a/packages/core/src/programmers/misc/MiscValidatePruneProgrammer.ts +++ /dev/null @@ -1,111 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { MiscPruneProgrammer } from "./MiscPruneProgrammer"; - -export namespace MiscValidatePruneProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate: FeatureProgrammer.IDecomposed = - ValidateProgrammer.decompose({ - ...props, - config: { - equals: false, - }, - }); - const prune: FeatureProgrammer.IDecomposed = MiscPruneProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...validate.functions, - ...prune.functions, - }, - statements: [ - ...validate.statements, - ...prune.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__prune", - value: prune.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "result", - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createIfStatement( - ts.factory.createIdentifier("result.success"), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__prune"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("result"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/misc/index.ts b/packages/core/src/programmers/misc/index.ts deleted file mode 100644 index acc8fbcc3cf..00000000000 --- a/packages/core/src/programmers/misc/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./MiscAssertCloneProgrammer"; -export * from "./MiscAssertPruneProgrammer"; -export * from "./MiscCloneProgrammer"; -export * from "./MiscIsCloneProgrammer"; -export * from "./MiscIsPruneProgrammer"; -export * from "./MiscLiteralsProgrammer"; -export * from "./MiscPruneProgrammer"; -export * from "./MiscValidateCloneProgrammer"; -export * from "./MiscValidatePruneProgrammer"; diff --git a/packages/core/src/programmers/notations/NotationAssertGeneralProgrammer.ts b/packages/core/src/programmers/notations/NotationAssertGeneralProgrammer.ts deleted file mode 100644 index ff1f89a5f24..00000000000 --- a/packages/core/src/programmers/notations/NotationAssertGeneralProgrammer.ts +++ /dev/null @@ -1,99 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { NotationGeneralProgrammer } from "./NotationGeneralProgrammer"; - -export namespace NotationAssertGeneralProgrammer { - export interface IProps extends IProgrammerProps { - rename: (str: string) => string; - } - - export const decompose = (props: { - rename: (str: string) => string; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - config: { - equals: false, - guard: false, - }, - }); - const notation: FeatureProgrammer.IDecomposed = - NotationGeneralProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...assert.functions, - ...notation.functions, - }, - statements: [ - ...assert.statements, - ...notation.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__notation", - value: notation.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - notation.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__notation"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createIdentifier("input"), - AssertProgrammer.Guardian.identifier(), - ], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/notations/NotationGeneralProgrammer.ts b/packages/core/src/programmers/notations/NotationGeneralProgrammer.ts deleted file mode 100644 index c3872cd73d8..00000000000 --- a/packages/core/src/programmers/notations/NotationGeneralProgrammer.ts +++ /dev/null @@ -1,980 +0,0 @@ -import { NamingConvention } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { TransformerError } from "../../context/TransformerError"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataArray } from "../../schemas/metadata/MetadataArray"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataMap } from "../../schemas/metadata/MetadataMap"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { MetadataSet } from "../../schemas/metadata/MetadataSet"; -import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; -import { MetadataTupleType } from "../../schemas/metadata/MetadataTupleType"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { NotationJoiner } from "../helpers/NotationJoiner"; -import { UnionExplorer } from "../helpers/UnionExplorer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { decode_union_object } from "../iterate/decode_union_object"; -import { postfix_of_tuple } from "../iterate/postfix_of_tuple"; -import { wrap_metadata_rest_tuple } from "../iterate/wrap_metadata_rest_tuple"; - -export namespace NotationGeneralProgrammer { - export interface IProps extends IProgrammerProps { - rename: (str: string) => string; - } - - export const returnType = (props: { - rename: (str: string) => string; - context: ITypiaContext; - type: string; - }) => - props.context.importer.type({ - file: "typia", - name: `${NamingConvention.capitalize(props.rename.name)}Case`, - arguments: [ts.factory.createTypeReferenceNode(props.type)], - }); - - export const decompose = (props: { - rename: (str: string) => string; - validated: boolean; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const config = configure(props); - if (props.validated === false) - config.addition = (collection) => - IsProgrammer.write_function_statements({ - context: props.context, - functor: props.functor, - collection, - }); - const composed: FeatureProgrammer.IComposed = FeatureProgrammer.compose({ - ...props, - config, - }); - return { - functions: composed.functions, - statements: composed.statements, - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - composed.parameters, - returnType({ - rename: props.rename, - context: props.context, - type: - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - }), - undefined, - composed.body, - ), - }; - }; - - export const write = (props: IProps) => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - validated: true, - }); - return FeatureProgrammer.writeDecomposed({ - functor, - modulo: props.modulo, - result, - }); - }; - - const write_array_functions = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .arrays() - .filter((a) => a.recursive) - .map((type, i) => - StatementFactory.constant({ - name: `${props.config.prefix}a${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_array_inline({ - ...props, - input: ts.factory.createIdentifier("input"), - array: MetadataArray.create({ - type, - tags: [], - }), - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - const write_tuple_functions = (props: { - config: FeatureProgrammer.IConfig; - rename: (str: string) => string; - context: ITypiaContext; - functor: FunctionProgrammer; - collection: MetadataCollection; - }): ts.VariableStatement[] => - props.collection - .tuples() - .filter((t) => t.recursive) - .map((tuple, i) => - StatementFactory.constant({ - name: `${props.config.prefix}t${i}`, - value: ts.factory.createArrowFunction( - undefined, - undefined, - FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - TypeFactory.keyword("any"), - undefined, - decode_tuple_inline({ - ...props, - input: ts.factory.createIdentifier("input"), - tuple, - explore: { - tracable: props.config.trace, - source: "function", - from: "array", - postfix: "", - }, - }), - ), - }), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decode = (props: { - config: FeatureProgrammer.IConfig; - rename: (str: string) => string; - context: ITypiaContext; - functor: FunctionProgrammer; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - input: ts.Expression; - }): ts.Expression => { - // ANY TYPE - if ( - props.metadata.any || - props.metadata.arrays.some((a) => a.type.value.any) || - props.metadata.tuples.some( - (t) => !!t.type.elements.length && t.type.elements.every((e) => e.any), - ) - ) - return ExpressionFactory.currying({ - function: props.context.importer.internal("notationAny"), - arguments: [ - props.context.importer.internal( - `notation${NamingConvention.capitalize(props.rename.name)}`, - ), - props.input, - ], - }); - - interface IUnion { - type: string; - is: () => ts.Expression; - value: () => ts.Expression; - } - const unions: IUnion[] = []; - - //---- - // LIST UP UNION TYPES - //---- - // FUNCTIONAL - if (props.metadata.functions.length) - unions.push({ - type: "functional", - is: () => - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("function"), - ts.factory.createTypeOfExpression(props.input), - ), - value: () => ts.factory.createIdentifier("undefined"), - }); - - // TUPLES - for (const tuple of props.metadata.tuples) - unions.push({ - type: "tuple", - is: () => - IsProgrammer.decode({ - ...props, - metadata: (() => { - const partial = MetadataSchema.initialize(); - partial.tuples.push(tuple); - return partial; - })(), - }), - value: () => - decode_tuple({ - ...props, - tuple, - }), - }); - - // ARRAYS - if (props.metadata.arrays.length) - unions.push({ - type: "array", - is: () => ExpressionFactory.isArray(props.input), - value: () => - explore_arrays({ - ...props, - arrays: props.metadata.arrays, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - - // NATIVE TYPES - if (props.metadata.sets.length) - unions.push({ - type: "set", - is: () => ExpressionFactory.isInstanceOf("Set", props.input), - value: () => - explore_sets({ - ...props, - sets: props.metadata.sets, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - if (props.metadata.maps.length) - unions.push({ - type: "map", - is: () => ExpressionFactory.isInstanceOf("Map", props.input), - value: () => - explore_maps({ - ...props, - maps: props.metadata.maps, - explore: { - ...props.explore, - from: "array", - }, - }), - }); - for (const native of props.metadata.natives) { - if (native.name === "WeakSet" || native.name === "WeakMap") continue; - unions.push({ - type: "native", - is: () => ExpressionFactory.isInstanceOf(native.name, props.input), - value: () => - native.name === "Boolean" || - native.name === "Number" || - native.name === "String" - ? ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "valueOf"), - undefined, - undefined, - ) - : decode_native({ - name: native.name, - input: props.input, - }), - }); - } - - // OBJECTS - if (props.metadata.objects.length) - unions.push({ - type: "object", - is: () => - ExpressionFactory.isObject({ - checkNull: true, - checkArray: false, - input: props.input, - }), - value: () => - explore_objects({ - ...props, - explore: { - ...props.explore, - from: "object", - }, - }), - }); - - // COMPOSITION - if (unions.length === 0) return props.input; - else if (unions.length === 1 && props.metadata.size() === 1) { - const value: ts.Expression = - (props.metadata.nullable || props.metadata.isRequired() === false) && - is_instance(props.metadata) - ? ts.factory.createConditionalExpression( - props.input, - undefined, - unions[0]!.value(), - undefined, - props.input, - ) - : unions[0]!.value(); - return ts.factory.createAsExpression(value, TypeFactory.keyword("any")); - } else { - let last: ts.Expression = props.input; - for (const u of unions.reverse()) - last = ts.factory.createConditionalExpression( - u.is(), - undefined, - u.value(), - undefined, - last, - ); - return ts.factory.createAsExpression(last, TypeFactory.keyword("any")); - } - }; - - const decode_object = (props: { - functor: FunctionProgrammer; - object: MetadataObjectType; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; - }) => - FeatureProgrammer.decode_object({ - config: { - trace: false, - path: false, - prefix: PREFIX, - }, - functor: props.functor, - object: props.object, - input: props.input, - explore: props.explore, - }); - - const decode_array = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - props.array.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}a${props.array.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - from: "array", - }, - input: props.input, - }), - ) - : decode_array_inline(props); - - const decode_array_inline = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - array: MetadataArray; - explore: FeatureProgrammer.IExplore; - }) => - FeatureProgrammer.decode_array({ - config: props.config, - functor: props.functor, - combiner: NotationJoiner.array, - array: props.array, - input: props.input, - explore: props.explore, - }); - - const decode_tuple = (props: { - config: FeatureProgrammer.IConfig; - rename: (str: string) => string; - context: ITypiaContext; - functor: FunctionProgrammer; - tuple: MetadataTuple; - explore: FeatureProgrammer.IExplore; - input: ts.Expression; - }): ts.Expression => - props.tuple.type.recursive - ? ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${props.config.prefix}t${props.tuple.type.index}`, - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: { - ...props.explore, - source: "function", - }, - input: props.input, - }), - ) - : decode_tuple_inline({ - ...props, - tuple: props.tuple.type, - }); - - const decode_tuple_inline = (props: { - context: ITypiaContext; - rename: (str: string) => string; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - explore: FeatureProgrammer.IExplore; - tuple: MetadataTupleType; - input: ts.Expression; - }): ts.Expression => { - const elements: ts.Expression[] = props.tuple.elements - .filter((m) => m.rest === null) - .map((elem, index) => - decode({ - ...props, - input: ts.factory.createElementAccessExpression(props.input, index), - metadata: elem, - explore: { - ...props.explore, - from: "array", - postfix: props.explore.postfix.length - ? `${postfix_of_tuple(props.explore.postfix)}[${index}]"` - : `"[${index}]"`, - }, - }), - ); - const rest = (() => { - if (props.tuple.elements.length === 0) return null; - - const last: MetadataSchema = props.tuple.elements.at(-1)!; - const rest: MetadataSchema | null = last.rest; - if (rest === null) return null; - - return decode({ - ...props, - input: ts.factory.createCallExpression( - IdentifierFactory.access(props.input, "slice"), - undefined, - [ExpressionFactory.number(props.tuple.elements.length - 1)], - ), - metadata: wrap_metadata_rest_tuple(props.tuple.elements.at(-1)!.rest!), - explore: { - ...props.explore, - start: props.tuple.elements.length - 1, - }, - }); - })(); - return NotationJoiner.tuple({ - elements, - rest, - }); - }; - - /* ----------------------------------------------------------- - NATIVE CLASSES - ----------------------------------------------------------- */ - const decode_native = (props: { name: string; input: ts.Expression }) => - props.name === "Date" - ? ts.factory.createNewExpression( - ts.factory.createIdentifier(props.name), - undefined, - [props.input], - ) - : props.input; - - /* ----------------------------------------------------------- - EXPLORERS FOR UNION TYPES - ----------------------------------------------------------- */ - const explore_sets = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; - sets: Array; - }): ts.Expression => - ts.factory.createCallExpression( - UnionExplorer.set({ - config: { - checker: (v) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: v.input, - metadata: v.definition, - explore: v.explore, - }), - decoder: (v) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Set"), - [TypeFactory.keyword("any")], - [ - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - ], - ), - empty: ts.factory.createNewExpression( - ts.factory.createIdentifier("Set"), - [TypeFactory.keyword("any")], - [], - ), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: [], - input: props.input, - sets: props.sets, - explore: props.explore, - }), - undefined, - undefined, - ); - - const explore_maps = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - maps: Array; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - ts.factory.createCallExpression( - UnionExplorer.map({ - config: { - checker: (v) => - ts.factory.createLogicalAnd( - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: ts.factory.createElementAccessExpression(v.input, 0), - metadata: v.definition[0], - explore: { - ...props.explore, - postfix: `${v.explore.postfix}[0]`, - }, - }), - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: ts.factory.createElementAccessExpression(v.input, 1), - metadata: v.definition[1], - explore: { - ...props.explore, - postfix: `${props.explore.postfix}[1]`, - }, - }), - ), - decoder: (v) => - ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - [TypeFactory.keyword("any"), TypeFactory.keyword("any")], - [ - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - ], - ), - empty: ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - [TypeFactory.keyword("any"), TypeFactory.keyword("any")], - [], - ), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: [], - input: props.input, - maps: props.maps, - explore: props.explore, - }), - undefined, - undefined, - ); - - const explore_objects = (props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - metadata: MetadataSchema; - explore: FeatureProgrammer.IExplore; - }) => { - if (props.metadata.objects.length === 1) - return decode_object({ - functor: props.functor, - object: props.metadata.objects[0]!.type, - input: props.input, - explore: props.explore, - }); - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(`${PREFIX}u${props.metadata.union_index!}`), - ), - undefined, - FeatureProgrammer.argumentsArray(props), - ); - }; - - const explore_arrays = (props: { - context: ITypiaContext; - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - input: ts.Expression; - arrays: MetadataArray[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => - explore_array_like_union_types({ - ...props, - factory: (next) => - UnionExplorer.array({ - config: { - checker: (v) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: v.input, - metadata: v.definition, - explore: v.explore, - }), - decoder: (v) => - decode_array({ - config: props.config, - functor: props.functor, - input: v.input, - array: v.definition, - explore: v.explore, - }), - empty: ts.factory.createIdentifier("[]"), - success: ts.factory.createTrue(), - failure: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - }, - parameters: next.parameters, - input: next.input, - arrays: next.definitions, - explore: next.explore, - }), - definitions: props.arrays, - input: props.input, - explore: props.explore, - }); - - const explore_array_like_union_types = < - T extends MetadataArray | MetadataTuple, - >(props: { - config: FeatureProgrammer.IConfig; - functor: FunctionProgrammer; - factory: (next: { - parameters: ts.ParameterDeclaration[]; - input: ts.Expression; - definitions: T[]; - explore: FeatureProgrammer.IExplore; - }) => ts.ArrowFunction; - input: ts.Expression; - definitions: T[]; - explore: FeatureProgrammer.IExplore; - }): ts.Expression => { - const arrow = (next: { - parameters: ts.ParameterDeclaration[]; - explore: FeatureProgrammer.IExplore; - input: ts.Expression; - }): ts.ArrowFunction => - props.factory({ - parameters: next.parameters, - definitions: props.definitions, - explore: next.explore, - input: next.input, - }); - if (props.definitions.every((e) => e.type.recursive === false)) - ts.factory.createCallExpression( - arrow({ - parameters: [], - explore: props.explore, - input: props.input, - }), - undefined, - [], - ); - - const arrayExplore: FeatureProgrammer.IExplore = { - ...props.explore, - source: "function", - from: "array", - }; - return ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.emplaceUnion( - props.config.prefix, - props.definitions.map((e) => e.type.name).join(" | "), - () => - arrow({ - parameters: FeatureProgrammer.parameterDeclarations({ - config: props.config, - type: TypeFactory.keyword("any"), - input: ts.factory.createIdentifier("input"), - }), - explore: { - ...arrayExplore, - postfix: "", - }, - input: ts.factory.createIdentifier("input"), - }), - ), - ), - undefined, - FeatureProgrammer.argumentsArray({ - config: props.config, - explore: arrayExplore, - input: props.input, - }), - ); - }; - - /* ----------------------------------------------------------- - CONFIGURATIONS - ----------------------------------------------------------- */ - const PREFIX = "_c"; - - const configure = (props: { - rename: (str: string) => string; - context: ITypiaContext; - functor: FunctionProgrammer; - }): FeatureProgrammer.IConfig => { - const config: FeatureProgrammer.IConfig = { - types: { - input: (type, name) => - ts.factory.createTypeReferenceNode( - name ?? - TypeFactory.getFullName({ checker: props.context.checker, type }), - ), - output: (type, name) => - returnType({ - rename: props.rename, - context: props.context, - type: - name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type, - }), - }), - }, - prefix: PREFIX, - trace: false, - path: false, - initializer, - decoder: (next) => - decode({ - config, - rename: props.rename, - context: props.context, - functor: props.functor, - metadata: next.metadata, - explore: next.explore, - input: next.input, - }), - objector: { - checker: (next) => - IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: next.input, - metadata: next.metadata, - explore: next.explore, - }), - decoder: (next) => - decode_object({ - functor: props.functor, - object: next.object, - input: next.input, - explore: next.explore, - }), - joiner: (next) => - NotationJoiner.object({ - rename: props.rename, - input: next.input!, - entries: next.entries, - }), - unionizer: (next) => - decode_union_object({ - checker: (v) => - IsProgrammer.decode_object({ - context: props.context, - functor: props.functor, - object: v.object, - input: v.input, - explore: v.explore, - }), - decoder: (v) => - decode_object({ - functor: props.functor, - object: v.object, - input: v.input, - explore: v.explore, - }), - success: (exp) => exp, - escaper: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - input: next.input, - objects: next.objects, - explore: next.explore, - }), - failure: (next) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: next.expected, - input: next.input, - }), - }, - generator: { - arrays: (collection) => - write_array_functions({ - functor: props.functor, - config, - collection, - }), - tuples: (collection) => - write_tuple_functions({ - rename: props.rename, - context: props.context, - functor: props.functor, - config, - collection, - }), - }, - }; - return config; - }; - - const initializer: FeatureProgrammer.IConfig["initializer"] = (props) => { - const collection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: false, - constant: true, - absorb: true, - }, - components: collection, - type: props.type, - }); - if (result.success === false) - throw TransformerError.from({ - code: props.functor.method, - errors: result.errors, - }); - return { - collection, - metadata: result.data, - }; - }; - - const create_throw_error = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - expected: string; - input: ts.Expression; - }) => - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - props.context.importer.internal("throwTypeGuardError"), - undefined, - [ - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "method", - ts.factory.createStringLiteral(props.functor.method), - ), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.input), - ], - true, - ), - ], - ), - ); - - const is_instance = (metadata: MetadataSchema): boolean => - !!metadata.objects.length || - !!metadata.arrays.length || - !!metadata.tuples.length || - !!metadata.sets.length || - !!metadata.maps.length || - !!metadata.natives.length || - (metadata.rest !== null && is_instance(metadata.rest)); -} diff --git a/packages/core/src/programmers/notations/NotationIsGeneralProgrammer.ts b/packages/core/src/programmers/notations/NotationIsGeneralProgrammer.ts deleted file mode 100644 index 5cb5d5cd030..00000000000 --- a/packages/core/src/programmers/notations/NotationIsGeneralProgrammer.ts +++ /dev/null @@ -1,103 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { NotationGeneralProgrammer } from "./NotationGeneralProgrammer"; - -export namespace NotationIsGeneralProgrammer { - export interface IProps extends IProgrammerProps { - rename: (str: string) => string; - } - - export const decompose = (props: { - rename: (str: string) => string; - context: ITypiaContext; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - config: { - equals: false, - }, - }); - const notation: FeatureProgrammer.IDecomposed = - NotationGeneralProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...is.functions, - ...notation.functions, - }, - statements: [ - ...is.statements, - ...notation.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__notation", - value: notation.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - ts.factory.createUnionTypeNode([ - notation.arrow.type ?? TypeFactory.keyword("any"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__notation"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/notations/NotationValidateGeneralProgrammer.ts b/packages/core/src/programmers/notations/NotationValidateGeneralProgrammer.ts deleted file mode 100644 index b703fd553c4..00000000000 --- a/packages/core/src/programmers/notations/NotationValidateGeneralProgrammer.ts +++ /dev/null @@ -1,117 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { NotationGeneralProgrammer } from "./NotationGeneralProgrammer"; - -export namespace NotationValidateGeneralProgrammer { - export interface IProps extends IProgrammerProps { - rename: (str: string) => string; - } - - export const decompose = (props: { - rename: (str: string) => string; - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - config: { - equals: false, - }, - }); - const notation = NotationGeneralProgrammer.decompose({ - ...props, - validated: true, - }); - return { - functions: { - ...validate.functions, - ...notation.functions, - }, - statements: [ - ...validate.statements, - ...notation.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__notation", - value: notation.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [notation.arrow.type ?? TypeFactory.keyword("any")], - }), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "result", - value: ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ts.factory.createIdentifier("input")], - ), - TypeFactory.keyword("any"), - ), - }), - ts.factory.createIfStatement( - ts.factory.createIdentifier("result.success"), - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("result.data"), - ts.SyntaxKind.EqualsToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__notation"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ), - ), - ts.factory.createReturnStatement( - ts.factory.createAsExpression( - ts.factory.createIdentifier("result"), - TypeFactory.keyword("any"), - ), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/notations/index.ts b/packages/core/src/programmers/notations/index.ts deleted file mode 100644 index 8ac8497e3ba..00000000000 --- a/packages/core/src/programmers/notations/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./NotationAssertGeneralProgrammer"; -export * from "./NotationGeneralProgrammer"; -export * from "./NotationIsGeneralProgrammer"; -export * from "./NotationValidateGeneralProgrammer"; diff --git a/packages/core/src/programmers/protobuf/ProtobufAssertDecodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufAssertDecodeProgrammer.ts deleted file mode 100644 index 730cb7982ff..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufAssertDecodeProgrammer.ts +++ /dev/null @@ -1,96 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { ProtobufDecodeProgrammer } from "./ProtobufDecodeProgrammer"; - -export namespace ProtobufAssertDecodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - ProtobufDecodeProgrammer.decompose(props); - return { - functions: { - ...assert.functions, - ...decode.functions, - }, - statements: [ - ...assert.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - ...decode.arrow.parameters, - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - decode.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - AssertProgrammer.Guardian.identifier(), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufAssertEncodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufAssertEncodeProgrammer.ts deleted file mode 100644 index a8af5d251a1..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufAssertEncodeProgrammer.ts +++ /dev/null @@ -1,100 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { AssertProgrammer } from "../AssertProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { ProtobufEncodeProgrammer } from "./ProtobufEncodeProgrammer"; - -export namespace ProtobufAssertEncodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - init?: ts.Expression | undefined; - }): FeatureProgrammer.IDecomposed => { - const assert: FeatureProgrammer.IDecomposed = AssertProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - finite: true, - }, - }, - config: { - equals: false, - guard: false, - }, - }); - const encode: FeatureProgrammer.IDecomposed = - ProtobufEncodeProgrammer.decompose(props); - return { - functions: { - ...assert.functions, - ...encode.functions, - }, - statements: [ - ...assert.statements, - ...encode.statements, - StatementFactory.constant({ - name: "__assert", - value: assert.arrow, - }), - StatementFactory.constant({ - name: "__encode", - value: encode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("input", TypeFactory.keyword("any")), - AssertProgrammer.Guardian.parameter({ - context: props.context, - init: props.init, - }), - ], - encode.arrow.type, - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__encode"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__assert"), - undefined, - [ - ts.factory.createIdentifier("input"), - AssertProgrammer.Guardian.identifier(), - ], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufDecodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufDecodeProgrammer.ts deleted file mode 100644 index a1ac5c44cfe..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufDecodeProgrammer.ts +++ /dev/null @@ -1,651 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { MetadataFactory } from "../../factories/MetadataFactory"; -import { ProtobufFactory } from "../../factories/ProtobufFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { IProtobufProperty } from "../../schemas/protobuf/IProtobufProperty"; -import { IProtobufPropertyType } from "../../schemas/protobuf/IProtobufPropertyType"; -import { IProtobufSchema } from "../../schemas/protobuf/IProtobufSchema"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { ProtobufUtil } from "../helpers/ProtobufUtil"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; - -export namespace ProtobufDecodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const collection: MetadataCollection = new MetadataCollection(); - const meta: MetadataSchema = ProtobufFactory.metadata({ - method: props.modulo.getText(), - checker: props.context.checker, - transformer: props.context.transformer, - components: collection, - type: props.type, - }); - return { - functions: Object.fromEntries( - collection - .objects() - .filter((object) => ProtobufUtil.isStaticObject(object)) - .map((object) => [ - `${PREFIX}o${object.index}`, - StatementFactory.constant({ - name: props.functor.useLocal(`${PREFIX}o${object.index}`), - value: write_object_function({ - context: props.context, - functor: props.functor, - object, - }), - }), - ]), - ), - statements: [], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode("Uint8Array"), - ), - ], - props.context.importer.type({ - file: "typia", - name: "Resolved", - arguments: [ - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ], - }), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "reader", - value: ts.factory.createNewExpression( - props.context.importer.internal("ProtobufReader"), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createReturnStatement( - decode_regular_object({ - top: true, - object: meta.objects[0]!.type, - }), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const write_object_function = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - object: MetadataObjectType; - }): ts.ArrowFunction => - ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter("reader"), - IdentifierFactory.parameter( - "length", - TypeFactory.keyword("number"), - ExpressionFactory.number(-1), - ), - ], - TypeFactory.keyword("any"), - undefined, - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("length"), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createConditionalExpression( - ts.factory.createLessThan( - ts.factory.createIdentifier("length"), - ExpressionFactory.number(0), - ), - undefined, - callReader("size"), - undefined, - ts.factory.createAdd( - callReader("index"), - ts.factory.createIdentifier("length"), - ), - ), - ), - ), - ...write_object_function_body({ - context: props.context, - condition: ts.factory.createLessThan( - callReader("index"), - ts.factory.createIdentifier("length"), - ), - tag: "tag", - output: "output", - object: props.object, - }), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("output"), - ), - ], - true, - ), - ); - - const write_object_function_body = (props: { - context: ITypiaContext; - condition: ts.Expression; - tag: string; - output: string; - object: MetadataObjectType; - }): ts.Statement[] => { - if (props.object.properties.some((p) => p.of_protobuf_ === undefined)) - ProtobufFactory.emplaceObject(props.object); - const clauses: ts.CaseClause[] = props.object.properties - .map((p) => - decode_property({ - context: props.context, - accessor: IdentifierFactory.access( - ts.factory.createIdentifier(props.output), - p.key.getSoleLiteral()!, - ), - protobuf: p.of_protobuf_!, - metadata: p.value, - }), - ) - .flat(); - return [ - StatementFactory.constant({ - name: props.output, - value: ts.factory.createAsExpression( - ts.factory.createObjectLiteralExpression( - props.object.properties - .filter( - (p) => - !( - props.context.compilerOptions.exactOptionalPropertyTypes === - true && p.value.optional === true - ), - ) - .map((p) => - ts.factory.createPropertyAssignment( - IdentifierFactory.identifier(p.key.getSoleLiteral()!), - write_property_default_value(p.value), - ), - ), - true, - ), - TypeFactory.keyword("any"), - ), - }), - ts.factory.createWhileStatement( - props.condition, - ts.factory.createBlock([ - StatementFactory.constant({ - name: props.tag, - value: callReader("uint32"), - }), - ts.factory.createSwitchStatement( - ts.factory.createUnsignedRightShift( - ts.factory.createIdentifier(props.tag), - ExpressionFactory.number(3), - ), - ts.factory.createCaseBlock([ - ...clauses, - ts.factory.createDefaultClause([ - ts.factory.createExpressionStatement( - callReader("skipType", [ - ts.factory.createBitwiseAnd( - ts.factory.createIdentifier(props.tag), - ExpressionFactory.number(7), - ), - ]), - ), - ts.factory.createBreakStatement(), - ]), - ]), - ), - ]), - ), - ]; - }; - - const write_property_default_value = (value: MetadataSchema) => - ts.factory.createAsExpression( - value.nullable - ? ts.factory.createNull() - : value.isRequired() === false - ? ts.factory.createIdentifier("undefined") - : value.arrays.length - ? ts.factory.createArrayLiteralExpression() - : value.maps.length - ? ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - undefined, - [], - ) - : value.natives.length - ? ts.factory.createNewExpression( - ts.factory.createIdentifier("Uint8Array"), - undefined, - [ts.factory.createArrayLiteralExpression([])], - ) - : value.atomics.some((a) => a.type === "string") || - value.constants.some( - (c) => - c.type === "string" && - c.values.some((v) => v.value === ""), - ) || - value.templates.some( - (tpl) => - tpl.row.length === 1 && - tpl.row[0]!.getName() === "string", - ) - ? ts.factory.createStringLiteral("") - : value.objects.length && - value.objects.some( - (obj) => !ProtobufUtil.isStaticObject(obj.type), - ) - ? ts.factory.createObjectLiteralExpression() - : ts.factory.createIdentifier("undefined"), - TypeFactory.keyword("any"), - ); - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decode_property = (props: { - context: ITypiaContext; - metadata: MetadataSchema; - protobuf: IProtobufProperty; - accessor: ts.Expression; - }): ts.CaseClause[] => - props.protobuf.union.map((schema) => - decode_property_type({ - context: props.context, - accessor: props.accessor, - schema, - required: - props.metadata.isRequired() && props.metadata.nullable === false, - }), - ); - - const decode_property_type = (props: { - context: ITypiaContext; - accessor: ts.Expression; - schema: IProtobufPropertyType; - required: boolean; - }): ts.CaseClause => { - const out = ( - title: string, - value: ts.Expression | ts.Statement[], - ): ts.CaseClause => - ts.factory.createCaseClause( - ExpressionFactory.number(props.schema.index), - [ - ts.factory.createExpressionStatement( - ts.factory.createIdentifier(`// ${title}`), - ), - ...(Array.isArray(value) - ? value - : [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - props.accessor, - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - value, - ), - ), - ]), - ts.factory.createBreakStatement(), - ], - ); - // ATOMICS - if (props.schema.type === "bytes") return out("bytes", callReader("bytes")); - else if (props.schema.type === "bool") - return out("bool", callReader("bool")); - else if (props.schema.type === "bigint") - return out(props.schema.name, callReader(props.schema.name)); - else if (props.schema.type === "number") - return out(props.schema.name, decode_number(props.schema)); - else if (props.schema.type === "string") - return out("string", callReader("string")); - // INSTANCES - else if (props.schema.type === "array") - return out( - `Array<${props.schema.array.value.getName()}>`, - decode_array({ - accessor: props.accessor, - schema: props.schema, - required: props.required, - }), - ); - else if (props.schema.type === "object") - return out( - props.schema.object.name, - decode_regular_object({ - top: false, - object: props.schema.object, - }), - ); - else if (props.schema.type === "map") - if (props.schema.map instanceof MetadataObjectType) { - const key: MetadataSchema = props.schema.map.properties[0]!.key; - const value: MetadataSchema = props.schema.map.properties[0]!.value; - return out( - `Record<${key.getName()}, ${value.getName()}>`, - decode_map({ - context: props.context, - accessor: props.accessor, - schema: props.schema, - required: props.required, - key, - value, - initializer: ts.factory.createObjectLiteralExpression([]), - setter: () => - ts.factory.createBinaryExpression( - ts.factory.createElementAccessExpression( - props.accessor, - ts.factory.createIdentifier("entry.key"), - ), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createIdentifier("entry.value"), - ), - }), - ); - } else { - const key: MetadataSchema = props.schema.map.key; - const value: MetadataSchema = props.schema.map.value; - return out( - `Map<${key.getName()}, ${value.getName()}>`, - decode_map({ - context: props.context, - accessor: props.accessor, - schema: props.schema, - required: props.required, - key, - value, - initializer: ts.factory.createNewExpression( - ts.factory.createIdentifier("Map"), - [TypeFactory.keyword("any"), TypeFactory.keyword("any")], - [], - ), - setter: () => - ts.factory.createCallExpression( - IdentifierFactory.access(props.accessor, "set"), - undefined, - [ - ts.factory.createIdentifier("entry.key"), - ts.factory.createIdentifier("entry.value"), - ], - ), - }), - ); - } - throw new Error( - "Error on ProtobufDecodeProgrammer.write(): unknown property type", - ); - }; - - const decode_number = (schema: IProtobufSchema.INumber): ts.Expression => { - const value = callReader(schema.name); - return schema.name === "int64" || schema.name === "uint64" - ? ts.factory.createCallExpression( - ts.factory.createIdentifier("Number"), - undefined, - [value], - ) - : value; - }; - - const decode_array = (props: { - accessor: ts.Expression; - schema: IProtobufSchema.IArray; - required: boolean; - }): ts.Statement[] => { - const statements: Array = []; - if (props.required === false) - statements.push( - ts.factory.createBinaryExpression( - props.accessor, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionEqualsToken), - ts.factory.createAsExpression( - ts.factory.createArrayLiteralExpression(), - ts.factory.createTypeReferenceNode("any[]"), - ), - ), - ); - const decoder: ts.Expression = decode_array_value(props.schema.value); - if (["bool", "bigint", "number"].includes(props.schema.value.type)) { - statements.push( - ts.factory.createIfStatement( - ts.factory.createStrictEquality( - ExpressionFactory.number(2), - ts.factory.createBitwiseAnd( - ts.factory.createIdentifier("tag"), - ExpressionFactory.number(7), - ), - ), - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "piece", - value: ts.factory.createAdd( - callReader("uint32"), - callReader("index"), - ), - }), - ts.factory.createWhileStatement( - ts.factory.createLessThan( - callReader("index"), - ts.factory.createIdentifier("piece"), - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - IdentifierFactory.access(props.accessor, "push"), - undefined, - [decoder], - ), - ), - ), - ], - true, - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - IdentifierFactory.access(props.accessor, "push"), - undefined, - [decoder], - ), - ), - ), - ); - } else - statements.push( - ts.factory.createCallExpression( - IdentifierFactory.access(props.accessor, "push"), - undefined, - [decoder], - ), - ); - return statements.map((stmt) => - ts.isExpression(stmt) ? ts.factory.createExpressionStatement(stmt) : stmt, - ); - }; - - const decode_regular_object = (props: { - top: boolean; - object: MetadataObjectType; - }): ts.Expression => - ts.factory.createCallExpression( - ts.factory.createIdentifier(`${PREFIX}o${props.object.index}`), - undefined, - [ - ts.factory.createIdentifier("reader"), - ...(props.top ? [] : [callReader("uint32")]), - ], - ); - - const decode_map = (props: { - context: ITypiaContext; - accessor: ts.Expression; - key: MetadataSchema; - value: MetadataSchema; - schema: IProtobufSchema.IMap; - initializer: ts.Expression; - required: boolean; - setter: () => ts.Expression; - }): ts.Statement[] => { - const statements: Array = [ - ...(props.required === true - ? [ - ts.factory.createBinaryExpression( - props.accessor, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionEqualsToken), - props.initializer, - ), - ] - : []), - StatementFactory.constant({ - name: "piece", - value: ts.factory.createAdd(callReader("uint32"), callReader("index")), - }), - ...write_object_function_body({ - context: props.context, - condition: ts.factory.createLessThan( - callReader("index"), - ts.factory.createIdentifier("piece"), - ), - tag: "kind", - output: "entry", - object: createObjectType([ - { - key: "key", - value: props.key, - }, - { - key: "value", - value: props.value, - }, - ]), - }), - ...(props.required === false - ? [ - ts.factory.createBinaryExpression( - props.accessor, - ts.factory.createToken(ts.SyntaxKind.QuestionQuestionEqualsToken), - props.initializer, - ), - ] - : []), - props.setter(), - ]; - return [ - ts.factory.createExpressionStatement( - ExpressionFactory.selfCall( - ts.factory.createBlock( - statements.map((stmt) => - ts.isExpression(stmt) - ? ts.factory.createExpressionStatement(stmt) - : stmt, - ), - true, - ), - ), - ), - ]; - }; - - const decode_array_value = ( - schema: IProtobufSchema.IArray["value"], - ): ts.Expression => { - if (schema.type === "bytes") return callReader("bytes"); - else if (schema.type === "bool") return callReader("bool"); - else if (schema.type === "bigint") return callReader(schema.name); - else if (schema.type === "number") return decode_number(schema); - else if (schema.type === "string") return callReader("string"); - else if (schema.type === "object") - return decode_regular_object({ - top: false, - object: schema.object, - }); - throw new Error("unreachable condition"); - }; -} - -const PREFIX = "_pd"; -const callReader = ( - method: string, - args?: ts.Expression[], -): ts.CallExpression => - ts.factory.createCallExpression( - IdentifierFactory.access(ts.factory.createIdentifier("reader"), method), - undefined, - args, - ); -const createObjectType = ( - definitions: Array<{ - key: string; - value: MetadataSchema; - }>, -): MetadataObjectType => { - const properties: MetadataProperty[] = definitions.map((def) => - MetadataProperty.create({ - key: MetadataFactory.soleLiteral(def.key), - value: def.value, - description: null, - jsDocTags: [], - }), - ); - const obj: MetadataObjectType = MetadataObjectType.create({ - name: "object.o" + Math.random().toString().slice(2), - properties, - description: undefined, - jsDocTags: [], - index: -1, - validated: true, - recursive: false, - nullables: [false], - }); - ProtobufFactory.emplaceObject(obj); - return obj; -}; diff --git a/packages/core/src/programmers/protobuf/ProtobufEncodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufEncodeProgrammer.ts deleted file mode 100644 index 7be11a20446..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufEncodeProgrammer.ts +++ /dev/null @@ -1,941 +0,0 @@ -import { ProtobufAtomic } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { ExpressionFactory } from "../../factories/ExpressionFactory"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { NumericRangeFactory } from "../../factories/NumericRangeFactory"; -import { ProtobufFactory } from "../../factories/ProtobufFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataMap } from "../../schemas/metadata/MetadataMap"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { IProtobufProperty } from "../../schemas/protobuf/IProtobufProperty"; -import { IProtobufPropertyType } from "../../schemas/protobuf/IProtobufPropertyType"; -import { IProtobufSchema } from "../../schemas/protobuf/IProtobufSchema"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { ProtobufUtil } from "../helpers/ProtobufUtil"; -import { ProtobufWire } from "../helpers/ProtobufWire"; -import { UnionPredicator } from "../helpers/UnionPredicator"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { decode_union_object } from "../iterate/decode_union_object"; - -export namespace ProtobufEncodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const collection: MetadataCollection = new MetadataCollection(); - const metadata: MetadataSchema = ProtobufFactory.metadata({ - method: props.modulo.getText(), - checker: props.context.checker, - transformer: props.context.transformer, - components: collection, - type: props.type, - }); - - const callEncoder = (writer: string, factory: ts.NewExpression) => - StatementFactory.constant({ - name: writer, - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("encoder"), - undefined, - [factory, ts.factory.createIdentifier("input")], - ), - }); - return { - functions: { - encoder: StatementFactory.constant({ - name: props.functor.useLocal("encoder"), - value: write_encoder({ - context: props.context, - functor: props.functor, - collection, - metadata, - }), - }), - }, - statements: [], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [ - IdentifierFactory.parameter( - "input", - ts.factory.createTypeReferenceNode( - props.name ?? - TypeFactory.getFullName({ - checker: props.context.checker, - type: props.type, - }), - ), - ), - ], - ts.factory.createTypeReferenceNode("Uint8Array"), - undefined, - ts.factory.createBlock( - [ - callEncoder( - "sizer", - ts.factory.createNewExpression( - props.context.importer.internal("ProtobufSizer"), - undefined, - [], - ), - ), - callEncoder( - "writer", - ts.factory.createNewExpression( - props.context.importer.internal("ProtobufWriter"), - undefined, - [ts.factory.createIdentifier("sizer")], - ), - ), - ts.factory.createReturnStatement(callWriter("buffer")), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; - - const write_encoder = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - collection: MetadataCollection; - metadata: MetadataSchema; - }): ts.ArrowFunction => { - const functors = props.collection - .objects() - .filter((obj) => ProtobufUtil.isStaticObject(obj)) - .map((object) => - StatementFactory.constant({ - name: `${PREFIX}o${object.index}`, - value: write_object_function({ - context: props.context, - functor: props.functor, - input: ts.factory.createIdentifier("input"), - object, - explore: { - source: "function", - from: "object", - tracable: false, - postfix: "", - }, - }), - }), - ); - return ts.factory.createArrowFunction( - undefined, - [ - ts.factory.createTypeParameterDeclaration( - undefined, - "Writer", - props.context.importer.type({ - file: "typia/lib/internal/_IProtobufWriter", - name: "_IProtobufWriter", - }), - ), - ], - [ - IdentifierFactory.parameter( - "writer", - ts.factory.createTypeReferenceNode("Writer"), - ), - IdentifierFactory.parameter("input"), - ], - ts.factory.createTypeReferenceNode("Writer"), - undefined, - ts.factory.createBlock( - [ - ...props.functor.declareUnions(), - ...functors, - ...IsProgrammer.write_function_statements(props), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal( - `${PREFIX}o${props.metadata.objects[0]?.type.index ?? 0}`, - ), - ), - [], - [ts.factory.createIdentifier("input")], - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("writer"), - ), - ], - true, - ), - ); - }; - - const write_object_function = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - input: ts.Expression; - object: MetadataObjectType; - explore: FeatureProgrammer.IExplore; - }): ts.ArrowFunction => { - const body: ts.Statement[] = props.object.properties - .map((p) => { - const block = decode_property({ - context: props.context, - functor: props.functor, - explore: props.explore, - metadata: p.value, - protobuf: p.of_protobuf_!, - input: IdentifierFactory.access(props.input, p.key.getSoleLiteral()!), - }); - return [ - ts.factory.createExpressionStatement( - ts.factory.createIdentifier( - `// property ${JSON.stringify(p.key.getSoleLiteral())}: ${p.value.getName()}`, - ), - ), - ...block.statements, - ]; - }) - .flat(); - return ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input")], - TypeFactory.keyword("any"), - undefined, - ts.factory.createBlock(body, true), - ); - }; - - /* ----------------------------------------------------------- - DECODER STATION - ----------------------------------------------------------- */ - const decode_property = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - metadata: MetadataSchema; - protobuf: IProtobufProperty; - input: ts.Expression; - explore: FeatureProgrammer.IExplore; - }): ts.Block => { - const union: IUnion[] = []; - for (const schema of props.protobuf.union) { - //---- - // ATOMICS - //---- - if (schema.type === "bool") - union.push({ - is: () => - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("boolean"), - ts.factory.createTypeOfExpression(props.input), - ), - value: () => - decode_bool({ - input: props.input, - index: schema.index, - }), - }); - else if (schema.type === "bigint") - union.push( - decode_bigint({ - input: props.input, - type: schema.name, - candidates: props.protobuf.union - .filter((s) => s.type === "bigint") - .map((s) => s.name), - index: schema.index, - }), - ); - else if (schema.type === "number") - union.push( - decode_number({ - input: props.input, - type: schema.name, - candidates: props.protobuf.union - .filter((s) => s.type === "number") - .map((s) => s.name), - index: schema.index, - }), - ); - else if (schema.type === "string") - union.push({ - is: () => - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("string"), - ts.factory.createTypeOfExpression(props.input), - ), - value: () => - decode_bytes({ - method: "string", - index: schema.index, - input: props.input, - }), - }); - //---- - // INSTANCES - //---- - else if (schema.type === "bytes") - union.push({ - is: () => ExpressionFactory.isInstanceOf("Uint8Array", props.input), - value: () => - decode_bytes({ - method: "bytes", - index: schema.index, - input: props.input, - }), - }); - else if (schema.type === "array") - union.push({ - is: () => ExpressionFactory.isArray(props.input), - value: () => - decode_array({ - context: props.context, - functor: props.functor, - input: props.input, - schema, - }), - }); - else if (schema.type === "map" && schema.map instanceof MetadataMap) { - union.push({ - is: () => ExpressionFactory.isInstanceOf("Map", props.input), - value: () => - decode_map({ - context: props.context, - functor: props.functor, - schema, - input: props.input, - }), - }); - } - const objectSchemas: Array< - IProtobufPropertyType.IObject | IProtobufPropertyType.IMap - > = props.protobuf.union - .filter((schema) => schema.type === "object" || schema.type === "map") - .filter( - (schema) => - schema.type === "object" || - (schema.type === "map" && schema.map instanceof MetadataObjectType), - ); - if (objectSchemas.length !== 0) - union.push({ - is: () => - ExpressionFactory.isObject({ - checkNull: true, - checkArray: false, - input: props.input, - }), - value: () => - explore_objects({ - context: props.context, - functor: props.functor, - level: 0, - schemas: objectSchemas, - explore: { - ...props.explore, - from: "object", - }, - input: props.input, - }), - }); - } - - // RETURNS - const wrapper: (block: ts.Block) => ts.Block = - props.metadata.isRequired() && props.metadata.nullable === false - ? (block) => block - : props.metadata.isRequired() === false && - props.metadata.nullable === true - ? (block) => - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createLogicalAnd( - ts.factory.createStrictInequality( - ts.factory.createIdentifier("undefined"), - props.input, - ), - ts.factory.createStrictInequality( - ts.factory.createNull(), - props.input, - ), - ), - block, - ), - ], - true, - ) - : props.metadata.isRequired() === false - ? (block) => - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createStrictInequality( - ts.factory.createIdentifier("undefined"), - props.input, - ), - block, - ), - ], - true, - ) - : (block) => - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createStrictInequality( - ts.factory.createNull(), - props.input, - ), - block, - ), - ], - true, - ); - if (union.length === 1) return wrapper(union[0]!.value()); - return wrapper( - ts.factory.createBlock( - [ - union - .map((u, i) => - ts.factory.createIfStatement( - u.is(), - u.value(), - i === union.length - 1 - ? create_throw_error({ - context: props.context, - functor: props.functor, - input: props.input, - expected: props.metadata.getName(), - }) - : undefined, - ), - ) - .reverse() - .reduce((a, b) => - ts.factory.createIfStatement(b.expression, b.thenStatement, a), - ), - ], - true, - ), - ); - }; - - /* ----------------------------------------------------------- - ATOMIC DECODERS - ----------------------------------------------------------- */ - const decode_bool = (props: { - input: ts.Expression; - index: number | null; - }): ts.Block => - ts.factory.createBlock( - [ - ...(props.index !== null - ? [ - decode_tag({ - wire: ProtobufWire.VARIANT, - index: props.index, - }), - ] - : []), - callWriter("bool", [props.input]), - ].map((exp) => ts.factory.createExpressionStatement(exp)), - true, - ); - - const decode_bigint = (props: { - candidates: ProtobufAtomic.BigNumeric[]; - type: ProtobufAtomic.BigNumeric; - input: ts.Expression; - index: number | null; - }): IUnion => ({ - is: () => - props.candidates.length === 1 - ? ts.factory.createStrictEquality( - ts.factory.createStringLiteral("bigint"), - ts.factory.createTypeOfExpression(props.input), - ) - : ts.factory.createLogicalAnd( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("bigint"), - ts.factory.createTypeOfExpression(props.input), - ), - NumericRangeFactory.bigint(props.type, props.input), - ), - value: () => - ts.factory.createBlock( - [ - ...(props.index !== null - ? [ - decode_tag({ - wire: ProtobufWire.VARIANT, - index: props.index, - }), - ] - : []), - callWriter(props.type, [props.input]), - ].map((exp) => ts.factory.createExpressionStatement(exp)), - true, - ), - }); - - const decode_number = (props: { - candidates: ProtobufAtomic.Numeric[]; - type: ProtobufAtomic.Numeric; - input: ts.Expression; - index: number | null; - }): IUnion => ({ - is: () => - props.candidates.length === 1 - ? ts.factory.createStrictEquality( - ts.factory.createStringLiteral("number"), - ts.factory.createTypeOfExpression(props.input), - ) - : ts.factory.createLogicalAnd( - ts.factory.createStrictEquality( - ts.factory.createStringLiteral("number"), - ts.factory.createTypeOfExpression(props.input), - ), - NumericRangeFactory.number(props.type, props.input), - ), - value: () => - ts.factory.createBlock( - [ - ...(props.index !== null - ? [ - decode_tag({ - wire: get_numeric_wire(props.type), - index: props.index, - }), - ] - : []), - callWriter(props.type, [props.input]), - ].map((exp) => ts.factory.createExpressionStatement(exp)), - true, - ), - }); - - const decode_container_value = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - schema: IProtobufPropertyType.IArray["value"]; - index: number; - kind: "array" | "map"; - input: ts.Expression; - }): ts.Block => { - if (props.schema.type === "bool") - return decode_bool({ - input: props.input, - index: props.kind === "array" ? null : props.index, - }); - else if (props.schema.type === "bigint") - return decode_bigint({ - input: props.input, - type: props.schema.name, - candidates: [props.schema.name], - index: props.kind === "array" ? null : props.index, - }).value(); - else if (props.schema.type === "number") - return decode_number({ - input: props.input, - type: props.schema.name, - candidates: [props.schema.name], - index: props.kind === "array" ? null : props.index, - }).value(); - else if (props.schema.type === "string" || props.schema.type === "bytes") - return decode_bytes({ - method: props.schema.type, - input: props.input, - index: props.index, - }); - return decode_object({ - context: props.context, - functor: props.functor, - schema: props.schema, - input: props.input, - index: props.index, - }); - }; - - /* ----------------------------------------------------------- - INSTANCE DECODERS - ----------------------------------------------------------- */ - const decode_bytes = (props: { - method: "bytes" | "string"; - index: number; - input: ts.Expression; - }): ts.Block => - ts.factory.createBlock( - [ - decode_tag({ - wire: ProtobufWire.LEN, - index: props.index, - }), - callWriter(props.method, [props.input]), - ].map((expr) => ts.factory.createExpressionStatement(expr)), - true, - ); - - const decode_array = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - schema: IProtobufPropertyType.IArray; - input: ts.Expression; - }): ts.Block => { - const value: IProtobufPropertyType.IArray["value"] = props.schema.value; - const wire: ProtobufWire = (() => { - if ( - value.type === "object" || - value.type === "bytes" || - value.type === "string" - ) - return ProtobufWire.LEN; - else if (value.type === "number" && value.name === "float") - return ProtobufWire.I32; - return ProtobufWire.VARIANT; - })(); - const forLoop = () => - ts.factory.createForOfStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration("elem")], - ts.NodeFlags.Const, - ), - props.input, - decode_container_value({ - kind: "array", - context: props.context, - functor: props.functor, - input: ts.factory.createIdentifier("elem"), - index: props.schema.index, - schema: props.schema.value, - }), - ); - const length = (block: ts.Block) => - ts.factory.createBlock( - [ - ts.factory.createIfStatement( - ts.factory.createStrictInequality( - ExpressionFactory.number(0), - IdentifierFactory.access(props.input, "length"), - ), - block, - ), - ], - true, - ); - if (wire === ProtobufWire.LEN) - return length(ts.factory.createBlock([forLoop()], true)); - return length( - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - decode_tag({ - wire: ProtobufWire.LEN, - index: props.schema.index, - }), - ), - ts.factory.createExpressionStatement(callWriter("fork")), - forLoop(), - ts.factory.createExpressionStatement(callWriter("ldelim")), - ], - true, - ), - ); - }; - - const decode_object = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - schema: IProtobufSchema.IObject; - index: number; - input: ts.Expression; - }): ts.Block => - ts.factory.createBlock( - [ - decode_tag({ - wire: ProtobufWire.LEN, - index: props.index, - }), - callWriter("fork"), - ts.factory.createCallExpression( - ts.factory.createIdentifier( - props.functor.useLocal(`${PREFIX}o${props.schema.object.index}`), - ), - [], - [props.input], - ), - callWriter("ldelim"), - ].map(ts.factory.createExpressionStatement), - true, - ); - - const decode_map = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - schema: IProtobufPropertyType.IMap; - input: ts.Expression; - }): ts.Block => { - const each: ts.Statement[] = [ - ts.factory.createExpressionStatement( - decode_tag({ - wire: ProtobufWire.LEN, - index: props.schema.index, - }), - ), - ts.factory.createExpressionStatement(callWriter("fork")), - ...decode_container_value({ - kind: "map", - context: props.context, - functor: props.functor, - index: 1, - input: ts.factory.createIdentifier("key"), - schema: props.schema.key, - }).statements, - ...decode_container_value({ - kind: "map", - context: props.context, - functor: props.functor, - index: 2, - input: ts.factory.createIdentifier("value"), - schema: props.schema.value, - }).statements, - ts.factory.createExpressionStatement(callWriter("ldelim")), - ]; - return ts.factory.createBlock( - [ - ts.factory.createForOfStatement( - undefined, - StatementFactory.entry({ - key: "key", - value: "value", - }), - props.input, - ts.factory.createBlock(each), - ), - ], - true, - ); - }; - - const explore_objects = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - level: number; - input: ts.Expression; - schemas: Array; - explore: FeatureProgrammer.IExplore; - }): ts.Block => { - const out = ( - schema: IProtobufPropertyType.IObject | IProtobufPropertyType.IMap, - ) => - schema.type === "object" - ? decode_object({ - context: props.context, - functor: props.functor, - schema, - index: schema.index, - input: props.input, - }) - : decode_map({ - context: props.context, - functor: props.functor, - schema, - input: ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createIdentifier("Object"), - "entries", - ), - undefined, - [props.input], - ), - }); - if (props.schemas.length === 1) return out(props.schemas[0]!); - - const objects: MetadataObjectType[] = props.schemas.map((s) => - s.type === "map" ? (s.map as MetadataObjectType) : s.object, - ); - const expected: string = `(${objects.map((t) => t.name).join(" | ")})`; - const indexes: WeakMap< - MetadataObjectType, - IProtobufPropertyType.IObject | IProtobufPropertyType.IMap - > = new WeakMap(objects.map((o, i) => [o, props.schemas[i]!])); - const specifications: UnionPredicator.ISpecialized[] = - UnionPredicator.object(objects); - - if (specifications.length === 0) { - const condition: ts.Expression = decode_union_object({ - checker: (v) => - IsProgrammer.decode_object({ - context: props.context, - functor: props.functor, - object: v.object, - input: v.input, - explore: v.explore, - }), - decoder: (v) => ExpressionFactory.selfCall(out(indexes.get(v.object)!)), - success: (expr) => expr, - escaper: (v) => - create_throw_error({ - context: props.context, - functor: props.functor, - expected: v.expected, - input: v.input, - }), - input: props.input, - explore: props.explore, - objects, - }); - return StatementFactory.block(condition); - } - const remained: MetadataObjectType[] = objects.filter( - (o) => specifications.find((s) => s.object === o) === undefined, - ); - - // DO SPECIALIZE - const condition: ts.IfStatement = specifications - .filter((spec) => spec.property.key.getSoleLiteral() !== null) - .map((spec, i, array) => { - const key: string = spec.property.key.getSoleLiteral()!; - const accessor: ts.Expression = IdentifierFactory.access( - props.input, - key, - ); - const pred: ts.Expression = spec.neighbor - ? IsProgrammer.decode({ - context: props.context, - functor: props.functor, - input: accessor, - metadata: spec.property.value, - explore: { - ...props.explore, - tracable: false, - postfix: IdentifierFactory.postfix(key), - }, - }) - : ExpressionFactory.isRequired(accessor); - const schema = indexes.get(spec.object)!; - return ts.factory.createIfStatement( - pred, - ts.factory.createExpressionStatement( - ExpressionFactory.selfCall(out(schema)), - ), - i === array.length - 1 - ? remained.length - ? ts.factory.createExpressionStatement( - ExpressionFactory.selfCall( - explore_objects({ - context: props.context, - functor: props.functor, - level: props.level + 1, - input: props.input, - schemas: remained.map((r) => indexes.get(r)!), - explore: props.explore, - }), - ), - ) - : create_throw_error({ - context: props.context, - functor: props.functor, - input: props.input, - expected, - }) - : undefined, - ); - }) - .reverse() - .reduce((a, b) => - ts.factory.createIfStatement(b.expression, b.thenStatement, a), - ); - - // RETURNS WITH CONDITIONS - return ts.factory.createBlock([condition], true); - }; - - /* ----------------------------------------------------------- - BACKGROUND FUNCTIONS - ----------------------------------------------------------- */ - const PREFIX = "_pe"; - - const decode_tag = (props: { - wire: ProtobufWire; - index: number; - }): ts.CallExpression => - callWriter("uint32", [ - ExpressionFactory.number((props.index << 3) | props.wire), - ]); - - const get_numeric_wire = (type: ProtobufAtomic.Numeric) => - type === "double" - ? ProtobufWire.I64 - : type === "float" - ? ProtobufWire.I32 - : ProtobufWire.VARIANT; - - const create_throw_error = (props: { - context: ITypiaContext; - functor: FunctionProgrammer; - expected: string; - input: ts.Expression; - }) => - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - props.context.importer.internal("throwTypeGuardError"), - [], - [ - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - "method", - ts.factory.createStringLiteral(props.functor.method), - ), - ts.factory.createPropertyAssignment( - "expected", - ts.factory.createStringLiteral(props.expected), - ), - ts.factory.createPropertyAssignment("value", props.input), - ], - true, - ), - ], - ), - ); -} - -const callWriter = ( - method: string, - args?: ts.Expression[], -): ts.CallExpression => - ts.factory.createCallExpression( - IdentifierFactory.access(ts.factory.createIdentifier("writer"), method), - undefined, - args, - ); - -interface IUnion { - is: () => ts.Expression; - value: () => ts.Block; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufIsDecodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufIsDecodeProgrammer.ts deleted file mode 100644 index 47f59e1930c..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufIsDecodeProgrammer.ts +++ /dev/null @@ -1,107 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { ProtobufDecodeProgrammer } from "./ProtobufDecodeProgrammer"; - -export namespace ProtobufIsDecodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - const decode: FeatureProgrammer.IDecomposed = - ProtobufDecodeProgrammer.decompose(props); - return { - functions: { - ...is.functions, - ...decode.functions, - }, - statements: [ - ...is.statements, - ...decode.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - ts.factory.createUnionTypeNode([ - decode.arrow.type ?? TypeFactory.keyword("any"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "value", - value: ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - }), - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("value")], - ), - ), - ts.factory.createReturnStatement(ts.factory.createNull()), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("value"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufIsEncodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufIsEncodeProgrammer.ts deleted file mode 100644 index b722a107971..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufIsEncodeProgrammer.ts +++ /dev/null @@ -1,96 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { IsProgrammer } from "../IsProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { ProtobufEncodeProgrammer } from "./ProtobufEncodeProgrammer"; - -export namespace ProtobufIsEncodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const is: FeatureProgrammer.IDecomposed = IsProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - finite: true, - }, - }, - config: { - equals: false, - }, - }); - const encode: FeatureProgrammer.IDecomposed = - ProtobufEncodeProgrammer.decompose(props); - return { - functions: { - ...is.functions, - ...encode.functions, - }, - statements: [ - ...is.statements, - ...encode.statements, - StatementFactory.constant({ - name: "__is", - value: is.arrow, - }), - StatementFactory.constant({ - name: "__encode", - value: encode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - ts.factory.createUnionTypeNode([ - encode.arrow.type ?? ts.factory.createTypeReferenceNode("Uint8Array"), - ts.factory.createTypeReferenceNode("null"), - ]), - undefined, - ts.factory.createConditionalExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__is"), - undefined, - [ts.factory.createIdentifier("input")], - ), - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__encode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - undefined, - ts.factory.createNull(), - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufMessageProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufMessageProgrammer.ts deleted file mode 100644 index 31ba89cb947..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufMessageProgrammer.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { MapUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { ProtobufFactory } from "../../factories/ProtobufFactory"; -import { MetadataCollection } from "../../schemas/metadata/MetadataCollection"; -import { MetadataObjectType } from "../../schemas/metadata/MetadataObjectType"; -import { MetadataSchema } from "../../schemas/metadata/MetadataSchema"; -import { IProtobufProperty } from "../../schemas/protobuf/IProtobufProperty"; -import { IProtobufPropertyType } from "../../schemas/protobuf/IProtobufPropertyType"; -import { IProtobufSchema } from "../../schemas/protobuf/IProtobufSchema"; -import { ProtobufNameEncoder } from "../../utils/ProtobufNameEncoder"; - -export namespace ProtobufMessageProgrammer { - export interface IProps { - context: ITypiaContext; - type: ts.Type; - } - export const write = (props: IProps) => { - // PARSE TARGET TYPE - const collection: MetadataCollection = new MetadataCollection(); - ProtobufFactory.metadata({ - method: "message", - checker: props.context.checker, - transformer: props.context.transformer, - components: collection, - type: props.type, - }); - - // STRINGIFY - const hierarchies: Map = new Map(); - for (const object of collection.objects()) - if (is_dynamic_object(object) === false) - emplace({ - hierarchies, - object, - }); - - const content: string = - `syntax = "proto3";\n\n` + - [...hierarchies.values()] - .map((hier) => write_hierarchy(hier)) - .join("\n\n"); - - // RETURNS - return ts.factory.createCallExpression( - IdentifierFactory.access( - ts.factory.createArrayLiteralExpression( - content.split("\n").map((str) => ts.factory.createStringLiteral(str)), - true, - ), - "join", - ), - undefined, - [ts.factory.createStringLiteral("\n")], - ); - }; - - const emplace = (props: { - hierarchies: Map; - object: MetadataObjectType; - }) => { - let hierarchies: Map = props.hierarchies; - const accessors: string[] = props.object.name.split("."); - accessors.forEach((access, i) => { - const hierarchy: Hierarchy = MapUtil.take(hierarchies, access, () => ({ - key: access, - object: null!, - children: new Map(), - })); - hierarchies = hierarchy.children; - if (i === accessors.length - 1) hierarchy.object = props.object; - }); - }; - - const is_dynamic_object = (object: MetadataObjectType): boolean => - object.properties.length === 1 && - object.properties[0]!.key.isSoleLiteral() === false; - - const write_hierarchy = (hierarchy: Hierarchy): string => { - const elements: string[] = [ - `message ${ProtobufNameEncoder.encode(hierarchy.key)} {`, - ]; - if (hierarchy.object !== null) { - const text: string = write_object(hierarchy.object); - elements.push(...text.split("\n").map((str) => ` ${str}`)); - } - if (hierarchy.children.size) - elements.push( - [...hierarchy.children.values()] - .map((child) => write_hierarchy(child)) - .map((body) => - body - .split("\n") - .map((line) => ` ${line}`) - .join("\n"), - ) - .join("\n\n"), - ); - elements.push("}"); - return elements.join("\n"); - }; - - const write_object = (obj: MetadataObjectType): string => { - return obj.properties - .map((p) => { - if (p.of_protobuf_ === undefined) ProtobufFactory.emplaceObject(obj); - const schema: IProtobufProperty = p.of_protobuf_!; - const key: string = p.key.getSoleLiteral()!; - return decodeProperty({ - key, - value: p.value, - union: schema.union, - }); - }) - .join("\n"); - }; - - /* ----------------------------------------------------------- - DECODERS - ----------------------------------------------------------- */ - const decodeProperty = (props: { - key: string; - value: MetadataSchema; - union: IProtobufPropertyType[]; - }): string => { - if (props.union.length === 1) { - const top = props.union[0]!; - return [ - ...(top.type === "array" || top.type === "map" - ? [] - : [ - props.value.isRequired() && props.value.nullable === false - ? "required" - : "optional", - ]), - decodeSchema(top), - props.key, - "=", - `${top.index};`, - ].join(" "); - } - return [ - `oneof ${props.key} {`, - ...props.union - .map((type) => `${decodeSchema(type)} v${type.index} = ${type.index};`) - .map((str) => str.split("\n")) - .map((str) => ` ${str}`), - `}`, - ].join("\n"); - }; - - const decodeSchema = (schema: IProtobufSchema): string => { - if ( - schema.type === "bytes" || - schema.type === "bool" || - schema.type === "string" - ) - return schema.type; - else if (schema.type === "bigint" || schema.type === "number") - return schema.name; - else if (schema.type === "array") - return `repeated ${decodeSchema(schema.value)}`; - else if (schema.type === "object") - return ProtobufNameEncoder.encode(schema.object.name); - // else if (schema.type === "map") - return `map<${decodeSchema(schema.key)}, ${decodeSchema(schema.value)}>`; - }; -} - -interface Hierarchy { - key: string; - object: MetadataObjectType | null; - children: Map; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufValidateDecodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufValidateDecodeProgrammer.ts deleted file mode 100644 index 88cd1233f70..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufValidateDecodeProgrammer.ts +++ /dev/null @@ -1,90 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { ProtobufDecodeProgrammer } from "./ProtobufDecodeProgrammer"; - -export namespace ProtobufValidateDecodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - config: { - equals: false, - }, - }); - const decode = ProtobufDecodeProgrammer.decompose(props); - return { - functions: { - ...validate.functions, - ...decode.functions, - }, - statements: [ - ...validate.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__decode", - value: decode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - decode.arrow.parameters, - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [decode.arrow.type ?? TypeFactory.keyword("any")], - }), - undefined, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ - ts.factory.createCallExpression( - ts.factory.createIdentifier("__decode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ], - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/protobuf/ProtobufValidateEncodeProgrammer.ts b/packages/core/src/programmers/protobuf/ProtobufValidateEncodeProgrammer.ts deleted file mode 100644 index 834832a765a..00000000000 --- a/packages/core/src/programmers/protobuf/ProtobufValidateEncodeProgrammer.ts +++ /dev/null @@ -1,117 +0,0 @@ -import ts from "@typescript/native-preview"; - -import { IProgrammerProps } from "../../context/IProgrammerProps"; -import { ITypiaContext } from "../../context/ITypiaContext"; -import { IdentifierFactory } from "../../factories/IdentifierFactory"; -import { StatementFactory } from "../../factories/StatementFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { ValidateProgrammer } from "../ValidateProgrammer"; -import { FunctionProgrammer } from "../helpers/FunctionProgrammer"; -import { FeatureProgrammer } from "../internal/FeatureProgrammer"; -import { ProtobufEncodeProgrammer } from "./ProtobufEncodeProgrammer"; - -export namespace ProtobufValidateEncodeProgrammer { - export const decompose = (props: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - functor: FunctionProgrammer; - type: ts.Type; - name: string | undefined; - }): FeatureProgrammer.IDecomposed => { - const validate = ValidateProgrammer.decompose({ - ...props, - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - finite: true, - }, - }, - config: { - equals: false, - }, - }); - const encode = ProtobufEncodeProgrammer.decompose(props); - return { - functions: { - ...validate.functions, - ...encode.functions, - }, - statements: [ - ...validate.statements, - ...encode.statements, - StatementFactory.constant({ - name: "__validate", - value: validate.arrow, - }), - StatementFactory.constant({ - name: "__encode", - value: encode.arrow, - }), - ], - arrow: ts.factory.createArrowFunction( - undefined, - undefined, - [IdentifierFactory.parameter("input", TypeFactory.keyword("any"))], - props.context.importer.type({ - file: "typia", - name: "IValidation", - arguments: [ - encode.arrow.type ?? - ts.factory.createTypeReferenceNode("Uint8Array"), - ], - }), - undefined, - ts.factory.createBlock( - [ - StatementFactory.constant({ - name: "result", - value: ts.factory.createAsExpression( - ts.factory.createCallExpression( - ts.factory.createIdentifier("__validate"), - undefined, - [ts.factory.createIdentifier("input")], - ), - TypeFactory.keyword("any"), - ), - }), - ts.factory.createIfStatement( - ts.factory.createIdentifier("result.success"), - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( - ts.factory.createIdentifier("result.data"), - ts.SyntaxKind.EqualsToken, - ts.factory.createCallExpression( - ts.factory.createIdentifier("__encode"), - undefined, - [ts.factory.createIdentifier("input")], - ), - ), - ), - ), - ts.factory.createReturnStatement( - ts.factory.createIdentifier("result"), - ), - ], - true, - ), - ), - }; - }; - - export const write = (props: IProgrammerProps): ts.CallExpression => { - const functor: FunctionProgrammer = new FunctionProgrammer( - props.modulo.getText(), - ); - const result: FeatureProgrammer.IDecomposed = decompose({ - ...props, - functor, - }); - return FeatureProgrammer.writeDecomposed({ - modulo: props.modulo, - functor, - result, - }); - }; -} diff --git a/packages/core/src/programmers/protobuf/index.ts b/packages/core/src/programmers/protobuf/index.ts deleted file mode 100644 index eb9c2581e43..00000000000 --- a/packages/core/src/programmers/protobuf/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./ProtobufAssertDecodeProgrammer"; -export * from "./ProtobufAssertEncodeProgrammer"; -export * from "./ProtobufDecodeProgrammer"; -export * from "./ProtobufEncodeProgrammer"; -export * from "./ProtobufIsDecodeProgrammer"; -export * from "./ProtobufIsEncodeProgrammer"; -export * from "./ProtobufMessageProgrammer"; -export * from "./ProtobufValidateDecodeProgrammer"; -export * from "./ProtobufValidateEncodeProgrammer"; diff --git a/packages/core/src/schemas/index.ts b/packages/core/src/schemas/index.ts deleted file mode 100644 index 2298807a797..00000000000 --- a/packages/core/src/schemas/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./metadata"; -export * from "./protobuf"; diff --git a/packages/core/src/schemas/metadata/IMetadataDictionary.ts b/packages/core/src/schemas/metadata/IMetadataDictionary.ts deleted file mode 100644 index 2f642d50054..00000000000 --- a/packages/core/src/schemas/metadata/IMetadataDictionary.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { MetadataAliasType } from "./MetadataAliasType"; -import { MetadataArrayType } from "./MetadataArrayType"; -import { MetadataObjectType } from "./MetadataObjectType"; -import { MetadataTupleType } from "./MetadataTupleType"; - -/** - * Dictionary of named type definitions for metadata deserialization. - * - * `IMetadataDictionary` maps type names to their metadata definitions, enabling - * reconstruction of metadata schemas from serialized JSON format. Used by - * {@link MetadataSchema.from} to resolve named type references. - * - * The dictionaries are populated during metadata collection and contain all - * unique named types encountered in the analyzed TypeScript code. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export interface IMetadataDictionary { - /** - * Named object type definitions. - * - * Maps object type names to their full type information including properties, - * required fields, and inheritance hierarchy. - */ - objects: Map; - - /** - * Named type alias definitions. - * - * Maps type alias names to their underlying type information. Aliases may - * reference other aliases or concrete types. - */ - aliases: Map; - - /** - * Named array type definitions. - * - * Maps array type names to their element type information. Used for recursive - * array types. - */ - arrays: Map; - - /** - * Named tuple type definitions. - * - * Maps tuple type names to their element types and structure. Each element - * position may have a different type. - */ - tuples: Map; -} diff --git a/packages/core/src/schemas/metadata/MetadataAlias.ts b/packages/core/src/schemas/metadata/MetadataAlias.ts deleted file mode 100644 index af76a6875ca..00000000000 --- a/packages/core/src/schemas/metadata/MetadataAlias.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { MetadataAliasType } from "./MetadataAliasType"; - -export class MetadataAlias { - public readonly type: MetadataAliasType; - public readonly tags: IMetadataTypeTag[][]; - private name_?: string; - - private constructor(props: ClassProperties) { - this.type = props.type; - this.tags = props.tags; - this.type = props.type; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataAlias { - return new MetadataAlias(props); - } - - public getName(): string { - return (this.name_ ??= (() => { - if (this.tags.length === 0) return this.type.name; - else if (this.tags.length === 1) { - const str: string = [ - this.type.name, - ...this.tags[0]!.map((t) => t.name), - ].join(" & "); - return `(${str})`; - } - const rows: string[] = this.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${this.type.name} & (${rows.join(" | ")}))`; - })()); - } - - public toJSON(): IMetadataSchema.IReference { - return { - name: this.type.name, - tags: this.tags, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataAliasType.ts b/packages/core/src/schemas/metadata/MetadataAliasType.ts deleted file mode 100644 index b7a87891585..00000000000 --- a/packages/core/src/schemas/metadata/MetadataAliasType.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - ClassProperties, - IJsDocTagInfo, - IMetadataSchema, -} from "@typia/interface"; - -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataAliasType { - public readonly name: string; - public readonly value: MetadataSchema; - public readonly description: string | null; - public readonly jsDocTags: IJsDocTagInfo[]; - public readonly recursive: boolean; - public readonly nullables: boolean[]; - - /* ----------------------------------------------------------- - CONSTRUCTORS - ----------------------------------------------------------- */ - private constructor(props: ClassProperties) { - this.name = props.name; - this.value = props.value; - this.description = props.description; - this.jsDocTags = props.jsDocTags; - this.recursive = props.recursive; - this.nullables = props.nullables; - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataAliasType { - return new MetadataAliasType(props); - } - - /** @internal */ - public static _From_without_value( - props: Omit, - ) { - return MetadataAliasType.create({ - name: props.name, - value: null!, - description: props.description, - recursive: props.recursive, - jsDocTags: props.jsDocTags.slice(), - nullables: props.nullables.slice(), - }); - } - - public toJSON(): IMetadataSchema.IAliasType { - return { - name: this.name, - value: this.value.toJSON(), - description: this.description, - recursive: this.recursive, - jsDocTags: this.jsDocTags.slice(), - nullables: this.nullables.slice(), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataApplication.ts b/packages/core/src/schemas/metadata/MetadataApplication.ts deleted file mode 100644 index beb9b5a1d8f..00000000000 --- a/packages/core/src/schemas/metadata/MetadataApplication.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ClassProperties, IMetadataSchemaCollection } from "@typia/interface"; - -import { MetadataComponents } from "./MetadataComponents"; -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataApplication { - public readonly schemas: MetadataSchema[]; - public readonly components: MetadataComponents; - - private constructor(props: ClassProperties) { - this.schemas = props.schemas; - this.components = props.components; - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataApplication { - return new MetadataApplication(props); - } - - public static from(app: IMetadataSchemaCollection): MetadataApplication { - const components: MetadataComponents = MetadataComponents.from( - app.components, - ); - const metadatas: MetadataSchema[] = app.schemas.map((metadata) => - MetadataSchema.from(metadata, components.dictionary), - ); - return MetadataApplication.create({ schemas: metadatas, components }); - } - - public toJSON(): IMetadataSchemaCollection { - return { - schemas: this.schemas.map((s) => s.toJSON()), - components: this.components.toJSON(), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataArray.ts b/packages/core/src/schemas/metadata/MetadataArray.ts deleted file mode 100644 index 903c17de7d7..00000000000 --- a/packages/core/src/schemas/metadata/MetadataArray.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { MetadataArrayType } from "./MetadataArrayType"; - -export class MetadataArray { - public readonly type: MetadataArrayType; - public readonly tags: IMetadataTypeTag[][]; - - private name_?: string; - - private constructor(props: ClassProperties) { - this.type = props.type; - this.tags = props.tags; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataArray { - return new MetadataArray(props); - } - - public getName(): string { - return (this.name_ ??= (() => { - if (this.tags.length === 0) return this.type.name; - else if (this.tags.length === 1) { - const str: string = [ - this.type.name, - ...this.tags[0]!.map((t) => t.name), - ].join(" & "); - return `(${str})`; - } - const rows: string[] = this.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${this.type.name} & (${rows.join(" | ")}))`; - })()); - } - - public toJSON(): IMetadataSchema.IReference { - return { - name: this.type.name, - tags: this.tags.map((row) => row.slice()), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataArrayType.ts b/packages/core/src/schemas/metadata/MetadataArrayType.ts deleted file mode 100644 index 5927250c369..00000000000 --- a/packages/core/src/schemas/metadata/MetadataArrayType.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ClassProperties, IMetadataSchema } from "@typia/interface"; - -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataArrayType { - public readonly name: string; - public readonly value: MetadataSchema; - public readonly nullables: boolean[]; - public readonly recursive: boolean; - public readonly index: number | null; - - private constructor(props: ClassProperties) { - this.name = props.name; - this.value = props.value; - this.index = props.index; - this.recursive = props.recursive; - this.nullables = props.nullables; - } - - /** @internal */ - public static _From_without_value( - props: Omit, - ): MetadataArrayType { - return MetadataArrayType.create({ - name: props.name, - value: null!, - index: props.index, - recursive: props.recursive, - nullables: props.nullables, - }); - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataArrayType { - return new MetadataArrayType(props); - } - - public toJSON(): IMetadataSchema.IArrayType { - return { - name: this.name, - value: this.value.toJSON(), - nullables: this.nullables, - recursive: this.recursive, - index: this.index, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataAtomic.ts b/packages/core/src/schemas/metadata/MetadataAtomic.ts deleted file mode 100644 index e4a8ae80ee5..00000000000 --- a/packages/core/src/schemas/metadata/MetadataAtomic.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -export class MetadataAtomic { - public readonly type: "boolean" | "bigint" | "number" | "string"; - public readonly tags: IMetadataTypeTag[][]; - - private name_?: string; - - private constructor(props: ClassProperties) { - this.type = props.type; - this.tags = props.tags; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataAtomic { - return new MetadataAtomic(props); - } - - public static from(json: IMetadataSchema.IAtomic): MetadataAtomic { - return MetadataAtomic.create({ - type: json.type, - tags: json.tags.map((row) => - row.map((tag) => ({ - target: tag.target, - name: tag.name, - kind: tag.kind, - value: - typeof tag.value === "object" && - tag.value?.type === "bigint" && - typeof tag.value.value === "string" - ? BigInt(tag.value.value) - : tag.value, - validate: tag.validate, - exclusive: tag.exclusive, - schema: tag.schema, - })), - ), - }); - } - - public getName(): string { - return (this.name_ ??= getName(this)); - } - - public toJSON(): IMetadataSchema.IAtomic { - return { - type: this.type, - tags: this.tags.map((row) => - row.map((tag) => ({ - target: tag.target, - name: tag.name, - kind: tag.kind, - value: - typeof tag.value === "bigint" - ? { - type: "bigint", - value: tag.value.toString(), - } - : tag.value, - validate: tag.validate, - exclusive: tag.exclusive, - schema: tag.schema, - })), - ), - }; - } -} - -const getName = (obj: MetadataAtomic): string => { - if (obj.tags.length === 0) return obj.type; - else if (obj.tags.length === 1) { - const str: string = [obj.type, ...obj.tags[0]!.map((t) => t.name)].join( - " & ", - ); - return `(${str})`; - } - const rows: string[] = obj.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${obj.type} & (${rows.join(" | ")}))`; -}; diff --git a/packages/core/src/schemas/metadata/MetadataCollection.ts b/packages/core/src/schemas/metadata/MetadataCollection.ts deleted file mode 100644 index 36fe769d700..00000000000 --- a/packages/core/src/schemas/metadata/MetadataCollection.ts +++ /dev/null @@ -1,275 +0,0 @@ -import { IMetadataComponents } from "@typia/interface"; -import { MapUtil } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { CommentFactory } from "../../factories/CommentFactory"; -import { TypeFactory } from "../../factories/TypeFactory"; -import { Writable } from "../../typings/Writable"; -import { MetadataAliasType } from "./MetadataAliasType"; -import { MetadataArrayType } from "./MetadataArrayType"; -import { MetadataObjectType } from "./MetadataObjectType"; -import { MetadataSchema } from "./MetadataSchema"; -import { MetadataTupleType } from "./MetadataTupleType"; - -/** - * Storage for collected metadata during type analysis. - * - * Caches analyzed types (objects, aliases, arrays, tuples) to handle recursive - * types and avoid redundant analysis. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export class MetadataCollection { - private objects_: Map; - private object_unions_: Map; - private aliases_: Map; - private arrays_: Map; - private tuples_: Map; - - private names_: Map>; - private object_index_: number; - private recursive_array_index_: number; - private recursive_tuple_index_: number; - - public constructor(private options?: Partial) { - this.objects_ = new Map(); - this.object_unions_ = new Map(); - this.aliases_ = new Map(); - this.arrays_ = new Map(); - this.tuples_ = new Map(); - - this.names_ = new Map(); - this.object_index_ = 0; - this.recursive_array_index_ = 0; - this.recursive_tuple_index_ = 0; - } - - public clone(): MetadataCollection { - const output: MetadataCollection = new MetadataCollection(this.options); - output.objects_ = new Map(this.objects_); - output.object_unions_ = new Map(this.object_unions_); - output.aliases_ = new Map(this.aliases_); - output.arrays_ = new Map(this.arrays_); - output.tuples_ = new Map(this.tuples_); - output.names_ = new Map(this.names_); - output.object_index_ = this.object_index_; - output.recursive_array_index_ = this.recursive_array_index_; - output.recursive_tuple_index_ = this.recursive_tuple_index_; - return output; - } - - /* ----------------------------------------------------------- - ACCESSORS - ----------------------------------------------------------- */ - public aliases(): MetadataAliasType[] { - return [...this.aliases_.values()]; - } - - public objects(): MetadataObjectType[] { - return [...this.objects_.values()]; - } - - public unions(): MetadataObjectType[][] { - return [...this.object_unions_.values()]; - } - - public arrays(): MetadataArrayType[] { - return [...this.arrays_.values()]; - } - - public tuples(): MetadataTupleType[] { - return [...this.tuples_.values()]; - } - - private getName(checker: ts.TypeChecker, type: ts.Type): string { - const name: string = (() => { - const str: string = TypeFactory.getFullName({ - checker, - type, - }); - return this.options?.replace ? this.options.replace(str) : str; - })(); - - const duplicates: Map = MapUtil.take( - this.names_, - name, - () => new Map(), - ); - const oldbie: string | undefined = duplicates.get(type); - if (oldbie !== undefined) return oldbie; - - const addicted: string = duplicates.size - ? `${name}.o${duplicates.size}` - : name; - duplicates.set(type, addicted); - return addicted; - } - - /** @internal */ - public getUnionIndex(meta: MetadataSchema): number { - const key: string = meta.objects.map((obj) => obj.type.name).join(" | "); - MapUtil.take(this.object_unions_, key, () => - meta.objects.map((o) => o.type), - ); - return [...this.object_unions_.keys()].indexOf(key); - } - - /* ----------------------------------------------------------- - INSTANCES - ----------------------------------------------------------- */ - public emplace( - checker: ts.TypeChecker, - type: ts.Type, - ): [MetadataObjectType, boolean] { - const oldbie = this.objects_.get(type); - if (oldbie !== undefined) return [oldbie, false]; - - const id: string = this.getName(checker, type); - const obj: MetadataObjectType = MetadataObjectType.create({ - name: id, - properties: [], - description: - (type.aliasSymbol && CommentFactory.description(type.aliasSymbol)) ?? - (type.symbol && CommentFactory.description(type.symbol)) ?? - undefined, - jsDocTags: - type.aliasSymbol?.getJsDocTags() ?? type.symbol?.getJsDocTags() ?? [], - validated: false, - index: this.object_index_++, - recursive: null!, - nullables: [], - }); - this.objects_.set(type, obj); - return [obj, true]; - } - - public emplaceAlias( - checker: ts.TypeChecker, - type: ts.Type, - symbol: ts.Symbol, - ): [MetadataAliasType, boolean, (meta: MetadataSchema) => void] { - const oldbie = this.aliases_.get(type); - if (oldbie !== undefined) return [oldbie, false, () => {}]; - - const id: string = this.getName(checker, type); - const alias: MetadataAliasType = MetadataAliasType.create({ - name: id, - value: null!, - description: CommentFactory.description(symbol) ?? null, - recursive: null!, - nullables: [], - jsDocTags: symbol.getJsDocTags() ?? [], - }); - this.aliases_.set(type, alias); - return [alias, true, (meta) => (Writable(alias).value = meta)]; - } - - public emplaceArray( - checker: ts.TypeChecker, - type: ts.Type, - ): [MetadataArrayType, boolean, (meta: MetadataSchema) => void] { - const oldbie = this.arrays_.get(type); - if (oldbie !== undefined) return [oldbie, false, () => {}]; - - const id = this.getName(checker, type); - const array: MetadataArrayType = MetadataArrayType.create({ - name: id, - value: null!, - index: null, - recursive: null!, - nullables: [], - }); - this.arrays_.set(type, array); - return [array, true, (meta) => (Writable(array).value = meta)]; - } - - public emplaceTuple( - checker: ts.TypeChecker, - type: ts.TupleType, - ): [MetadataTupleType, boolean, (elements: MetadataSchema[]) => void] { - const oldbie = this.tuples_.get(type); - if (oldbie !== undefined) return [oldbie, false, () => {}]; - - const id = this.getName(checker, type); - const tuple: MetadataTupleType = MetadataTupleType.create({ - name: id, - elements: null!, - index: null, - recursive: null!, - nullables: [], - }); - this.tuples_.set(type, tuple); - return [tuple, true, (elements) => (Writable(tuple).elements = elements)]; - } - - /** @internal */ - public setObjectRecursive(obj: MetadataObjectType, recursive: boolean): void { - Writable(obj).recursive = recursive; - } - - /** @internal */ - public setAliasRecursive(alias: MetadataAliasType, recursive: boolean): void { - Writable(alias).recursive = recursive; - } - - /** @internal */ - public setArrayRecursive(array: MetadataArrayType, recursive: boolean): void { - Writable(array).recursive = recursive; - if (recursive) Writable(array).index = this.recursive_array_index_++; - } - - public setTupleRecursive(tuple: MetadataTupleType, recursive: boolean): void { - Writable(tuple).recursive = recursive; - if (recursive) Writable(tuple).index = this.recursive_tuple_index_++; - } - - public toJSON(): IMetadataComponents { - return { - objects: this.objects().map((o) => o.toJSON()), - aliases: this.aliases().map((d) => d.toJSON()), - arrays: [...this.arrays_.values()].map((a) => a.toJSON()), - tuples: [...this.tuples_.values()].map((t) => t.toJSON()), - }; - } -} -export namespace MetadataCollection { - export interface IOptions { - replace?(str: string): string; - } - - export const replace = (str: string): string => { - let replaced: string = str; - for (const [before] of REPLACERS) - replaced = replaced.split(before).join(""); - if (replaced.length !== 0) return replaced; - - for (const [before, after] of REPLACERS) - str = str.split(before).join(after); - return str; - }; - - export const escape = (str: string): string => { - for (const [before, after] of REPLACERS) - if (after !== "") str = str.split(after).join(before); - return str; - }; -} -const REPLACERS: [string, string][] = [ - ["$", "_dollar_"], - ["&", "_and_"], - ["|", "_or_"], - ["{", "_blt_"], - ["}", "_bgt_"], - ["<", "_lt_"], - [">", "_gt_"], - ["[", "_alt_"], - ["]", "_agt_"], - [",", "_comma_"], - ["`", "_backquote_"], - ["'", "_singlequote_"], - ['"', "_doublequote_"], - [" ", "_space_"], - ["?", "_question_"], - [":", "_colon_"], - [";", "_semicolon_"], -]; diff --git a/packages/core/src/schemas/metadata/MetadataComponents.ts b/packages/core/src/schemas/metadata/MetadataComponents.ts deleted file mode 100644 index f6c47496bcf..00000000000 --- a/packages/core/src/schemas/metadata/MetadataComponents.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { ClassProperties, IMetadataComponents } from "@typia/interface"; - -import { Writable } from "../../typings/Writable"; -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataAliasType } from "./MetadataAliasType"; -import { MetadataArrayType } from "./MetadataArrayType"; -import { MetadataObjectType } from "./MetadataObjectType"; -import { MetadataProperty } from "./MetadataProperty"; -import { MetadataSchema } from "./MetadataSchema"; -import { MetadataTupleType } from "./MetadataTupleType"; - -export class MetadataComponents { - public readonly aliases: MetadataAliasType[]; - public readonly objects: MetadataObjectType[]; - public readonly arrays: MetadataArrayType[]; - public readonly tuples: MetadataTupleType[]; - public readonly dictionary: IMetadataDictionary; - - private constructor(props: ClassProperties) { - this.aliases = props.aliases; - this.objects = props.objects; - this.arrays = props.arrays; - this.tuples = props.tuples; - this.dictionary = props.dictionary; - } - - public static from(json: IMetadataComponents): MetadataComponents { - // INITIALIZE COMPONENTS - const dictionary: IMetadataDictionary = { - objects: new Map( - json.objects.map((obj) => [ - obj.name, - MetadataObjectType._From_without_properties(obj), - ]), - ), - aliases: new Map( - json.aliases.map((alias) => [ - alias.name, - MetadataAliasType._From_without_value(alias), - ]), - ), - arrays: new Map( - json.arrays.map((arr) => [ - arr.name, - MetadataArrayType._From_without_value(arr), - ]), - ), - tuples: new Map( - json.tuples.map((tpl) => [ - tpl.name, - MetadataTupleType._From_without_elements(tpl), - ]), - ), - }; - - // CONSTRUCT METADATA OF THEM - for (const obj of json.objects) - dictionary.objects - .get(obj.name)! - .properties.push( - ...obj.properties.map((prop) => - MetadataProperty.from(prop, dictionary), - ), - ); - for (const alias of json.aliases) - Writable(dictionary.aliases.get(alias.name)!).value = MetadataSchema.from( - alias.value, - dictionary, - ); - for (const array of json.arrays) - Writable(dictionary.arrays.get(array.name)!).value = MetadataSchema.from( - array.value, - dictionary, - ); - for (const tuple of json.tuples) - Writable(dictionary.tuples.get(tuple.name)!).elements = - tuple.elements.map((elem) => MetadataSchema.from(elem, dictionary)); - - // FINALIZE - return new MetadataComponents({ - aliases: [...dictionary.aliases.values()], - objects: [...dictionary.objects.values()], - arrays: [...dictionary.arrays.values()], - tuples: [...dictionary.tuples.values()], - dictionary, - }); - } - - public toJSON(): IMetadataComponents { - return { - aliases: this.aliases.map((alias) => alias.toJSON()), - objects: this.objects.map((object) => object.toJSON()), - arrays: this.arrays.map((array) => array.toJSON()), - tuples: this.tuples.map((tuple) => tuple.toJSON()), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataConstant.ts b/packages/core/src/schemas/metadata/MetadataConstant.ts deleted file mode 100644 index 6bae5b3c564..00000000000 --- a/packages/core/src/schemas/metadata/MetadataConstant.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ClassProperties, IMetadataSchema } from "@typia/interface"; - -import { MetadataConstantValue } from "./MetadataConstantValue"; - -export class MetadataConstant { - public readonly type: "boolean" | "bigint" | "number" | "string"; - public readonly values: MetadataConstantValue[]; - - private constructor(props: ClassProperties) { - this.type = props.type; - this.values = props.values.map(MetadataConstantValue.create); - } - - public static create( - props: ClassProperties, - ): MetadataConstant { - return new MetadataConstant(props); - } - - public static from(json: IMetadataSchema.IConstant): MetadataConstant { - return MetadataConstant.create({ - type: json.type, - values: json.values.map(MetadataConstantValue.from), - }); - } - - public toJSON(): IMetadataSchema.IConstant { - return { - type: this.type, - values: this.values.map((value) => value.toJSON()), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataConstantValue.ts b/packages/core/src/schemas/metadata/MetadataConstantValue.ts deleted file mode 100644 index 2cfeec7f575..00000000000 --- a/packages/core/src/schemas/metadata/MetadataConstantValue.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - ClassProperties, - IJsDocTagInfo, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -export class MetadataConstantValue { - public readonly value: boolean | bigint | number | string; - public tags: IMetadataTypeTag[][]; - public readonly description?: string | null; - public readonly jsDocTags?: IJsDocTagInfo[]; - private name_?: string; - - private constructor(props: ClassProperties) { - this.value = props.value; - this.tags = props.tags; - this.description = props.description; - this.jsDocTags = props.jsDocTags; - } - - public static create( - props: ClassProperties, - ): MetadataConstantValue { - return new MetadataConstantValue(props); - } - - public static from( - json: IMetadataSchema.IConstant.IValue, - ): MetadataConstantValue { - return MetadataConstantValue.create({ - value: typeof json.value === "bigint" ? BigInt(json.value) : json.value, - tags: json.tags, - description: json.description, - jsDocTags: json.jsDocTags, - }); - } - - public getName(): string { - return (this.name_ ??= getName(this)); - } - - public toJSON(): IMetadataSchema.IConstant.IValue { - return { - value: - typeof this.value === "bigint" ? this.value.toString() : this.value, - tags: this.tags, - description: this.description, - jsDocTags: this.jsDocTags, - }; - } -} - -const getName = (obj: MetadataConstantValue): string => { - const base: string = - typeof obj.value === "string" - ? JSON.stringify(obj.value) - : obj.value.toString(); - if (!obj.tags?.length) return base; - const rows: string[] = obj.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${base} & (${rows.join(" | ")}))`; -}; diff --git a/packages/core/src/schemas/metadata/MetadataEscaped.ts b/packages/core/src/schemas/metadata/MetadataEscaped.ts deleted file mode 100644 index 1b04d7617dd..00000000000 --- a/packages/core/src/schemas/metadata/MetadataEscaped.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ClassProperties, IMetadataSchema } from "@typia/interface"; - -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataEscaped { - public readonly original: MetadataSchema; - public readonly returns: MetadataSchema; - - private constructor(props: ClassProperties) { - this.original = props.original; - this.returns = props.returns; - } - - /** @internal */ - public static from( - props: IMetadataSchema.IEscaped, - dict: IMetadataDictionary, - ): MetadataEscaped { - return MetadataEscaped.create({ - original: MetadataSchema.from(props.original, dict), - returns: MetadataSchema.from(props.returns, dict), - }); - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataEscaped { - return new MetadataEscaped(props); - } - - public getName(): string { - return this.returns.getName(); - } - - public toJSON(): IMetadataSchema.IEscaped { - return { - original: this.original.toJSON(), - returns: this.returns.toJSON(), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataFunction.ts b/packages/core/src/schemas/metadata/MetadataFunction.ts deleted file mode 100644 index 27726d27d20..00000000000 --- a/packages/core/src/schemas/metadata/MetadataFunction.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ClassProperties, IMetadataSchema } from "@typia/interface"; - -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataParameter } from "./MetadataParameter"; -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataFunction { - public parameters: MetadataParameter[]; - public output: MetadataSchema; - public async: boolean; - - private constructor(props: ClassProperties) { - this.parameters = props.parameters; - this.output = props.output; - this.async = props.async; - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataFunction { - return new MetadataFunction(props); - } - - public static from( - json: IMetadataSchema.IFunction, - dict: IMetadataDictionary, - ): MetadataFunction { - return MetadataFunction.create({ - parameters: json.parameters.map((p) => MetadataParameter.from(p, dict)), - output: MetadataSchema.from(json.output, dict), - async: json.async, - }); - } - - public toJSON(): IMetadataSchema.IFunction { - return { - parameters: this.parameters.map((p) => p.toJSON()), - output: this.output.toJSON(), - async: this.async, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataMap.ts b/packages/core/src/schemas/metadata/MetadataMap.ts deleted file mode 100644 index 6aa97d1c5d8..00000000000 --- a/packages/core/src/schemas/metadata/MetadataMap.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataMap { - public readonly key: MetadataSchema; - public readonly value: MetadataSchema; - public readonly tags: IMetadataTypeTag[][]; - private name_?: string; - - private constructor(props: ClassProperties) { - this.key = props.key; - this.value = props.value; - this.tags = props.tags; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataMap { - return new MetadataMap(props); - } - - public getName(): string { - return (this.name_ ??= (() => { - const symbol: string = `Map<${this.key.getName()}, ${this.value.getName()}>`; - if (this.tags.length === 0) return symbol; - else if (this.tags.length === 1) { - const str: string = [symbol, ...this.tags[0]!.map((t) => t.name)].join( - " & ", - ); - return `(${str})`; - } - const rows: string[] = this.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${symbol} & (${rows.join(" | ")}))`; - })()); - } - - public toJSON(): IMetadataSchema.IMap { - return { - key: this.key.toJSON(), - value: this.value.toJSON(), - tags: this.tags, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataNative.ts b/packages/core/src/schemas/metadata/MetadataNative.ts deleted file mode 100644 index 5f1170fe051..00000000000 --- a/packages/core/src/schemas/metadata/MetadataNative.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -export class MetadataNative { - public readonly name: string; - public readonly tags: IMetadataTypeTag[][]; - private typeName_?: string; - - private constructor(props: ClassProperties) { - this.name = props.name; - this.tags = props.tags; - } - - public static create(props: ClassProperties): MetadataNative { - return new MetadataNative(props); - } - - public getName(): string { - return (this.typeName_ ??= (() => { - if (this.tags.length === 0) return this.name; - else if (this.tags.length === 1) { - const str: string = [ - this.name, - ...this.tags[0]!.map((t) => t.name), - ].join(" & "); - return `(${str})`; - } - const rows: string[] = this.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${this.name} & (${rows.join(" | ")}))`; - })()); - } - - public toJSON(): IMetadataSchema.IReference { - return { - name: this.name, - tags: this.tags, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataObject.ts b/packages/core/src/schemas/metadata/MetadataObject.ts deleted file mode 100644 index 88c177a9a13..00000000000 --- a/packages/core/src/schemas/metadata/MetadataObject.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { MetadataObjectType } from "./MetadataObjectType"; - -export class MetadataObject { - public readonly type: MetadataObjectType; - public readonly tags: IMetadataTypeTag[][]; - private name_?: string; - - private constructor(props: ClassProperties) { - this.type = props.type; - this.tags = props.tags; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataObject { - return new MetadataObject(props); - } - - public getName(): string { - return (this.name_ ??= (() => { - if (this.tags.length === 0) return this.type.name; - else if (this.tags.length === 1) { - const str: string = [ - this.type.name, - ...this.tags[0]!.map((t) => t.name), - ].join(" & "); - return `(${str})`; - } - const rows: string[] = this.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${this.type.name} & (${rows.join(" | ")}))`; - })()); - } - - public toJSON(): IMetadataSchema.IReference { - return { - name: this.type.name, - tags: this.tags.map((row) => row.slice()), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataObjectType.ts b/packages/core/src/schemas/metadata/MetadataObjectType.ts deleted file mode 100644 index f84eb1f2bfd..00000000000 --- a/packages/core/src/schemas/metadata/MetadataObjectType.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - ClassProperties, - IJsDocTagInfo, - IMetadataSchema, -} from "@typia/interface"; - -import { MetadataProperty } from "./MetadataProperty"; - -export class MetadataObjectType { - public readonly name: string; - public readonly properties: Array; - public readonly description: string | undefined; - public readonly jsDocTags: IJsDocTagInfo[]; - - public readonly index: number; - public validated: boolean; - public recursive: boolean; - public nullables: boolean[] = []; - - /** @internal */ - public tagged_: boolean = false; - - /** @internal */ - private literal_?: boolean; - - /* ----------------------------------------------------------- - CONSTRUCTORS - ----------------------------------------------------------- */ - private constructor( - props: Omit, "tagged_">, - ) { - this.name = props.name; - this.properties = props.properties; - this.description = props.description; - this.jsDocTags = props.jsDocTags; - - this.index = props.index; - this.validated = props.validated; - this.recursive = props.recursive; - this.nullables = props.nullables.slice(); - - this.tagged_ = false; - } - - /** @internal */ - public static create( - props: Omit, "tagged_">, - ) { - return new MetadataObjectType(props); - } - - /** @internal */ - public static _From_without_properties( - obj: IMetadataSchema.IObjectType, - ): MetadataObjectType { - return MetadataObjectType.create({ - name: obj.name, - properties: [], - description: obj.description, - jsDocTags: obj.jsDocTags, - - index: obj.index, - validated: false, - recursive: obj.recursive, - nullables: obj.nullables.slice(), - }); - } - - public isPlain(level: number = 0): boolean { - return ( - this.recursive === false && - this.properties.length < 10 && - this.properties.every( - (property) => - property.key.isSoleLiteral() && - property.value.size() === 1 && - property.value.isRequired() === true && - property.value.nullable === false && - (property.value.atomics.length === 1 || - (level < 1 && - property.value.objects.length === 1 && - property.value.objects[0]!.type.isPlain(level + 1))), - ) - ); - } - - public isLiteral(): boolean { - return (this.literal_ ??= (() => { - if (this.recursive === true) return false; - return ( - this.name === "__type" || - this.name === "__object" || - this.name.startsWith("__type.") || - this.name.startsWith("__object.") || - this.name.includes("readonly [") - ); - })()); - } - - public toJSON(): IMetadataSchema.IObjectType { - return { - name: this.name, - properties: this.properties.map((property) => property.toJSON()), - description: this.description, - jsDocTags: this.jsDocTags, - - index: this.index, - recursive: this.recursive, - nullables: this.nullables.slice(), - }; - } -} - -/** @internal */ -export namespace MetadataObjectType { - export const intersects = ( - x: MetadataObjectType, - y: MetadataObjectType, - ): boolean => - x.properties.some( - (prop) => - y.properties.find( - (oppo) => prop.key.getName() === oppo.key.getName(), - ) !== undefined, - ); - - export const covers = ( - x: MetadataObjectType, - y: MetadataObjectType, - ): boolean => - x.properties.length >= y.properties.length && - x.properties.every( - (prop) => - y.properties.find( - (oppo) => prop.key.getName() === oppo.key.getName(), - ) !== undefined, - ); -} diff --git a/packages/core/src/schemas/metadata/MetadataParameter.ts b/packages/core/src/schemas/metadata/MetadataParameter.ts deleted file mode 100644 index 1249f33085b..00000000000 --- a/packages/core/src/schemas/metadata/MetadataParameter.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - ClassProperties, - IJsDocTagInfo, - IMetadataSchema, -} from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataParameter { - public name: string; - public type: MetadataSchema; - public description: string | null; - public jsDocTags: IJsDocTagInfo[]; - public tsType?: ts.Type; - - private constructor(props: ClassProperties) { - this.name = props.name; - this.type = props.type; - this.description = props.description; - this.jsDocTags = props.jsDocTags; - this.tsType = props.tsType; - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataParameter { - return new MetadataParameter(props); - } - - public static from( - json: IMetadataSchema.IParameter, - dict: IMetadataDictionary, - ): MetadataParameter { - return MetadataParameter.create({ - name: json.name, - type: MetadataSchema.from(json.type, dict), - description: json.description, - jsDocTags: json.jsDocTags, - }); - } - - public toJSON(): IMetadataSchema.IParameter { - return { - name: this.name, - type: this.type.toJSON(), - description: this.description, - jsDocTags: this.jsDocTags, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataProperty.ts b/packages/core/src/schemas/metadata/MetadataProperty.ts deleted file mode 100644 index c48c272c67d..00000000000 --- a/packages/core/src/schemas/metadata/MetadataProperty.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - ClassProperties, - IJsDocTagInfo, - IMetadataSchema, -} from "@typia/interface"; - -import { IProtobufProperty } from "../protobuf/IProtobufProperty"; -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataProperty { - public readonly key: MetadataSchema; - public readonly value: MetadataSchema; - public readonly description: string | null; - public readonly jsDocTags: IJsDocTagInfo[]; - public readonly mutability?: "readonly" | null | undefined; - - public of_protobuf_?: IProtobufProperty; - - /* ----------------------------------------------------------- - CONSTRUCTORS - ----------------------------------------------------------- */ - private constructor(props: ClassProperties) { - this.key = props.key; - this.value = props.value; - this.description = props.description; - this.jsDocTags = props.jsDocTags; - this.mutability = props.mutability; - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataProperty { - return new MetadataProperty(props); - } - - /** @internal */ - public static from( - property: IMetadataSchema.IProperty, - dict: IMetadataDictionary, - ) { - return MetadataProperty.create({ - key: MetadataSchema.from(property.key, dict), - value: MetadataSchema.from(property.value, dict), - description: property.description, - jsDocTags: property.jsDocTags.slice(), - mutability: property.mutability, - }); - } - - public toJSON(): IMetadataSchema.IProperty { - return { - key: this.key.toJSON(), - value: this.value.toJSON(), - description: this.description, - jsDocTags: this.jsDocTags, - mutability: this.mutability, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataSchema.ts b/packages/core/src/schemas/metadata/MetadataSchema.ts deleted file mode 100644 index fff36cf06ba..00000000000 --- a/packages/core/src/schemas/metadata/MetadataSchema.ts +++ /dev/null @@ -1,701 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; -import { ArrayUtil } from "@typia/utils"; - -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataAlias } from "./MetadataAlias"; -import { MetadataArray } from "./MetadataArray"; -import { MetadataAtomic } from "./MetadataAtomic"; -import { MetadataConstant } from "./MetadataConstant"; -import { MetadataEscaped } from "./MetadataEscaped"; -import { MetadataFunction } from "./MetadataFunction"; -import { MetadataMap } from "./MetadataMap"; -import { MetadataNative } from "./MetadataNative"; -import { MetadataObject } from "./MetadataObject"; -import { MetadataObjectType } from "./MetadataObjectType"; -import { MetadataSet } from "./MetadataSet"; -import { MetadataTemplate } from "./MetadataTemplate"; -import { MetadataTuple } from "./MetadataTuple"; - -/** - * TypeScript type metadata representation. - * - * `MetadataSchema` captures full TypeScript type information at compile-time - * for runtime validation and code generation. Used internally by typia - * transformer to analyze `typia.*()` calls. - * - * Represents union types as arrays of type categories: - * - * - Primitives: {@link atomics} (boolean, bigint, number, string) - * - Literals: {@link constants} (e.g., `"hello"`, `42`) - * - Templates: {@link templates} (template literal types) - * - Collections: {@link arrays}, {@link tuples}, {@link sets}, {@link maps} - * - Objects: {@link objects} (named object types) - * - Aliases: {@link aliases} (type aliases) - * - Natives: {@link natives} (Date, Uint8Array, etc.) - * - * Modifiers: - * - * - {@link required}: Not `undefined` - * - {@link optional}: Has `?` modifier - * - {@link nullable}: Includes `null` - * - {@link any}: Is `any` type - * - * @author Jeongho Nam - https://github.com/samchon - */ -export class MetadataSchema { - public any: boolean; - public required: boolean; - public optional: boolean; - public nullable: boolean; - - public escaped: MetadataEscaped | null; - public atomics: MetadataAtomic[]; - public constants: MetadataConstant[]; - public templates: MetadataTemplate[]; - - public rest: MetadataSchema | null; - public aliases: MetadataAlias[]; - public arrays: MetadataArray[]; - public tuples: MetadataTuple[]; - public objects: MetadataObject[]; - public functions: MetadataFunction[]; - - public natives: MetadataNative[]; - public sets: MetadataSet[]; - public maps: MetadataMap[]; - - /** @internal */ private name_?: string; - /** @internal */ private parent_resolved_: boolean = false; - /** @internal */ public union_index?: number; - /** @internal */ public fixed_?: number | null; - /** @internal */ public boolean_literal_intersected_?: boolean; - /** @internal */ private is_sequence_?: boolean; - - /* ----------------------------------------------------------- - CONSTRUCTORS - ----------------------------------------------------------- */ - private constructor(props: ClassProperties) { - this.any = props.any; - this.required = props.required; - this.optional = props.optional; - this.nullable = props.nullable; - this.functions = props.functions; - - this.escaped = props.escaped; - this.atomics = props.atomics; - this.constants = props.constants; - this.templates = props.templates; - - this.rest = props.rest; - this.arrays = props.arrays; - this.tuples = props.tuples; - this.objects = props.objects; - this.aliases = props.aliases; - - this.natives = props.natives; - this.sets = props.sets; - this.maps = props.maps; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataSchema { - return new MetadataSchema(props); - } - - /** @internal */ - public static initialize(parentResolved: boolean = false): MetadataSchema { - const meta: MetadataSchema = MetadataSchema.create({ - any: false, - nullable: false, - required: true, - optional: false, - - escaped: null, - constants: [], - atomics: [], - templates: [], - arrays: [], - tuples: [], - objects: [], - aliases: [], - functions: [], - - rest: null, - natives: [], - sets: [], - maps: [], - }); - meta.parent_resolved_ = parentResolved; - return meta; - } - - public toJSON(): IMetadataSchema { - return { - any: this.any, - required: this.required, - optional: this.optional, - nullable: this.nullable, - functions: this.functions.map((f) => f.toJSON()), - - atomics: this.atomics.map((a) => a.toJSON()), - constants: this.constants.map((c) => c.toJSON()), - templates: this.templates.map((tpl) => tpl.toJSON()), - escaped: this.escaped ? this.escaped.toJSON() : null, - - rest: this.rest ? this.rest.toJSON() : null, - arrays: this.arrays.map((array) => array.toJSON()), - tuples: this.tuples.map((tuple) => tuple.toJSON()), - objects: this.objects.map((obj) => obj.toJSON()), - aliases: this.aliases.map((alias) => alias.toJSON()), - - natives: this.natives.map((native) => native.toJSON()), - sets: this.sets.map((set) => set.toJSON()), - maps: this.maps.map((map) => map.toJSON()), - }; - } - - public static from( - meta: IMetadataSchema, - dict: IMetadataDictionary, - ): MetadataSchema { - return MetadataSchema.create({ - any: meta.any, - required: meta.required, - optional: meta.optional, - nullable: meta.nullable, - functions: meta.functions.map((f) => MetadataFunction.from(f, dict)), - - constants: meta.constants.map(MetadataConstant.from), - atomics: meta.atomics.map(MetadataAtomic.from), - templates: meta.templates.map((tpl) => MetadataTemplate.from(tpl, dict)), - escaped: meta.escaped ? MetadataEscaped.from(meta.escaped, dict) : null, - - rest: meta.rest ? this.from(meta.rest, dict) : null, - arrays: meta.arrays.map((ref) => { - const type = dict.arrays.get(ref.name); - if (type === undefined) - throw new RangeError( - `Error on Metadata.from(): failed to find array "${ref.name}".`, - ); - return MetadataArray.create({ - type, - tags: ref.tags.map((row) => row.slice()), - }); - }), - tuples: meta.tuples.map((t) => { - const type = dict.tuples.get(t.name); - if (type === undefined) - throw new RangeError( - `Error on Metadata.from(): failed to find tuple "${t.name}".`, - ); - return MetadataTuple.create({ - type, - tags: t.tags.map((r) => r.slice()), - }); - }), - objects: meta.objects.map((obj) => { - const found = dict.objects.get(obj.name); - if (found === undefined) - throw new RangeError( - `Error on Metadata.from(): failed to find object "${name}".`, - ); - return MetadataObject.create({ - type: found, - tags: obj.tags.map((r) => r.slice()), - }); - }), - aliases: meta.aliases.map((alias) => { - const type = dict.aliases.get(alias.name); - if (type === undefined) - throw new RangeError( - `Error on Metadata.from(): failed to find alias "${alias}".`, - ); - return MetadataAlias.create({ - type, - tags: alias.tags.map((r) => r.slice()), - }); - }), - natives: meta.natives.map((native) => MetadataNative.create(native)), - sets: meta.sets.map((set) => - MetadataSet.create({ - value: MetadataSchema.from(set.value, dict), - tags: set.tags.map((r) => r.slice()), - }), - ), - maps: meta.maps.map((map) => - MetadataMap.create({ - key: this.from(map.key, dict), - value: this.from(map.value, dict), - tags: map.tags.map((r) => r.slice()), - }), - ), - }); - } - - /* ----------------------------------------------------------- - ACCESSORS - ----------------------------------------------------------- */ - public getName(): string { - return (this.name_ ??= getName(this)); - } - - public empty(): boolean { - return this.bucket() === 0 || this.size() === 0; - } - - public size(): number { - return ( - (this.any ? 1 : 0) + - (this.escaped ? 1 : 0) + - (this.rest ? this.rest.size() : 0) + - this.templates.length + - this.atomics.length + - this.constants.map((c) => c.values.length).reduce((x, y) => x + y, 0) + - this.arrays.length + - this.tuples.length + - this.natives.length + - this.maps.length + - this.sets.length + - this.objects.length + - this.functions.length + - this.aliases.length - ); - } - - public bucket(): number { - return ( - (this.any ? 1 : 0) + - (this.escaped ? 1 : 0) + - (this.templates.length ? 1 : 0) + - (this.atomics.length ? 1 : 0) + - (this.constants.length ? 1 : 0) + - (this.rest ? this.rest.size() : 0) + - (this.arrays.length ? 1 : 0) + - (this.tuples.length ? 1 : 0) + - (this.natives.length ? 1 : 0) + - (this.sets.length ? 1 : 0) + - (this.maps.length ? 1 : 0) + - (this.objects.length ? 1 : 0) + - (this.functions.length ? 1 : 0) + - (this.aliases.length ? 1 : 0) - ); - } - - /** @internal */ - public isSequence(): boolean { - return (this.is_sequence_ ??= (() => { - const exists = (tags: IMetadataTypeTag[][]): boolean => - tags.some((row) => { - const sequence = row.find( - (t) => - t.kind === "sequence" && - typeof (t.schema as any)?.["x-protobuf-sequence"] === "number", - ); - if (sequence === undefined) return false; - const value: number = Number( - (sequence.schema as any)["x-protobuf-sequence"], - ); - return !Number.isNaN(value); - }); - return ( - this.atomics.some((atomic) => exists(atomic.tags)) && - this.constants.some((c) => - c.values.some((v) => exists(v.tags ?? [])), - ) && - this.templates.some((tpl) => exists(tpl.tags)) && - this.arrays.some((array) => exists(array.tags)) && - this.objects.some((object) => exists(object.tags)) && - this.natives.some( - (native) => native.name === "Uint8Array" && exists(native.tags), - ) - ); - })()); - } - - public isConstant(): boolean { - return this.bucket() === (this.constants.length ? 1 : 0); - } - - public isRequired(): boolean { - return this.required === true && this.optional === false; - } - - /** @internal */ - public isUnionBucket(): boolean { - const size: number = this.bucket(); - const emended: number = - !!this.atomics.length && !!this.constants.length ? size - 1 : size; - return emended > 1; - } - - /** @internal */ - public getSoleLiteral(): string | null { - if ( - this.size() === 1 && - this.constants.length === 1 && - this.constants[0]!.type === "string" && - this.constants[0]!.values.length === 1 - ) - return this.constants[0]!.values[0]!.value as string; - else return null; - } - - public isSoleLiteral(): boolean { - return this.getSoleLiteral() !== null; - } - - /** @internal */ - public isParentResolved(): boolean { - return this.parent_resolved_; - } -} -export namespace MetadataSchema { - export const intersects = (x: MetadataSchema, y: MetadataSchema): boolean => { - // CHECK ANY & OPTIONAL - if (x.any || y.any) return true; - if (x.isRequired() === false && false === y.isRequired()) return true; - if (x.nullable === true && true === y.nullable) return true; - if (!!x.functions.length && !!y.functions.length === true) return true; - - //---- - // INSTANCES - //---- - // ARRAYS - if (x.arrays.length && y.arrays.length) return true; - if (x.tuples.length && y.tuples.length) return true; - if (x.objects.length && y.objects.length) return true; - if (x.aliases.length && y.aliases.length) return true; - - // NATIVES - if (x.natives.length && y.natives.length) - if (x.natives.some((xn) => y.natives.some((yn) => xn === yn))) - return true; - - // ESCAPED - if (x.escaped && y.escaped) - return ( - intersects(x.escaped.original, y.escaped.original) || - intersects(x.escaped.returns, y.escaped.returns) - ); - - //---- - // VALUES - //---- - // ATOMICS - for (const atomic of x.atomics) { - if (y.atomics.some((ya) => atomic.type === ya.type)) return true; - if (y.constants.some((yc) => atomic.type === yc.type)) return true; - } - - // CONSTANTS - for (const constant of x.constants) { - const atomic: MetadataAtomic | undefined = y.atomics.find( - (elem) => elem.type === constant.type, - ); - if (atomic !== undefined) return true; - - const opposite: MetadataConstant | undefined = y.constants.find( - (elem) => elem.type === constant.type, - ); - if (opposite === undefined) continue; - - const values: Set = new Set([ - ...constant.values.map((e) => e.value), - ...opposite.values.map((e) => e.value), - ]); - if (values.size !== constant.values.length + opposite.values.length) - return true; - } - - // TEMPLATES - if (!!x.templates.length && y.atomics.some((ya) => ya.type === "string")) - return true; - else if ( - !!y.templates.length && - x.atomics.some((xa) => xa.type === "string") - ) - return true; - return false; - }; - - export const covers = ( - x: MetadataSchema, - y: MetadataSchema, - level: number = 0, - escaped: boolean = false, - ): boolean => { - // CHECK ANY - if (x === y) return false; - else if (x.any) return true; - else if (y.any) return false; - - if (escaped === false) { - if (x.escaped === null && y.escaped !== null) return false; - else if ( - x.escaped !== null && - y.escaped !== null && - (!covers(x.escaped.original, y.escaped.original, level, true) || - !covers(x.escaped.returns, y.escaped.returns, level, true)) - ) - return false; - } - - //---- - // INSTANCES - //---- - if (level === 0) { - // ARRAYS - for (const ya of y.arrays) - if ( - !x.arrays.some((xa) => - covers(xa.type.value, ya.type.value, level + 1), - ) - ) { - return false; - } - - // TUPLES - for (const yt of y.tuples) - if ( - yt.type.elements.length !== 0 && - x.tuples.some( - (xt) => - xt.type.elements.length >= yt.type.elements.length && - xt.type.elements - .slice(yt.type.elements.length) - .every((xv, i) => covers(xv, yt.type.elements[i]!, level + 1)), - ) === false - ) - return false; - } - - // OBJECTS - for (const yo of y.objects) - if ( - x.objects.some((xo) => MetadataObjectType.covers(xo.type, yo.type)) === - false - ) - return false; - - // ALIASES - for (const yd of y.aliases) - if (x.aliases.some((xd) => xd.type.name === yd.type.name) === false) - return false; - - // NATIVES - for (const yn of y.natives) - if (x.natives.some((xn) => xn === yn) === false) return false; - - // SETS - for (const ys of y.sets) - if (x.sets.some((xs) => covers(xs.value, ys.value)) === false) - return false; - - //---- - // VALUES - //---- - // ATOMICS - if ( - y.atomics.some( - (ya) => x.atomics.some((xa) => xa.type === ya.type) === false, - ) - ) - return false; - - // CONSTANTS - for (const yc of y.constants) { - if (x.atomics.some((atom) => yc.type === atom.type)) continue; - const xc: MetadataConstant | undefined = x.constants.find( - (elem) => elem.type === yc.type, - ); - if (xc === undefined) return false; - else if ( - (yc.values.map((e) => e.value) as number[]).some( - (yv) => xc.values.includes(yv as never) === false, - ) - ) - return false; - } - - // FUNCTIONAL - if (!!x.functions.length === false && !!y.functions.length) return false; - - // SUCCESS - return true; - }; - - /** @internal */ - export const merge = ( - x: MetadataSchema, - y: MetadataSchema, - ): MetadataSchema => { - const output: MetadataSchema = MetadataSchema.create({ - any: x.any || y.any, - nullable: x.nullable || y.nullable, - required: x.required && y.required, - optional: x.optional || y.optional, - functions: x.functions.length ? x.functions : y.functions, // @todo - escaped: - x.escaped !== null && y.escaped !== null - ? MetadataEscaped.create({ - original: merge(x.escaped.original, y.escaped.original), - returns: merge(x.escaped.returns, y.escaped.returns), - }) - : (x.escaped ?? y.escaped), - atomics: mergeTaggedTypes({ - container: x.atomics, - equals: (x, y) => x.type === y.type, - getter: (x) => x.tags, - })(y.atomics), - constants: [...x.constants], - templates: x.templates.slice(), - rest: - x.rest !== null && y.rest !== null - ? merge(x.rest, y.rest) - : (x.rest ?? y.rest), - arrays: mergeTaggedTypes({ - container: x.arrays, - equals: (x, y) => x.type.name === y.type.name, - getter: (x) => x.tags, - })(y.arrays), - tuples: mergeTaggedTypes({ - container: x.tuples, - equals: (x, y) => x.type.name === y.type.name, - getter: (x) => x.tags, - })(y.tuples), - objects: mergeTaggedTypes({ - container: x.objects, - equals: (x, y) => x.type.name === y.type.name, - getter: (x) => x.tags, - })(y.objects), - aliases: mergeTaggedTypes({ - container: x.aliases, - equals: (x, y) => x.type.name === y.type.name, - getter: (x) => x.tags, - })(y.aliases), - natives: mergeTaggedTypes({ - container: x.natives, - equals: (x, y) => x.name === y.name, - getter: (x) => x.tags, - })(y.natives), - sets: mergeTaggedTypes({ - container: x.sets, - equals: (x, y) => x.value.getName() === y.value.getName(), - getter: (x) => x.tags, - })(y.sets), - maps: mergeTaggedTypes({ - container: x.maps, - equals: (x, y) => - x.key.getName() === y.key.getName() && - x.value.getName() === y.value.getName(), - getter: (x) => x.tags, - })(y.maps), - }); - for (const constant of y.constants) { - const target: MetadataConstant = ArrayUtil.take( - output.constants, - (elem) => elem.type === constant.type, - () => - MetadataConstant.create({ - type: constant.type, - values: [], - }), - ); - for (const value of constant.values) - ArrayUtil.add(target.values, value, (a, b) => a.value === b.value); - } - return output; - }; - - /** @internal */ - export const unalias = (w: MetadataSchema) => { - const visited: Set = new Set(); - while ( - w.size() === 1 && - w.nullable === false && - w.isRequired() === true && - w.aliases.length === 1 - ) { - if (visited.has(w)) break; - w = w.aliases[0]!.type.value; - visited.add(w); - } - return w; - }; -} - -const getName = (metadata: MetadataSchema): string => { - if (metadata.any === true) return "any"; - - const elements: string[] = []; - - // OPTIONAL - if (metadata.nullable === true) elements.push("null"); - if (metadata.isRequired() === false) elements.push("undefined"); - - // ATOMIC - for (const atom of metadata.atomics) { - elements.push(atom.getName()); - } - for (const constant of metadata.constants) - for (const value of constant.values) elements.push(value.getName()); - for (const template of metadata.templates) elements.push(template.getName()); - - // NATIVES - for (const native of metadata.natives) elements.push(native.getName()); - for (const set of metadata.sets) elements.push(set.getName()); - for (const map of metadata.maps) elements.push(map.getName()); - - // INSTANCES - if (metadata.rest !== null) elements.push(`...${metadata.rest.getName()}`); - for (const tuple of metadata.tuples) elements.push(tuple.type.name); - for (const array of metadata.arrays) elements.push(array.getName()); - for (const object of metadata.objects) elements.push(object.getName()); - for (const alias of metadata.aliases) elements.push(alias.getName()); - if (metadata.escaped !== null) elements.push(metadata.escaped.getName()); - - // RETURNS - if (elements.length === 0) return "unknown"; - else if (elements.length === 1) return elements[0]!; - - elements.sort(); - return `(${elements.join(" | ")})`; -}; - -const mergeTaggedTypes = - (props: { - container: T[]; - equals: (x: T, y: T) => boolean; - getter: (x: T) => IMetadataTypeTag[][]; - }) => - (opposite: T[]) => { - const output: T[] = [...props.container]; - for (const elem of opposite) { - const equal = props.container.find((x) => props.equals(x, elem)); - if (equal === undefined) { - output.push(elem); - continue; - } - - const matrix: string[][] = props - .getter(equal) - .map((tags) => tags.map((t) => t.name)) - .sort(); - for (const tags of props.getter(elem)) { - const names: string[] = tags.map((t) => t.name).sort(); - if ( - matrix.some( - (m) => - m.length === names.length && m.every((s, i) => s === names[i]), - ) - ) - continue; - props.getter(equal).push(tags); - } - } - return output; - }; diff --git a/packages/core/src/schemas/metadata/MetadataSet.ts b/packages/core/src/schemas/metadata/MetadataSet.ts deleted file mode 100644 index 521d5c24374..00000000000 --- a/packages/core/src/schemas/metadata/MetadataSet.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataSet { - public readonly value: MetadataSchema; - public readonly tags: IMetadataTypeTag[][]; - private name_?: string; - - private constructor(props: ClassProperties) { - this.value = props.value; - this.tags = props.tags; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataSet { - return new MetadataSet(props); - } - - public getName(): string { - return (this.name_ ??= (() => { - const symbol: string = `Set<${this.value.getName()}>`; - if (this.tags.length === 0) return symbol; - else if (this.tags.length === 1) { - const str: string = [symbol, ...this.tags[0]!.map((t) => t.name)].join( - " & ", - ); - return `(${str})`; - } - const rows: string[] = this.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${symbol} & (${rows.join(" | ")}))`; - })()); - } - - public toJSON(): IMetadataSchema.ISet { - return { - value: this.value.toJSON(), - tags: this.tags, - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataTemplate.ts b/packages/core/src/schemas/metadata/MetadataTemplate.ts deleted file mode 100644 index 41c1a0b8ac1..00000000000 --- a/packages/core/src/schemas/metadata/MetadataTemplate.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { IMetadataDictionary } from "./IMetadataDictionary"; -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataTemplate { - public readonly row: MetadataSchema[]; - public readonly tags: IMetadataTypeTag[][]; - - private name_?: string; - - private constructor(props: ClassProperties) { - this.row = props.row.map(MetadataSchema.create); - this.tags = props.tags; - } - - /** @internal */ - public static create( - props: ClassProperties, - ): MetadataTemplate { - return new MetadataTemplate(props); - } - - public static from( - json: IMetadataSchema.ITemplate, - dict: IMetadataDictionary, - ): MetadataTemplate { - return new MetadataTemplate({ - row: json.row.map((m) => MetadataSchema.from(m, dict)), - tags: json.tags, - }); - } - - public getName(): string { - return (this.name_ ??= getName(this)); - } - - /** @internal */ - public getBaseName(): string { - return ( - "`" + - this.row - .map((child) => - child.isConstant() && child.size() === 1 - ? child.constants[0]!.values[0]!.value - : `$\{${child.getName()}\}`, - ) - .join("") - .split("`") - .join("\\`") + - "`" - ); - } - - public toJSON(): IMetadataSchema.ITemplate { - return { - row: this.row.map((m) => m.toJSON()), - tags: this.tags, - }; - } -} - -const getName = (template: MetadataTemplate): string => { - const base: string = template.getBaseName(); - if (!template.tags?.length) return base; - else if (template.tags.length === 1) { - const str: string = [base, ...template.tags[0]!.map((t) => t.name)].join( - " & ", - ); - return `(${str})`; - } - const rows: string[] = template.tags.map((row) => { - const str: string = row.map((t) => t.name).join(" & "); - return row.length === 1 ? str : `(${str})`; - }); - return `(${base} & (${rows.join(" | ")}))`; -}; diff --git a/packages/core/src/schemas/metadata/MetadataTuple.ts b/packages/core/src/schemas/metadata/MetadataTuple.ts deleted file mode 100644 index 4a79c88bf4f..00000000000 --- a/packages/core/src/schemas/metadata/MetadataTuple.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - ClassProperties, - IMetadataSchema, - IMetadataTypeTag, -} from "@typia/interface"; - -import { MetadataTupleType } from "./MetadataTupleType"; - -export class MetadataTuple { - public readonly type: MetadataTupleType; - public readonly tags: IMetadataTypeTag[][]; - - private constructor(props: ClassProperties) { - this.type = props.type; - this.tags = props.tags; - } - - /** @internal */ - public static create(props: ClassProperties): MetadataTuple { - return new MetadataTuple(props); - } - - public toJSON(): IMetadataSchema.IReference { - return { - name: this.type.name, - tags: this.tags.map((row) => row.slice()), - }; - } -} diff --git a/packages/core/src/schemas/metadata/MetadataTupleType.ts b/packages/core/src/schemas/metadata/MetadataTupleType.ts deleted file mode 100644 index 47d426eda87..00000000000 --- a/packages/core/src/schemas/metadata/MetadataTupleType.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ClassProperties, IMetadataSchema } from "@typia/interface"; - -import { MetadataSchema } from "./MetadataSchema"; - -export class MetadataTupleType { - public readonly name: string; - public readonly elements: MetadataSchema[]; - - public readonly index: number | null; - public readonly recursive: boolean; - public readonly nullables: boolean[]; - - /** @internal */ - public of_map?: boolean; - - private constructor(props: ClassProperties) { - this.name = props.name; - this.elements = props.elements; - this.index = props.index; - this.recursive = props.recursive; - this.nullables = props.nullables; - } - - /** @internal */ - public static _From_without_elements( - props: Omit, - ): MetadataTupleType { - return MetadataTupleType.create({ - name: props.name, - index: props.index, - elements: null!, - recursive: props.recursive, - nullables: props.nullables.slice(), - }); - } - - public static create( - props: ClassProperties, - ): MetadataTupleType { - return new MetadataTupleType(props); - } - - public isRest(): boolean { - return ( - this.elements.length > 0 && - this.elements[this.elements.length - 1]!.rest !== null - ); - } - - public toJSON(): IMetadataSchema.ITupleType { - return { - name: this.name, - index: this.index, - elements: this.elements.map((elem) => elem.toJSON()), - recursive: this.recursive, - nullables: this.nullables.slice(), - }; - } -} diff --git a/packages/core/src/schemas/metadata/index.ts b/packages/core/src/schemas/metadata/index.ts deleted file mode 100644 index 342692cb480..00000000000 --- a/packages/core/src/schemas/metadata/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export * from "./IMetadataDictionary"; -export * from "./MetadataAlias"; -export * from "./MetadataAliasType"; -export * from "./MetadataApplication"; -export * from "./MetadataArray"; -export * from "./MetadataArrayType"; -export * from "./MetadataAtomic"; -export * from "./MetadataComponents"; -export * from "./MetadataConstant"; -export * from "./MetadataConstantValue"; -export * from "./MetadataEscaped"; -export * from "./MetadataFunction"; -export * from "./MetadataMap"; -export * from "./MetadataNative"; -export * from "./MetadataObject"; -export * from "./MetadataObjectType"; -export * from "./MetadataParameter"; -export * from "./MetadataProperty"; -export * from "./MetadataSchema"; -export * from "./MetadataSet"; -export * from "./MetadataCollection"; -export * from "./MetadataTemplate"; -export * from "./MetadataTuple"; -export * from "./MetadataTupleType"; diff --git a/packages/core/src/schemas/protobuf/IProtobufProperty.ts b/packages/core/src/schemas/protobuf/IProtobufProperty.ts deleted file mode 100644 index 5fbcebdb307..00000000000 --- a/packages/core/src/schemas/protobuf/IProtobufProperty.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IProtobufPropertyType } from "./IProtobufPropertyType"; - -export interface IProtobufProperty { - fixed: boolean; - union: IProtobufPropertyType[]; -} diff --git a/packages/core/src/schemas/protobuf/IProtobufPropertyType.ts b/packages/core/src/schemas/protobuf/IProtobufPropertyType.ts deleted file mode 100644 index 0edae1ca449..00000000000 --- a/packages/core/src/schemas/protobuf/IProtobufPropertyType.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { IProtobufSchema } from "./IProtobufSchema"; - -export type IProtobufPropertyType = - | IProtobufPropertyType.IByte - | IProtobufPropertyType.IBoolean - | IProtobufPropertyType.IBigint - | IProtobufPropertyType.INumber - | IProtobufPropertyType.IString - | IProtobufPropertyType.IArray - | IProtobufPropertyType.IObject - | IProtobufPropertyType.IMap; -export namespace IProtobufPropertyType { - export interface IByte extends IProtobufSchema.IByte { - index: number; - } - export interface IBoolean extends IProtobufSchema.IBoolean { - index: number; - } - export interface IBigint extends IProtobufSchema.IBigint { - index: number; - } - export interface INumber extends IProtobufSchema.INumber { - index: number; - } - export interface IString extends IProtobufSchema.IString { - index: number; - } - export interface IArray extends IProtobufSchema.IArray { - index: number; - } - export interface IObject extends IProtobufSchema.IObject { - index: number; - } - export interface IMap extends IProtobufSchema.IMap { - index: number; - } -} diff --git a/packages/core/src/schemas/protobuf/IProtobufSchema.ts b/packages/core/src/schemas/protobuf/IProtobufSchema.ts deleted file mode 100644 index c9dd88b3a48..00000000000 --- a/packages/core/src/schemas/protobuf/IProtobufSchema.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { MetadataArrayType } from "../metadata/MetadataArrayType"; -import { MetadataMap } from "../metadata/MetadataMap"; -import { MetadataObjectType } from "../metadata/MetadataObjectType"; - -export type IProtobufSchema = - | IProtobufSchema.IByte - | IProtobufSchema.IBoolean - | IProtobufSchema.IBigint - | IProtobufSchema.INumber - | IProtobufSchema.IString - | IProtobufSchema.IArray - | IProtobufSchema.IObject - | IProtobufSchema.IMap; -export namespace IProtobufSchema { - export interface IByte { - type: "bytes"; - } - export interface IBoolean { - type: "bool"; - } - export interface IBigint { - type: "bigint"; - name: "int64" | "uint64"; - } - export interface INumber { - type: "number"; - name: "int32" | "int64" | "uint32" | "uint64" | "float" | "double"; - } - export interface IString { - type: "string"; - } - export interface IArray { - type: "array"; - array: MetadataArrayType; - value: Exclude; - } - export interface IObject { - type: "object"; - object: MetadataObjectType; - } - export interface IMap { - type: "map"; - map: MetadataMap | MetadataObjectType; - key: - | IProtobufSchema.IBoolean - | IProtobufSchema.INumber - | IProtobufSchema.IString; - value: Exclude; - } -} diff --git a/packages/core/src/schemas/protobuf/index.ts b/packages/core/src/schemas/protobuf/index.ts deleted file mode 100644 index e91bb088ff3..00000000000 --- a/packages/core/src/schemas/protobuf/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./IProtobufProperty"; -export * from "./IProtobufPropertyType"; -export * from "./IProtobufSchema"; diff --git a/packages/core/src/typings/Writable.ts b/packages/core/src/typings/Writable.ts deleted file mode 100644 index 6297934d9dd..00000000000 --- a/packages/core/src/typings/Writable.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ClassProperties } from "@typia/interface"; - -export type Writable = { - -readonly [P in keyof T]: T[P]; -}; - -export function Writable( - elem: T, -): Writable> { - return elem as any; -} diff --git a/packages/core/src/utils/PatternUtil.ts b/packages/core/src/utils/PatternUtil.ts deleted file mode 100644 index 1bf4b2eb382..00000000000 --- a/packages/core/src/utils/PatternUtil.ts +++ /dev/null @@ -1,29 +0,0 @@ -export namespace PatternUtil { - export const fix = (str: string): string => { - const first: number = str.indexOf(STRING); - const last: number = str.lastIndexOf(STRING); - return [ - first === -1 || none("(")(str.slice(0, first)) ? "^" : "", - str, - last === -1 || none(")")(str.slice(last + STRING.length)) ? "$" : "", - ].join(""); - }; - - export const escape = (str: string): string => { - return str.replace(/[|\\/{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); - }; - - export const NUMBER = - "[+-]?" + // optional sign - "\\d+(?:\\.\\d+)?" + // integer or decimal - "(?:[eE][+-]?\\d+)?"; // optional exponent - export const BOOLEAN = "true|false"; - export const STRING = "(.*)"; -} - -const none = - (parenthesis: string) => - (str: string): boolean => { - for (const ch of str) if (ch !== parenthesis) return true; - return false; - }; diff --git a/packages/core/src/utils/ProtobufNameEncoder.ts b/packages/core/src/utils/ProtobufNameEncoder.ts deleted file mode 100644 index bf35bf6bc1b..00000000000 --- a/packages/core/src/utils/ProtobufNameEncoder.ts +++ /dev/null @@ -1,32 +0,0 @@ -export namespace ProtobufNameEncoder { - export const encode = (str: string): string => { - for (const [before, after] of REPLACERS) - str = str.split(before).join(after); - return str; - }; - - export const decode = (str: string): string => { - for (const [before, after] of REPLACERS) - if (after !== "") str = str.split(after).join(before); - return str; - }; -} - -const REPLACERS: [string, string][] = [ - ["$", "_dollar_"], - ["&", "_and_"], - ["|", "_or_"], - ["{", "_blt_"], - ["}", "_bgt_"], - ["<", "_lt_"], - [">", "_gt_"], - ["(", "_lp_"], - [")", "_rp_"], - ["[", "_alt_"], - ["]", "_agt_"], - [",", "_comma_"], - ["`", "_backquote_"], - ["'", "_singlequote_"], - ['"', "_doublequote_"], - [" ", "_space_"], -]; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json deleted file mode 100644 index 31e11708d92..00000000000 --- a/packages/core/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../config/tsconfig.json", - "compilerOptions": { - "outDir": "lib", - "rootDir": "src" - }, - "include": ["src"], -} diff --git a/packages/interface/package.json b/packages/interface/package.json index d2931400c9a..410d29cae09 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -2,18 +2,14 @@ "name": "@typia/interface", "version": "12.0.2", "description": "Superfast runtime validators with only one line", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "src/index.ts", + "types": "src/index.ts", "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "default": "./lib/index.js" - }, + ".": "./src/index.ts", "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && ttsc build", + "build": "rimraf lib && ttsc", "dev": "ttsc --watch", "prepack": "pnpm run build" }, diff --git a/packages/interface/src/metadata/IMetadataTypeTag.ts b/packages/interface/src/metadata/IMetadataTypeTag.ts index d82d94c3bfd..d0a7a0cfc04 100644 --- a/packages/interface/src/metadata/IMetadataTypeTag.ts +++ b/packages/interface/src/metadata/IMetadataTypeTag.ts @@ -1,5 +1,3 @@ -import type ts from "@typescript/native-preview"; - /** * Type constraint tag metadata. * @@ -33,7 +31,4 @@ export interface IMetadataTypeTag { /** JSON schema fragment to merge. */ schema?: object | undefined; - - /** @internal */ - predicate?: (input: ts.Expression) => ts.Expression; } diff --git a/packages/langchain/package.json b/packages/langchain/package.json index 24f4e99da13..20b16e3bcee 100644 --- a/packages/langchain/package.json +++ b/packages/langchain/package.json @@ -8,7 +8,7 @@ "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && ttsc build && rollup -c", + "build": "rimraf lib && ttsc && rollup -c", "dev": "ttsc --watch", "prepack": "pnpm run build" }, diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 96ec66d2127..9c8ee441efd 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -8,7 +8,7 @@ "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && ttsc build && rollup -c", + "build": "rimraf lib && ttsc && rollup -c", "dev": "ttsc --watch", "prepack": "pnpm run build" }, diff --git a/packages/transform/README.md b/packages/transform/README.md deleted file mode 100644 index c5b61a222a2..00000000000 --- a/packages/transform/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# `@typia/transform` - -![Typia Logo](https://typia.io/logo.png) - -[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samchon/typia/blob/master/LICENSE) -[![NPM Version](https://img.shields.io/npm/v/typia.svg)](https://www.npmjs.com/package/typia) -[![NPM Downloads](https://img.shields.io/npm/dm/typia.svg)](https://www.npmjs.com/package/typia) -[![Build Status](https://github.com/samchon/typia/workflows/test/badge.svg)](https://github.com/samchon/typia/actions?query=workflow%3Atest) -[![Guide Documents](https://img.shields.io/badge/Guide-Documents-forestgreen)](https://typia.io/docs/) -[![Gurubase](https://img.shields.io/badge/Gurubase-Document%20Chatbot-006BFF)](https://gurubase.io/g/typia) -[![Discord Badge](https://img.shields.io/badge/discord-samchon-d91965?style=flat&labelColor=5866f2&logo=discord&logoColor=white&link=https://discord.gg/E94XhzrUCZ)](https://discord.gg/E94XhzrUCZ) - -TypeScript transformer plugin for [`typia`](https://github.com/samchon/typia). - -Transforms `typia.*()` function calls into optimized runtime code at compile time by analyzing TypeScript types. - -This is an **internal package** of `typia`. You don't need to install it directly — it is automatically included as a dependency of `typia`. - -## Key Exports - -| Export | Description | -|--------|-------------| -| `transform()` | Typia transformer factory — consumed via `typia/lib/ttsc/plugin` in `compilerOptions.plugins` | -| `TypiaGenerator.build()` | Programmatic build API for generating transformed output files | -| `ImportTransformer` | Rewrites import paths and removes unused typia imports after transformation | diff --git a/packages/transform/native/cmd/ttsc-typia/build.go b/packages/transform/native/cmd/ttsc-typia/build.go index 5b551c43ecb..8ed5996795e 100644 --- a/packages/transform/native/cmd/ttsc-typia/build.go +++ b/packages/transform/native/cmd/ttsc-typia/build.go @@ -60,12 +60,14 @@ func runBuild(args []string) int { return 2 } if len(diags) > 0 { - for _, d := range diags { - fmt.Fprintln(stderr, " -", d.String()) - } + driver.WritePrettyDiagnostics(stderr, diags, cwd) return 2 } defer prog.Close() + if diags := prog.Diagnostics(); len(diags) > 0 { + driver.WritePrettyDiagnostics(stderr, diags, cwd) + return 2 + } rewrites := driver.NewRewriteSet() sites := 0 diff --git a/packages/transform/native/cmd/ttsc-typia/transform.go b/packages/transform/native/cmd/ttsc-typia/transform.go index 1ebe293f707..6cd3cf14951 100644 --- a/packages/transform/native/cmd/ttsc-typia/transform.go +++ b/packages/transform/native/cmd/ttsc-typia/transform.go @@ -52,12 +52,14 @@ func runTransform(args []string) int { return 2 } if len(diags) > 0 { - for _, d := range diags { - fmt.Fprintln(stderr, " -", d.String()) - } + driver.WritePrettyDiagnostics(stderr, diags, cwd) return 2 } defer prog.Close() + if diags := prog.Diagnostics(); len(diags) > 0 { + driver.WritePrettyDiagnostics(stderr, diags, cwd) + return 2 + } absFile := *file if !filepath.IsAbs(absFile) { @@ -73,22 +75,22 @@ func runTransform(args []string) int { } } + target := prog.SourceFile(absFile) + if target == nil { + fmt.Fprintf(stderr, "ttsc-typia transform: source file is not in program: %s\n", absFile) + return 2 + } + var captured []byte - bestScore := 0 capture := func(name, text string, _ *shimcompiler.WriteFileData) error { rel := filepath.ToSlash(name) if !strings.HasSuffix(rel, ".js") { return nil } - score := sharedSourceStemSegments(rel, absFile) - if score == 0 || score < bestScore { - return nil - } captured = []byte(text) - bestScore = score return nil } - _, eDiags, err := prog.EmitAll(rewrites, capture) + _, eDiags, err := prog.EmitFile(rewrites, target, capture) if err != nil { fmt.Fprintf(stderr, "ttsc-typia transform: emit: %v\n", err) return 3 diff --git a/packages/transform/native/go.mod b/packages/transform/native/go.mod index 662432786f7..1121c495f67 100644 --- a/packages/transform/native/go.mod +++ b/packages/transform/native/go.mod @@ -8,6 +8,7 @@ replace ( github.com/microsoft/typescript-go/shim/checker => ../../../toolchain/ttsc/shim/checker github.com/microsoft/typescript-go/shim/compiler => ../../../toolchain/ttsc/shim/compiler github.com/microsoft/typescript-go/shim/core => ../../../toolchain/ttsc/shim/core + github.com/microsoft/typescript-go/shim/diagnosticwriter => ../../../toolchain/ttsc/shim/diagnosticwriter github.com/microsoft/typescript-go/shim/parser => ../../../toolchain/ttsc/shim/parser github.com/microsoft/typescript-go/shim/scanner => ../../../toolchain/ttsc/shim/scanner github.com/microsoft/typescript-go/shim/tsoptions => ../../../toolchain/ttsc/shim/tsoptions @@ -23,6 +24,7 @@ require ( github.com/microsoft/typescript-go/shim/ast v0.0.0 github.com/microsoft/typescript-go/shim/checker v0.0.0 github.com/microsoft/typescript-go/shim/compiler v0.0.0 + github.com/microsoft/typescript-go/shim/scanner v0.0.0 github.com/samchon/typia/packages/core/native v0.0.0 github.com/samchon/typia/toolchain/ttsc v0.0.0 ) @@ -33,7 +35,7 @@ require ( github.com/microsoft/typescript-go v0.0.0-20260408193441-2a5e1cf9fe22 // indirect github.com/microsoft/typescript-go/shim/bundled v0.0.0 // indirect github.com/microsoft/typescript-go/shim/core v0.0.0 // indirect - github.com/microsoft/typescript-go/shim/scanner v0.0.0 // indirect + github.com/microsoft/typescript-go/shim/diagnosticwriter v0.0.0 // indirect github.com/microsoft/typescript-go/shim/tsoptions v0.0.0 // indirect github.com/microsoft/typescript-go/shim/tspath v0.0.0 // indirect github.com/microsoft/typescript-go/shim/vfs v0.0.0 // indirect diff --git a/packages/transform/package.json b/packages/transform/package.json deleted file mode 100644 index 15fc2e61e1c..00000000000 --- a/packages/transform/package.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "name": "@typia/transform", - "version": "12.0.2", - "description": "Superfast runtime validators with only one line", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "default": "./lib/index.js" - }, - "./package.json": "./package.json" - }, - "scripts": { - "build": "rimraf lib && ttsc build && rollup -c", - "dev": "ttsc --watch", - "prepack": "pnpm run build" - }, - "repository": { - "type": "git", - "url": "https://github.com/samchon/typia" - }, - "author": "Jeongho Nam", - "license": "MIT", - "bugs": { - "url": "https://github.com/samchon/typia/issues" - }, - "homepage": "https://typia.io", - "dependencies": { - "@typia/core": "workspace:^", - "@typia/interface": "workspace:^", - "@typia/utils": "workspace:^" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "catalog:rollup", - "@rollup/plugin-node-resolve": "catalog:rollup", - "@types/node": "catalog:utils", - "@typescript/native-preview": "catalog:typescript", - "@typia/ttsc": "workspace:^", - "rimraf": "catalog:utils", - "rollup": "catalog:rollup", - "rollup-plugin-auto-external": "catalog:rollup", - "rollup-plugin-node-externals": "catalog:rollup", - "tinyglobby": "^0.2.12" - }, - "sideEffects": false, - "files": [ - "README.md", - "bin", - "native", - "package.json", - "lib", - "src" - ], - "keywords": [ - "fast", - "json", - "stringify", - "transform", - "ajv", - "io-ts", - "zod", - "schema", - "json-schema", - "generator", - "assert", - "clone", - "is", - "validate", - "equal", - "runtime", - "type", - "typebox", - "checker", - "validator", - "safe", - "parse", - "prune", - "random", - "protobuf", - "llm", - "llm-function-calling", - "structured-output", - "openai", - "chatgpt", - "claude", - "gemini", - "llama" - ], - "packageManager": "pnpm@10.6.4", - "publishConfig": { - "access": "public", - "main": "lib/index.js", - "module": "lib/index.mjs", - "types": "lib/index.d.ts", - "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.mjs", - "default": "./lib/index.js" - }, - "./package.json": "./package.json" - } - } -} diff --git a/packages/transform/rollup.config.mjs b/packages/transform/rollup.config.mjs deleted file mode 100644 index 077e7f4a1e1..00000000000 --- a/packages/transform/rollup.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from "../../config/rollup.config.mjs"; diff --git a/packages/transform/src/CallExpressionTransformer.ts b/packages/transform/src/CallExpressionTransformer.ts deleted file mode 100644 index e72b79d465b..00000000000 --- a/packages/transform/src/CallExpressionTransformer.ts +++ /dev/null @@ -1,581 +0,0 @@ -import { - FunctionAssertReturnProgrammer, - FunctionalAssertFunctionProgrammer, - FunctionalAssertParametersProgrammer, - FunctionalIsFunctionProgrammer, - FunctionalIsParametersProgrammer, - FunctionalIsReturnProgrammer, - FunctionalValidateFunctionProgrammer, - FunctionalValidateParametersProgrammer, - FunctionalValidateReturnProgrammer, - ITypiaContext, -} from "@typia/core"; -import { NamingConvention } from "@typia/utils"; -import path from "path"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "./ITransformProps"; -import { AssertTransformer } from "./features/AssertTransformer"; -import { CreateAssertTransformer } from "./features/CreateAssertTransformer"; -import { CreateIsTransformer } from "./features/CreateIsTransformer"; -import { CreateRandomTransformer } from "./features/CreateRandomTransformer"; -import { CreateValidateTransformer } from "./features/CreateValidateTransformer"; -import { IsTransformer } from "./features/IsTransformer"; -import { RandomTransformer } from "./features/RandomTransformer"; -import { ValidateTransformer } from "./features/ValidateTransformer"; -import { FunctionalGenericTransformer } from "./features/functional/FunctionalGenericTransformer"; -import { CreateHttpAssertFormDataTransformer } from "./features/http/CreateHttpAssertFormDataTransformer"; -import { CreateHttpAssertHeadersTransformer } from "./features/http/CreateHttpAssertHeadersTransformer"; -import { CreateHttpAssertQueryTransformer } from "./features/http/CreateHttpAssertQueryTransformer"; -import { CreateHttpFormDataTransformer } from "./features/http/CreateHttpFormDataTransformer"; -import { CreateHttpHeadersTransformer } from "./features/http/CreateHttpHeadersTransformer"; -import { CreateHttpIsFormDataTransformer } from "./features/http/CreateHttpIsFormDataTransformer"; -import { CreateHttpIsHeadersTransformer } from "./features/http/CreateHttpIsHeadersTransformer"; -import { CreateHttpIsQueryTransformer } from "./features/http/CreateHttpIsQueryTransformer"; -import { CreateHttpParameterTransformer } from "./features/http/CreateHttpParameterTransformer"; -import { CreateHttpQueryTransformer } from "./features/http/CreateHttpQueryTransformer"; -import { CreateHttpValidateFormDataTransformer } from "./features/http/CreateHttpValidateFormDataTransformer"; -import { CreateHttpValidateHeadersTransformer } from "./features/http/CreateHttpValidateHeadersTransformer"; -import { CreateHttpValidateQueryTransformer } from "./features/http/CreateHttpValidateQueryTransformer"; -import { HttpAssertFormDataTransformer } from "./features/http/HttpAssertFormDataTransformer"; -import { HttpAssertHeadersTransformer } from "./features/http/HttpAssertHeadersTransformer"; -import { HttpAssertQueryTransformer } from "./features/http/HttpAssertQueryTransformer"; -import { HttpFormDataTransformer } from "./features/http/HttpFormDataTransformer"; -import { HttpHeadersTransformer } from "./features/http/HttpHeadersTransformer"; -import { HttpIsFormDataTransformer } from "./features/http/HttpIsFormDataTransformer"; -import { HttpIsHeadersTransformer } from "./features/http/HttpIsHeadersTransformer"; -import { HttpIsQueryTransformer } from "./features/http/HttpIsQueryTransformer"; -import { HttpParameterTransformer } from "./features/http/HttpParameterTransformer"; -import { HttpQueryTransformer } from "./features/http/HttpQueryTransformer"; -import { HttpValidateFormDataTransformer } from "./features/http/HttpValidateFormDataTransformer"; -import { HttpValidateHeadersTransformer } from "./features/http/HttpValidateHeadersTransformer"; -import { HttpValidateQueryTransformer } from "./features/http/HttpValidateQueryTransformer"; -import { JsonApplicationTransformer } from "./features/json/JsonApplicationTransformer"; -// import { JsonApplicationTransformer } from "./features/json/JsonApplicationTransformer"; -import { JsonAssertParseTransformer } from "./features/json/JsonAssertParseTransformer"; -import { JsonAssertStringifyTransformer } from "./features/json/JsonAssertStringifyTransformer"; -import { JsonCreateAssertParseTransformer } from "./features/json/JsonCreateAssertParseTransformer"; -import { JsonCreateAssertStringifyTransformer } from "./features/json/JsonCreateAssertStringifyTransformer"; -import { JsonCreateIsParseTransformer } from "./features/json/JsonCreateIsParseTransformer"; -import { JsonCreateIsStringifyTransformer } from "./features/json/JsonCreateIsStringifyTransformer"; -import { JsonCreateStringifyTransformer } from "./features/json/JsonCreateStringifyTransformer"; -import { JsonCreateValidateParseTransformer } from "./features/json/JsonCreateValidateParseTransformer"; -import { JsonCreateValidateStringifyTransformer } from "./features/json/JsonCreateValidateStringifyProgrammer"; -import { JsonIsParseTransformer } from "./features/json/JsonIsParseTransformer"; -import { JsonIsStringifyTransformer } from "./features/json/JsonIsStringifyTransformer"; -import { JsonSchemaTransformer } from "./features/json/JsonSchemaTransformer"; -import { JsonSchemasTransformer } from "./features/json/JsonSchemasTransformer"; -import { JsonStringifyTransformer } from "./features/json/JsonStringifyTransformer"; -import { JsonValidateParseTransformer } from "./features/json/JsonValidateParseTransformer"; -import { JsonValidateStringifyTransformer } from "./features/json/JsonValidateStringifyTransformer"; -import { LlmApplicationTransformer } from "./features/llm/LlmApplicationTransformer"; -import { LlmCoerceTransformer } from "./features/llm/LlmCoerceTransformer"; -import { LlmControllerTransformer } from "./features/llm/LlmControllerTransformer"; -import { LlmCreateCoerceTransformer } from "./features/llm/LlmCreateCoerceTransformer"; -import { LlmCreateParseTransformer } from "./features/llm/LlmCreateParseTransformer"; -import { LlmParametersTransformer } from "./features/llm/LlmParametersTransformer"; -import { LlmParseTransformer } from "./features/llm/LlmParseTransformer"; -import { LlmSchemaTransformer } from "./features/llm/LlmSchemaTransformer"; -import { LlmStructuredOutputTransformer } from "./features/llm/LlmStructuredOutputTransformer"; -import { MiscAssertCloneTransformer } from "./features/misc/MiscAssertCloneTransformer"; -import { MiscAssertPruneTransformer } from "./features/misc/MiscAssertPruneTransformer"; -import { MiscCloneTransformer } from "./features/misc/MiscCloneTransformer"; -import { MiscCreateAssertCloneTransformer } from "./features/misc/MiscCreateAssertCloneTransformer"; -import { MiscCreateAssertPruneTransformer } from "./features/misc/MiscCreateAssertPruneTransformer"; -import { MiscCreateCloneTransformer } from "./features/misc/MiscCreateCloneTransformer"; -import { MiscCreateIsCloneTransformer } from "./features/misc/MiscCreateIsCloneTransformer"; -import { MiscCreateIsPruneTransformer } from "./features/misc/MiscCreateIsPruneTransformer"; -import { MiscCreatePruneTransformer } from "./features/misc/MiscCreatePruneTransformer"; -import { MiscCreateValidateCloneTransformer } from "./features/misc/MiscCreateValidateCloneTransformer"; -import { MiscCreateValidatePruneTransformer } from "./features/misc/MiscCreateValidatePruneTransformer"; -import { MiscIsCloneTransformer } from "./features/misc/MiscIsCloneTransformer"; -import { MiscIsPruneTransformer } from "./features/misc/MiscIsPruneTransformer"; -import { MiscLiteralsTransformer } from "./features/misc/MiscLiteralsTransformer"; -import { MiscPruneTransformer } from "./features/misc/MiscPruneTransformer"; -import { MiscValidateCloneTransformer } from "./features/misc/MiscValidateCloneTransformer"; -import { MiscValidatePruneTransformer } from "./features/misc/MiscValidatePruneTransformer"; -import { NotationAssertGeneralTransformer } from "./features/notations/NotationAssertGeneralTransformer"; -import { NotationCreateAssertGeneralTransformer } from "./features/notations/NotationCreateAssertGeneralTransformer"; -import { NotationCreateGeneralTransformer } from "./features/notations/NotationCreateGeneralTransformer"; -import { NotationCreateIsGeneralTransformer } from "./features/notations/NotationCreateIsGeneralTransformer"; -import { NotationCreateValidateGeneralTransformer } from "./features/notations/NotationCreateValidateGeneralTransformer"; -import { NotationGeneralTransformer } from "./features/notations/NotationGeneralTransformer"; -import { NotationIsGeneralTransformer } from "./features/notations/NotationIsGeneralTransformer"; -import { NotationValidateGeneralTransformer } from "./features/notations/NotationValidateGeneralTransformer"; -import { ProtobufAssertDecodeTransformer } from "./features/protobuf/ProtobufAssertDecodeTransformer"; -import { ProtobufAssertEncodeTransformer } from "./features/protobuf/ProtobufAssertEncodeTransformer"; -import { ProtobufCreateAssertDecodeTransformer } from "./features/protobuf/ProtobufCreateAssertDecodeTransformer"; -import { ProtobufCreateAssertEncodeTransformer } from "./features/protobuf/ProtobufCreateAssertEncodeTransformer"; -import { ProtobufCreateDecodeTransformer } from "./features/protobuf/ProtobufCreateDecodeTransformer"; -import { ProtobufCreateEncodeTransformer } from "./features/protobuf/ProtobufCreateEncodeTransformer"; -import { ProtobufCreateIsDecodeTransformer } from "./features/protobuf/ProtobufCreateIsDecodeTransformer"; -import { ProtobufCreateIsEncodeTransformer } from "./features/protobuf/ProtobufCreateIsEncodeTransformer"; -import { ProtobufCreateValidateDecodeTransformer } from "./features/protobuf/ProtobufCreateValidateDecodeTransformer"; -import { ProtobufCreateValidateEncodeTransformer } from "./features/protobuf/ProtobufCreateValidateEncodeTransformer"; -import { ProtobufDecodeTransformer } from "./features/protobuf/ProtobufDecodeTransformer"; -import { ProtobufEncodeTransformer } from "./features/protobuf/ProtobufEncodeTransformer"; -import { ProtobufIsDecodeTransformer } from "./features/protobuf/ProtobufIsDecodeTransformer"; -import { ProtobufIsEncodeTransformer } from "./features/protobuf/ProtobufIsEncodeTransformer"; -import { ProtobufMessageTransformer } from "./features/protobuf/ProtobufMessageTransformer"; -import { ProtobufValidateDecodeTransformer } from "./features/protobuf/ProtobufValidateDecodeTransformer"; -import { ProtobufValidateEncodeTransformer } from "./features/protobuf/ProtobufValidateEncodeTransformer"; -import { ReflectMetadataTransformer } from "./features/reflect/ReflectMetadataTransformer"; -import { ReflectNameTransformer } from "./features/reflect/ReflectNameTransformer"; -import { ReflectSchemaTransformer } from "./features/reflect/ReflectSchemaTransformer"; -import { ReflectSchemasTransformer } from "./features/reflect/ReflectSchemasTransformer"; - -/** - * Transforms `typia.*` function call expressions. - * - * Routes typia function calls (e.g., `typia.is()`, `typia.assert()`) to - * their corresponding transformers. Identifies calls by resolving the - * declaration signature and matching against registered functor map. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace CallExpressionTransformer { - export const transform = (props: { - context: ITypiaContext; - expression: ts.CallExpression; - }): ts.Expression | null => { - //---- - // VALIDATIONS - //---- - // SIGNATURE DECLARATION - const declaration: ts.Declaration | undefined = - props.context.checker.getResolvedSignature(props.expression)?.declaration; - if (!declaration) return props.expression; - - // FILE PATH - const location: string = path.resolve(declaration.getSourceFile().fileName); - if (isTarget(location) === false) return props.expression; - - //---- - // TRANSFORMATION - //---- - // FUNCTION NAME - const module: string = location.split(path.sep).at(-1)!.split(".")[0]!; - const { name } = - props.context.checker.getTypeAtLocation(declaration).symbol; - - // FIND TRANSFORMER - const functor: (() => Task) | undefined = FUNCTORS[module]?.[name]; - if (functor === undefined) return props.expression; - - // RETURNS WITH TRANSFORMATION - const result: ts.Expression | null = functor()({ - context: props.context, - modulo: props.expression.expression, - expression: props.expression, - }); - return result ?? props.expression; - }; - - const isTarget = (location: string): boolean => { - const files: string[] = Object.keys(FUNCTORS); - return files.some( - (f) => - location.includes(path.join("typia", "lib", `${f}.d.ts`)) || - location.includes(path.join("typia", "src", `${f}.ts`)), - ); - }; -} - -type Task = (props: ITransformProps) => ts.Expression | null; - -const FUNCTORS: Record Task>> = { - module: { - // BASIC - assert: () => AssertTransformer.transform({ equals: false, guard: false }), - assertGuard: () => - AssertTransformer.transform({ equals: false, guard: true }), - assertType: () => - AssertTransformer.transform({ equals: false, guard: false }), - is: () => IsTransformer.transform({ equals: false }), - validate: () => ValidateTransformer.transform({ equals: false }), - - // STRICT - assertEquals: () => - AssertTransformer.transform({ equals: true, guard: false }), - assertGuardEquals: () => - AssertTransformer.transform({ equals: true, guard: true }), - equals: () => IsTransformer.transform({ equals: true }), - validateEquals: () => ValidateTransformer.transform({ equals: true }), - - // RANDOM + INTERNAL - random: () => RandomTransformer.transform, - metadata: () => ReflectMetadataTransformer.transform, - - // FACTORIES - createAssert: () => - CreateAssertTransformer.transform({ equals: false, guard: false }), - createAssertGuard: () => - CreateAssertTransformer.transform({ equals: false, guard: true }), - createAssertType: () => - CreateAssertTransformer.transform({ equals: false, guard: false }), - createIs: () => CreateIsTransformer.transform({ equals: false }), - createValidate: () => - CreateValidateTransformer.transform({ - equals: false, - standardSchema: true, - }), - createAssertEquals: () => - CreateAssertTransformer.transform({ equals: true, guard: false }), - createAssertGuardEquals: () => - CreateAssertTransformer.transform({ equals: true, guard: true }), - createEquals: () => CreateIsTransformer.transform({ equals: true }), - createValidateEquals: () => - CreateValidateTransformer.transform({ - equals: true, - standardSchema: true, - }), - createRandom: () => CreateRandomTransformer.transform, - }, - functional: { - // ASSERTIONS - assertFunction: () => - FunctionalGenericTransformer.transform({ - method: "assertFunction", - config: { - equals: false, - }, - programmer: FunctionalAssertFunctionProgrammer.write, - }), - assertParameters: () => - FunctionalGenericTransformer.transform({ - method: "assertParameters", - config: { - equals: false, - }, - programmer: FunctionalAssertParametersProgrammer.write, - }), - assertReturn: () => - FunctionalGenericTransformer.transform({ - method: "assertReturn", - config: { - equals: false, - }, - programmer: FunctionAssertReturnProgrammer.write, - }), - assertEqualsFunction: () => - FunctionalGenericTransformer.transform({ - method: "assertEqualsFunction", - config: { - equals: true, - }, - programmer: FunctionalAssertFunctionProgrammer.write, - }), - assertEqualsParameters: () => - FunctionalGenericTransformer.transform({ - method: "assertEqualsParameters", - config: { - equals: true, - }, - programmer: FunctionalAssertParametersProgrammer.write, - }), - assertEqualsReturn: () => - FunctionalGenericTransformer.transform({ - method: "assertEqualsReturn", - config: { - equals: true, - }, - programmer: FunctionAssertReturnProgrammer.write, - }), - - // IS - isFunction: () => - FunctionalGenericTransformer.transform({ - method: "isFunction", - config: { - equals: false, - }, - programmer: FunctionalIsFunctionProgrammer.write, - }), - isParameters: () => - FunctionalGenericTransformer.transform({ - method: "isParameters", - config: { - equals: false, - }, - programmer: FunctionalIsParametersProgrammer.write, - }), - isReturn: () => - FunctionalGenericTransformer.transform({ - method: "isReturn", - config: { - equals: false, - }, - programmer: FunctionalIsReturnProgrammer.write, - }), - equalsFunction: () => - FunctionalGenericTransformer.transform({ - method: "equalsFunction", - config: { - equals: true, - }, - programmer: FunctionalIsFunctionProgrammer.write, - }), - equalsParameters: () => - FunctionalGenericTransformer.transform({ - method: "equalsParameters", - config: { - equals: true, - }, - programmer: FunctionalIsParametersProgrammer.write, - }), - equalsReturn: () => - FunctionalGenericTransformer.transform({ - method: "equalsReturn", - config: { - equals: true, - }, - programmer: FunctionalIsReturnProgrammer.write, - }), - - // VALIDATIONS - validateFunction: () => - FunctionalGenericTransformer.transform({ - method: "validateFunction", - config: { - equals: false, - }, - programmer: FunctionalValidateFunctionProgrammer.write, - }), - validateParameters: () => - FunctionalGenericTransformer.transform({ - method: "validateParameters", - config: { - equals: false, - }, - programmer: FunctionalValidateParametersProgrammer.write, - }), - validateReturn: () => - FunctionalGenericTransformer.transform({ - method: "validateReturn", - config: { - equals: false, - }, - programmer: FunctionalValidateReturnProgrammer.write, - }), - validateEqualsFunction: () => - FunctionalGenericTransformer.transform({ - method: "validateEqualsFunction", - config: { - equals: true, - }, - programmer: FunctionalValidateFunctionProgrammer.write, - }), - validateEqualsParameters: () => - FunctionalGenericTransformer.transform({ - method: "validateEqualsParameters", - config: { - equals: true, - }, - programmer: FunctionalValidateParametersProgrammer.write, - }), - validateEqualsReturn: () => - FunctionalGenericTransformer.transform({ - method: "validateEqualsReturn", - config: { - equals: true, - }, - programmer: FunctionalValidateReturnProgrammer.write, - }), - }, - http: { - // FORM-DATA - formData: () => HttpFormDataTransformer.transform, - isFormData: () => HttpIsFormDataTransformer.transform, - assertFormData: () => HttpAssertFormDataTransformer.transform, - validateFormData: () => HttpValidateFormDataTransformer.transform, - - // HEADERS - headers: () => HttpHeadersTransformer.transform, - isHeaders: () => HttpIsHeadersTransformer.transform, - assertHeaders: () => HttpAssertHeadersTransformer.transform, - validateHeaders: () => HttpValidateHeadersTransformer.transform, - - // PARAMETER - parameter: () => HttpParameterTransformer.transform, - - // QUERY - query: () => HttpQueryTransformer.transform, - isQuery: () => HttpIsQueryTransformer.transform, - assertQuery: () => HttpAssertQueryTransformer.transform, - validateQuery: () => HttpValidateQueryTransformer.transform, - - // FACTORIES - createFormData: () => CreateHttpFormDataTransformer.transform, - createIsFormData: () => CreateHttpIsFormDataTransformer.transform, - createAssertFormData: () => CreateHttpAssertFormDataTransformer.transform, - createValidateFormData: () => - CreateHttpValidateFormDataTransformer.transform, - createHeaders: () => CreateHttpHeadersTransformer.transform, - createIsHeaders: () => CreateHttpIsHeadersTransformer.transform, - createAssertHeaders: () => CreateHttpAssertHeadersTransformer.transform, - createValidateHeaders: () => CreateHttpValidateHeadersTransformer.transform, - createParameter: () => CreateHttpParameterTransformer.transform, - createQuery: () => CreateHttpQueryTransformer.transform, - createIsQuery: () => CreateHttpIsQueryTransformer.transform, - createAssertQuery: () => CreateHttpAssertQueryTransformer.transform, - createValidateQuery: () => CreateHttpValidateQueryTransformer.transform, - }, - llm: { - controller: () => LlmControllerTransformer.transform, - applicationOfValidate: () => LlmApplicationTransformer.transform, - application: () => LlmApplicationTransformer.transform, - structuredOutput: () => LlmStructuredOutputTransformer.transform, - parameters: () => LlmParametersTransformer.transform, - schema: () => LlmSchemaTransformer.transform, - parse: () => LlmParseTransformer.transform, - createParse: () => LlmCreateParseTransformer.transform, - coerce: () => LlmCoerceTransformer.transform, - createCoerce: () => LlmCreateCoerceTransformer.transform, - }, - json: { - // METADATA - schema: () => JsonSchemaTransformer.transform, - schemas: () => JsonSchemasTransformer.transform, - application: () => JsonApplicationTransformer.transform, - - // PARSER - isParse: () => JsonIsParseTransformer.transform, - assertParse: () => JsonAssertParseTransformer.transform, - validateParse: () => JsonValidateParseTransformer.transform, - - // STRINGIFY - stringify: () => JsonStringifyTransformer.transform, - assertStringify: () => JsonAssertStringifyTransformer.transform, - isStringify: () => JsonIsStringifyTransformer.transform, - validateStringify: () => JsonValidateStringifyTransformer.transform, - - // FACTORIES - createIsParse: () => JsonCreateIsParseTransformer.transform, - createAssertParse: () => JsonCreateAssertParseTransformer.transform, - createValidateParse: () => JsonCreateValidateParseTransformer.transform, - createStringify: () => JsonCreateStringifyTransformer.transform, - createAssertStringify: () => JsonCreateAssertStringifyTransformer.transform, - createIsStringify: () => JsonCreateIsStringifyTransformer.transform, - createValidateStringify: () => - JsonCreateValidateStringifyTransformer.transform, - }, - protobuf: { - // SCHEMA - message: () => ProtobufMessageTransformer.transform, - - // ENCODE - encode: () => ProtobufEncodeTransformer.transform, - assertEncode: () => ProtobufAssertEncodeTransformer.transform, - isEncode: () => ProtobufIsEncodeTransformer.transform, - validateEncode: () => ProtobufValidateEncodeTransformer.transform, - - // DECODE - decode: () => ProtobufDecodeTransformer.transform, - assertDecode: () => ProtobufAssertDecodeTransformer.transform, - isDecode: () => ProtobufIsDecodeTransformer.transform, - validateDecode: () => ProtobufValidateDecodeTransformer.transform, - - // FACTORIES - createEncode: () => ProtobufCreateEncodeTransformer.transform, - createAssertEncode: () => ProtobufCreateAssertEncodeTransformer.transform, - createIsEncode: () => ProtobufCreateIsEncodeTransformer.transform, - createValidateEncode: () => - ProtobufCreateValidateEncodeTransformer.transform, - createDecode: () => ProtobufCreateDecodeTransformer.transform, - createAssertDecode: () => ProtobufCreateAssertDecodeTransformer.transform, - createIsDecode: () => ProtobufCreateIsDecodeTransformer.transform, - createValidateDecode: () => - ProtobufCreateValidateDecodeTransformer.transform, - }, - reflect: { - metadata: () => ReflectMetadataTransformer.transform, - name: () => ReflectNameTransformer.transform, - schema: () => ReflectSchemaTransformer.transform, - schemas: () => ReflectSchemasTransformer.transform, - }, - misc: { - literals: () => MiscLiteralsTransformer.transform, - - // CLONE - clone: () => MiscCloneTransformer.transform, - assertClone: () => MiscAssertCloneTransformer.transform, - isClone: () => MiscIsCloneTransformer.transform, - validateClone: () => MiscValidateCloneTransformer.transform, - - // PRUNE - prune: () => MiscPruneTransformer.transform, - assertPrune: () => MiscAssertPruneTransformer.transform, - isPrune: () => MiscIsPruneTransformer.transform, - validatePrune: () => MiscValidatePruneTransformer.transform, - - // FACTORIES - createClone: () => MiscCreateCloneTransformer.transform, - createAssertClone: () => MiscCreateAssertCloneTransformer.transform, - createIsClone: () => MiscCreateIsCloneTransformer.transform, - createValidateClone: () => MiscCreateValidateCloneTransformer.transform, - createPrune: () => MiscCreatePruneTransformer.transform, - createAssertPrune: () => MiscCreateAssertPruneTransformer.transform, - createIsPrune: () => MiscCreateIsPruneTransformer.transform, - createValidatePrune: () => MiscCreateValidatePruneTransformer.transform, - }, - notations: { - // CAMEL - camel: () => NotationGeneralTransformer.transform(NamingConvention.camel), - assertCamel: () => - NotationAssertGeneralTransformer.transform(NamingConvention.camel), - isCamel: () => - NotationIsGeneralTransformer.transform(NamingConvention.camel), - validateCamel: () => - NotationValidateGeneralTransformer.transform(NamingConvention.camel), - - // PASCAL - pascal: () => NotationGeneralTransformer.transform(NamingConvention.pascal), - assertPascal: () => - NotationAssertGeneralTransformer.transform(NamingConvention.pascal), - isPascal: () => - NotationIsGeneralTransformer.transform(NamingConvention.pascal), - validatePascal: () => - NotationValidateGeneralTransformer.transform(NamingConvention.pascal), - - // SNAKE - snake: () => NotationGeneralTransformer.transform(NamingConvention.snake), - assertSnake: () => - NotationAssertGeneralTransformer.transform(NamingConvention.snake), - isSnake: () => - NotationIsGeneralTransformer.transform(NamingConvention.snake), - validateSnake: () => - NotationValidateGeneralTransformer.transform(NamingConvention.snake), - - // FACTORIES - createCamel: () => - NotationCreateGeneralTransformer.transform(NamingConvention.camel), - createAssertCamel: () => - NotationCreateAssertGeneralTransformer.transform(NamingConvention.camel), - createIsCamel: () => - NotationCreateIsGeneralTransformer.transform(NamingConvention.camel), - createValidateCamel: () => - NotationCreateValidateGeneralTransformer.transform( - NamingConvention.camel, - ), - createPascal: () => - NotationCreateGeneralTransformer.transform(NamingConvention.pascal), - createAssertPascal: () => - NotationCreateAssertGeneralTransformer.transform(NamingConvention.pascal), - createIsPascal: () => - NotationCreateIsGeneralTransformer.transform(NamingConvention.pascal), - createValidatePascal: () => - NotationCreateValidateGeneralTransformer.transform( - NamingConvention.pascal, - ), - createSnake: () => - NotationCreateGeneralTransformer.transform(NamingConvention.snake), - createAssertSnake: () => - NotationCreateAssertGeneralTransformer.transform(NamingConvention.snake), - createIsSnake: () => - NotationCreateIsGeneralTransformer.transform(NamingConvention.snake), - createValidateSnake: () => - NotationCreateValidateGeneralTransformer.transform( - NamingConvention.snake, - ), - }, -}; diff --git a/packages/transform/src/FileTransformer.ts b/packages/transform/src/FileTransformer.ts deleted file mode 100644 index a3d1b2237ed..00000000000 --- a/packages/transform/src/FileTransformer.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { ITypiaContext, ImportProgrammer } from "@typia/core"; -import { Singleton } from "@typia/utils"; -import ts from "@typescript/native-preview"; - -import { NodeTransformer } from "./NodeTransformer"; -import { TransformerError } from "./TransformerError"; - -/** - * TypeScript source file transformer for typia. - * - * Entry point for typia's compile-time transformation. Traverses AST nodes, - * transforms `typia.*` function calls into optimized runtime code, and injects - * required imports. Skips declaration files. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace FileTransformer { - export const transform = - (environments: Omit) => - (transformer: ts.TransformationContext) => - (file: ts.SourceFile): ts.SourceFile => { - if (file.isDeclarationFile) return file; - - const importer: ImportProgrammer = new ImportProgrammer({ - internalPrefix: "typia_transform_", - runtime: environments.options.runtime, - }); - const context: ITypiaContext = { - ...environments, - transformer, - importer, - }; - checkJsDocParsingMode.get(context, file); - - file = ts.visitEachChild( - file, - (node) => - iterate_node({ - context, - node, - }), - transformer, - ); - const index: number = find_import_injection_index(file); - return ts.factory.updateSourceFile( - file, - [ - ...file.statements.slice(0, index), - ...importer.toStatements(), - ...file.statements.slice(index), - ], - false, - file.referencedFiles, - file.typeReferenceDirectives, - file.hasNoDefaultLib, - file.libReferenceDirectives, - ); - }; - - const iterate_node = (props: { - context: ITypiaContext; - node: ts.Node; - }): ts.Node => - ts.visitEachChild( - try_transform_node(props) ?? props.node, - (node) => - iterate_node({ - context: props.context, - node, - }), - props.context.transformer, - ); - - const try_transform_node = (props: { - context: ITypiaContext; - node: ts.Node; - }): ts.Node | null => { - try { - return NodeTransformer.transform(props); - } catch (exp) { - // ONLY ACCEPT TRANSFORMER-ERROR - if (!isTransformerError(exp)) throw exp; - - // REPORT DIAGNOSTIC - const diagnostic = ts.createDiagnosticForNode(props.node, { - key: exp.code, - category: ts.DiagnosticCategory.Error, - message: exp.message, - code: `(${exp.code})` as any, - }); - props.context.extras.addDiagnostic(diagnostic); - return null; - } - }; - - const find_import_injection_index = (file: ts.SourceFile): number => { - let i: number = 0; - for (; i < file.statements.length; ++i) { - const stmt: ts.Statement = file.statements[i]!; - if ( - ts.isExpressionStatement(stmt) && - ts.isStringLiteralLike(stmt.expression) && - stmt.expression.text.startsWith("use ") - ) - continue; - break; - } - return i; - }; -} - -const isTransformerError = (error: any): error is TransformerError => - typeof error === "object" && - error !== null && - error.constructor.name === "TransformerError" && - typeof error.code === "string" && - typeof error.message === "string"; - -const checkJsDocParsingMode = new Singleton( - (context: ITypiaContext, file: ts.SourceFile) => { - if ( - typeof file.jsDocParsingMode === "number" && - file.jsDocParsingMode !== 0 - ) { - context.extras.addDiagnostic( - ts.createDiagnosticForNode(file, { - code: `(typia setup)` as any, - key: "jsDocParsingMode", - category: ts.DiagnosticCategory.Warning, - message: [ - `Run "npx typia setup" command again.`, - ``, - `The active compiler host is not parsing JSDoc comments. Therefore, "typia" also cannot utilize those JSDoc comments, and it damages features like comment tags or JSON schema generation.`, - ``, - `To solve this problem, run "npx typia setup" so the tsgo + ttsc plugin pipeline can be configured again.`, - ``, - ` - reference: https://github.com/microsoft/TypeScript/pull/55739`, - ].join("\n"), - }), - ); - } - }, -); diff --git a/packages/transform/src/ITransformProps.ts b/packages/transform/src/ITransformProps.ts deleted file mode 100644 index 41a16df0475..00000000000 --- a/packages/transform/src/ITransformProps.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ITypiaContext } from "@typia/core"; -import ts from "@typescript/native-preview"; - -/** - * Properties for individual typia function transformation. - * - * Passed to each transformer handler when processing a `typia.*()` call. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export interface ITransformProps { - /** Typia transformation context with type checker and utilities. */ - context: ITypiaContext; - - /** Module expression (e.g., `typia` in `typia.is()`). */ - modulo: ts.LeftHandSideExpression; - - /** The original `typia.*()` call expression being transformed. */ - expression: ts.CallExpression; -} diff --git a/packages/transform/src/ImportTransformer.ts b/packages/transform/src/ImportTransformer.ts deleted file mode 100644 index ede25aadbab..00000000000 --- a/packages/transform/src/ImportTransformer.ts +++ /dev/null @@ -1,262 +0,0 @@ -import path from "path"; -import ts from "@typescript/native-preview"; - -/** - * Transforms import paths for typia build output. - * - * Rewrites relative import paths when building typia packages. Also removes - * unused typia imports that only contained transformable function calls (since - * those are replaced at compile time). - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace ImportTransformer { - export const transform = - (props: { from: string; to: string }) => - (context: ts.TransformationContext) => - (file: ts.SourceFile) => - transform_file({ - top: props.from, - to: props.to, - context, - file, - }); - - const transform_file = (props: { - top: string; - to: string; - context: ts.TransformationContext; - file: ts.SourceFile; - }): ts.SourceFile => { - if (props.file.isDeclarationFile) return props.file; - - const from: string = get_directory_path( - path.resolve(props.file.getSourceFile().fileName), - ); - const to: string = from.replace(props.top, props.to); - - // First pass: transform relative imports - let transformedFile = ts.visitEachChild( - props.file, - (node) => - transform_node({ - top: props.top, - from, - to, - node, - }), - props.context, - ); - - // Second pass: remove unused typia imports - transformedFile = removeUnusedTypiaImports(transformedFile); - - return transformedFile; - }; - - const transform_node = (props: { - top: string; - from: string; - to: string; - node: ts.Node; - }) => { - if ( - !ts.isImportDeclaration(props.node) || - !ts.isStringLiteral(props.node.moduleSpecifier) - ) - return props.node; - - const text: string = props.node.moduleSpecifier.text; - if (text[0] !== ".") return props.node; - - const location: string = path.resolve(props.from, text); - if (location.indexOf(props.top) === 0) return props.node; - - const replaced: string = (() => { - const simple: string = path - .relative(props.to, location) - .split(path.sep) - .join("/"); - return simple[0] === "." ? simple : `./${simple}`; - })(); - - return ts.factory.createImportDeclaration( - undefined, - props.node.importClause, - ts.factory.createStringLiteral(replaced), - props.node.assertClause, - ); - }; -} - -const get_directory_path = (file: string): string => { - const split: string[] = path.resolve(file).split(path.sep); - split.pop(); - return path.resolve(split.join(path.sep)); -}; - -/** Remove unused typia imports that are only used in transformable calls */ -const removeUnusedTypiaImports = (file: ts.SourceFile): ts.SourceFile => { - // Find typia imports and collect all identifiers - interface ImportMetadata { - declaration: ts.ImportDeclaration; - default: boolean; - } - const imports: Map = new Map(); - for (const stmt of file.statements) { - if ( - ts.isImportDeclaration(stmt) === false || - ts.isStringLiteral(stmt.moduleSpecifier) === false || - stmt.moduleSpecifier.text !== "typia" || - stmt.importClause === undefined - ) - continue; - - // Track default import (import typia from 'typia') - if (stmt.importClause.name) - imports.set(stmt.importClause.name.text, { - declaration: stmt, - default: true, - }); - - // Track named imports (import { tags } from 'typia') - keep these - if ( - stmt.importClause.namedBindings && - ts.isNamedImports(stmt.importClause.namedBindings) - ) - for (const element of stmt.importClause.namedBindings.elements) - imports.set(element.name.text, { - declaration: stmt, - default: false, - }); - } - if (imports.size === 0) return file; // No typia imports to check - - // Find usage of typia identifiers that are NOT transformable calls - const nonTransformableUsage = new Set(); - const checkUsage = (node: ts.Node) => { - if (ts.isIdentifier(node) && imports.has(node.text)) { - const identifier: string = node.text; - // Check if this identifier is being used as part of a property access - if ( - node.parent && - ts.isPropertyAccessExpression(node.parent) && - node.parent.expression === node - ) { - // This is typia.something - check if it's a transformable call pattern - if (!isLikelyTransformableCall(node.parent)) - nonTransformableUsage.add(identifier); - } else - // Direct usage of the typia identifier (not as property access) - // This is definitely non-transformable usage - nonTransformableUsage.add(identifier); - } - ts.forEachChild(node, checkUsage); - }; - - // Check all statements except import declarations - for (const stmt of file.statements) - if ( - !ts.isImportDeclaration(stmt) || - !ts.isStringLiteral(stmt.moduleSpecifier) || - stmt.moduleSpecifier.text !== "typia" - ) - checkUsage(stmt); - - // Update import statements - const newStatements: ts.Statement[] = file.statements - .map((stmt) => { - if ( - ts.isImportDeclaration(stmt) && - ts.isStringLiteral(stmt.moduleSpecifier) && - stmt.moduleSpecifier.text === "typia" && - stmt.importClause - ) { - const newImportClause = filterTypiaImportClause( - stmt.importClause, - nonTransformableUsage, - ); - if (newImportClause) - return ts.factory.createImportDeclaration( - stmt.modifiers, - newImportClause, - stmt.moduleSpecifier, - stmt.attributes, - ); - return null; // Skip adding the import if all imports are unused - } - return stmt; - }) - .filter((stmt) => stmt !== null); - return ts.factory.updateSourceFile( - file, - newStatements, - file.isDeclarationFile, - file.referencedFiles, - file.typeReferenceDirectives, - file.hasNoDefaultLib, - file.libReferenceDirectives, - ); -}; - -/** - * Check if a property access expression looks like a transformable typia call - * This uses heuristics to detect patterns like typia.xxx(), - * typia.namespace.xxx() - */ -const isLikelyTransformableCall = ( - node: ts.PropertyAccessExpression, -): boolean => { - // Check if this is eventually part of a call expression - let current: ts.Node = node; - - // Walk up the chain to find if this leads to a call expression - // Handle patterns like: typia.xxx(), typia.namespace.xxx() - while (ts.isPropertyAccessExpression(current)) { - current = current.parent; - } - - // If the final result is a call expression, this is likely transformable - if ( - ts.isCallExpression(current) && - (current.expression === node || - (ts.isPropertyAccessExpression(current.expression) && - isTypiaPropertyChain(current.expression))) - ) { - return true; - } - - return false; -}; - -/** Check if a property access expression is part of a typia.xxx.yyy chain */ -const isTypiaPropertyChain = (node: ts.PropertyAccessExpression): boolean => { - let current: ts.Expression = node; - - while (ts.isPropertyAccessExpression(current)) { - current = current.expression; - } - - return ts.isIdentifier(current) && current.text === "typia"; -}; - -/** Filter import clause to remove unused default imports */ -const filterTypiaImportClause = ( - importClause: ts.ImportClause, - usedImports: Set, -): ts.ImportClause | undefined => { - const hasDefaultImport = - importClause.name && usedImports.has(importClause.name.text); - const namedBindings = importClause.namedBindings; // Always keep named bindings like { tags } - - // Return undefined if no imports are used - if (!hasDefaultImport && !namedBindings) { - return undefined; - } - - return ts.factory.createImportClause( - importClause.isTypeOnly, - hasDefaultImport ? importClause.name : undefined, - namedBindings, - ); -}; diff --git a/packages/transform/src/NodeTransformer.ts b/packages/transform/src/NodeTransformer.ts deleted file mode 100644 index 2da605e1594..00000000000 --- a/packages/transform/src/NodeTransformer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ITypiaContext } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { CallExpressionTransformer } from "./CallExpressionTransformer"; - -/** - * TypeScript AST node transformer. - * - * Delegates call expression nodes to {@link CallExpressionTransformer} for - * potential `typia.*` function transformation. Non-call nodes pass through. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export namespace NodeTransformer { - export const transform = (props: { - context: ITypiaContext; - node: ts.Node; - }): ts.Node | null => - ts.isCallExpression(props.node) && props.node.parent - ? CallExpressionTransformer.transform({ - context: props.context, - expression: props.node, - }) - : props.node; -} diff --git a/packages/transform/src/TransformerError.ts b/packages/transform/src/TransformerError.ts deleted file mode 100644 index 8282c0b879e..00000000000 --- a/packages/transform/src/TransformerError.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { MetadataFactory, MetadataObjectType } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -/** - * Error thrown during typia transformation. - * - * `TransformerError` is thrown when `typia.*()` receives unsupported types - * or invalid configurations at compile time. The error message details which - * types failed and why. - * - * Common causes: - * - * - Tuples in LLM schema (not supported by most LLMs) - * - Recursive types without `$ref` support - * - `any` types without explicit handling - * - Native classes not serializable to JSON - * - * Use {@link from} to create from {@link MetadataFactory.IError} instances. - * - * @author Jeongho Nam - https://github.com/samchon - */ -export class TransformerError extends Error { - /** Error code identifying the error type. */ - public readonly code: string; - - public constructor(props: TransformerError.IProps) { - super(props.message); - this.code = props.code; - - // INHERITANCE POLYFILL - const proto = new.target.prototype; - if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto); - else (this as any).__proto__ = proto; - } -} -export namespace TransformerError { - /** Constructor properties for TransformerError. */ - export interface IProps { - /** Error code identifying the error type. */ - code: string; - - /** Human-readable error message. */ - message: string; - } - - /** - * Create error from metadata factory errors. - * - * Formats multiple type errors into a single TransformerError. - */ - export const from = (props: { - code: string; - errors: MetadataFactory.IError[]; - }): TransformerError => { - const body: string = props.errors - .map((e) => { - const subject: string = - e.explore.object === null - ? "" - : join(e.explore.object)(e.explore.property); - const middle: string = e.explore.parameter - ? `(parameter: ${JSON.stringify(e.explore.parameter)})` - : e.explore.output - ? "(return type)" - : ""; - const type: string = `${subject.length ? `${subject}: ` : ""}${e.name}`; - return `- ${type}${middle}\n${e.messages - .map((msg) => ` - ${msg}`) - .join("\n")}`; - }) - .join("\n\n"); - return new TransformerError({ - code: props.code, - message: `unsupported type detected\n\n${body}`, - }); - }; - - const join = - (object: MetadataObjectType) => (key: string | object | null) => { - if (key === null) return object.name; - else if (typeof key === "object") return `${object.name}[key]`; - else if (NamingConvention.variable(key)) return `${object.name}.${key}`; - return `${object.name}[${JSON.stringify(key)}]`; - }; -} diff --git a/packages/transform/src/TypiaGenerator.ts b/packages/transform/src/TypiaGenerator.ts deleted file mode 100644 index 74438ba79b8..00000000000 --- a/packages/transform/src/TypiaGenerator.ts +++ /dev/null @@ -1,174 +0,0 @@ -import fs from "fs"; -import path from "path"; -import ts from "@typescript/native-preview"; - -import { ImportTransformer } from "./ImportTransformer"; -import { transform } from "./transform"; - -export namespace TypiaGenerator { - export interface ILocation { - input: string; - output: string; - project: string; - } - - export const build = async ( - location: TypiaGenerator.ILocation, - ): Promise => { - location.input = path.resolve(location.input); - location.output = path.resolve(location.output); - - if ((await is_directory(location.input)) === false) - throw new URIError( - "Error on TypiaGenerator.generate(): input path is not a directory.", - ); - else if (fs.existsSync(location.output) === false) - await fs.promises.mkdir(location.output, { recursive: true }); - else if ((await is_directory(location.output)) === false) { - const parent: string = path.join(location.output, ".."); - if ((await is_directory(parent)) === false) - throw new URIError( - "Error on TypiaGenerator.generate(): output path is not a directory.", - ); - await fs.promises.mkdir(location.output); - } - - // CREATE PROGRAM - const { options: compilerOptions } = ts.parseJsonConfigFileContent( - ts.readConfigFile(location.project, ts.sys.readFile).config, - { - fileExists: ts.sys.fileExists, - readFile: ts.sys.readFile, - readDirectory: ts.sys.readDirectory, - useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, - }, - path.dirname(location.project), - ); - - const program: ts.Program = ts.createProgram( - await (async () => { - const container: string[] = []; - await gather({ - location, - container, - from: location.input, - to: location.output, - }); - return container; - })(), - compilerOptions, - ); - - // DO TRANSFORM - const diagnostics: ts.Diagnostic[] = []; - const result: ts.TransformationResult = ts.transform( - program - .getSourceFiles() - .filter( - (file) => - !file.isDeclarationFile && - path.resolve(file.fileName).indexOf(location.input) !== -1, - ), - [ - ImportTransformer.transform({ - from: location.input, - to: location.output, - }), - transform( - program, - ((compilerOptions.plugins as any[]) ?? []).find( - (p: any) => - p.transform === "typia/lib/ttsc/plugin" || - p.transform === "typia/lib/transform", - ) ?? {}, - { - addDiagnostic: (diag) => diagnostics.push(diag), - }, - ), - ], - program.getCompilerOptions(), - ); - - // TRACE ERRORS - for (const diag of diagnostics) { - const file: string = diag.file - ? path.relative(diag.file.fileName, process.cwd()) - : "(unknown file)"; - const category: string = - diag.category === ts.DiagnosticCategory.Warning - ? "warning" - : diag.category === ts.DiagnosticCategory.Error - ? "error" - : diag.category === ts.DiagnosticCategory.Suggestion - ? "suggestion" - : diag.category === ts.DiagnosticCategory.Message - ? "message" - : "unknown"; - const [line, pos] = diag.file - ? (() => { - const lines: string[] = diag - .file!.text.substring(0, diag.start) - .split("\n"); - if (lines.length === 0) return [0, 0]; - return [lines.length, lines.at(-1)!.length + 1]; - })() - : [0, 0]; - console.error( - `${file}:${line}:${pos} - ${category} TS${diag.code}: ${diag.messageText}`, - ); - } - if (diagnostics.length) process.exit(-1); - - // ARCHIVE TRANSFORMED FILES - const printer: ts.Printer = ts.createPrinter({ - newLine: ts.NewLineKind.LineFeed, - }); - for (const file of result.transformed) { - const to: string = path - .resolve(file.fileName) - .replace(location.input, location.output); - - const content: string = printer.printFile(file); - await fs.promises.writeFile(to, content, "utf8"); - } - }; - - const is_directory = async (current: string): Promise => { - const stat: fs.Stats = await fs.promises.stat(current); - return stat.isDirectory(); - }; - - const gather = async (props: { - location: ILocation; - container: string[]; - from: string; - to: string; - }) => { - if (props.from === props.location.output) return; - else if (fs.existsSync(props.to) === false) - await fs.promises.mkdir(props.to); - - for (const file of await fs.promises.readdir(props.from)) { - const next: string = path.join(props.from, file); - const stat: fs.Stats = await fs.promises.stat(next); - - if (stat.isDirectory()) { - await gather({ - location: props.location, - container: props.container, - from: next, - to: path.join(props.to, file), - }); - continue; - } else if (is_supported_extension(file)) props.container.push(next); - } - }; - - const is_supported_extension = (filename: string): boolean => { - // avoid using look-behind assertion as it is not marked as Baseline Widely Available - return TS_PATTERN.test(filename) && !DTS_PATTERN.test(filename); - }; -} - -const TS_PATTERN = /\.[cm]?tsx?$/; -const DTS_PATTERN = /\.d\.[cm]?tsx?$/; diff --git a/packages/transform/src/features/AssertTransformer.ts b/packages/transform/src/features/AssertTransformer.ts deleted file mode 100644 index 0d1cef3dd57..00000000000 --- a/packages/transform/src/features/AssertTransformer.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AssertProgrammer } from "@typia/core"; - -import { ITransformProps } from "../ITransformProps"; -import { GenericTransformer } from "../internal/GenericTransformer"; - -export namespace AssertTransformer { - export const transform = - (config: AssertProgrammer.IConfig) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: config.equals - ? config.guard - ? "assertGuardEquals" - : "assertEquals" - : config.guard - ? "assertGuard" - : "assert", - write: (x) => - AssertProgrammer.write({ - ...x, - config, - }), - }); -} diff --git a/packages/transform/src/features/CreateAssertTransformer.ts b/packages/transform/src/features/CreateAssertTransformer.ts deleted file mode 100644 index 61dcbc9dd5c..00000000000 --- a/packages/transform/src/features/CreateAssertTransformer.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AssertProgrammer } from "@typia/core"; - -import { ITransformProps } from "../ITransformProps"; -import { GenericTransformer } from "../internal/GenericTransformer"; - -export namespace CreateAssertTransformer { - export const transform = - (config: AssertProgrammer.IConfig) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: config.equals - ? config.guard - ? "assertGuardEquals" - : "assertEquals" - : config.guard - ? "assertGuard" - : "assert", - write: (x) => - AssertProgrammer.write({ - ...x, - config, - }), - }); -} diff --git a/packages/transform/src/features/CreateIsTransformer.ts b/packages/transform/src/features/CreateIsTransformer.ts deleted file mode 100644 index 6508c4263b5..00000000000 --- a/packages/transform/src/features/CreateIsTransformer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IsProgrammer } from "@typia/core"; - -import { ITransformProps } from "../ITransformProps"; -import { GenericTransformer } from "../internal/GenericTransformer"; - -export namespace CreateIsTransformer { - export const transform = - (config: IsProgrammer.IConfig) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: config.equals ? "equals" : "is", - write: (x) => - IsProgrammer.write({ - ...x, - config, - }), - }); -} diff --git a/packages/transform/src/features/CreateRandomTransformer.ts b/packages/transform/src/features/CreateRandomTransformer.ts deleted file mode 100644 index a809b052ce9..00000000000 --- a/packages/transform/src/features/CreateRandomTransformer.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { RandomProgrammer } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../ITransformProps"; -import { TransformerError } from "../TransformerError"; - -export namespace CreateRandomTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // CHECK GENERIC ARGUMENT EXISTENCE - if (!props.expression.typeArguments?.[0]) - throw new TransformerError({ - code: "typia.createRandom", - message: "generic argument is not specified.", - }); - - // GET TYPE INFO - const node: ts.TypeNode = props.expression.typeArguments[0]; - const type: ts.Type = props.context.checker.getTypeFromTypeNode(node); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.createRandom", - message: "non-specified generic argument.", - }); - - // DO TRANSFORM - return RandomProgrammer.write({ - context: { - ...props.context, - options: { - ...props.context.options, - functional: false, - numeric: false, - }, - }, - modulo: props.modulo, - type, - name: node.getFullText().trim(), - init: props.expression.arguments?.[0], - }); - }; -} diff --git a/packages/transform/src/features/CreateValidateTransformer.ts b/packages/transform/src/features/CreateValidateTransformer.ts deleted file mode 100644 index 909957d04e4..00000000000 --- a/packages/transform/src/features/CreateValidateTransformer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ValidateProgrammer } from "@typia/core"; - -import { ITransformProps } from "../ITransformProps"; -import { GenericTransformer } from "../internal/GenericTransformer"; - -export namespace CreateValidateTransformer { - export const transform = - (config: ValidateProgrammer.IConfig) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: config.equals ? "validateEquals" : "validate", - write: (x) => - ValidateProgrammer.write({ - ...x, - config, - }), - }); -} diff --git a/packages/transform/src/features/IsTransformer.ts b/packages/transform/src/features/IsTransformer.ts deleted file mode 100644 index b6e1507d8ff..00000000000 --- a/packages/transform/src/features/IsTransformer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IsProgrammer } from "@typia/core"; - -import { ITransformProps } from "../ITransformProps"; -import { GenericTransformer } from "../internal/GenericTransformer"; - -export namespace IsTransformer { - export const transform = - (config: IsProgrammer.IConfig) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: config.equals ? "equals" : "is", - write: (x) => - IsProgrammer.write({ - ...x, - config, - }), - }); -} diff --git a/packages/transform/src/features/RandomTransformer.ts b/packages/transform/src/features/RandomTransformer.ts deleted file mode 100644 index 19954480509..00000000000 --- a/packages/transform/src/features/RandomTransformer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { RandomProgrammer } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../ITransformProps"; -import { TransformerError } from "../TransformerError"; - -export namespace RandomTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // CHECK GENERIC ARGUMENT EXISTENCE - if (!props.expression.typeArguments?.[0]) - throw new TransformerError({ - code: `typia.${props.modulo.getText()}`, - message: "generic argument is not specified.", - }); - - // GET TYPE INFO - const node: ts.TypeNode = props.expression.typeArguments[0]; - const type: ts.Type = props.context.checker.getTypeFromTypeNode(node); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: `typia.${props.modulo.getText()}`, - message: "non-specified generic argument.", - }); - - return ts.factory.createCallExpression( - RandomProgrammer.write({ - context: props.context, - modulo: props.modulo, - type, - name: node.getFullText().trim(), - init: undefined, - }), - undefined, - props.expression.arguments.length - ? [props.expression.arguments[0]!] - : undefined, - ); - }; -} diff --git a/packages/transform/src/features/ValidateTransformer.ts b/packages/transform/src/features/ValidateTransformer.ts deleted file mode 100644 index 51859b40191..00000000000 --- a/packages/transform/src/features/ValidateTransformer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ValidateProgrammer } from "@typia/core"; - -import { ITransformProps } from "../ITransformProps"; -import { GenericTransformer } from "../internal/GenericTransformer"; - -export namespace ValidateTransformer { - export const transform = - (config: ValidateProgrammer.IConfig) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: config.equals ? "validateEquals" : "validate", - write: (x) => - ValidateProgrammer.write({ - ...x, - config, - }), - }); -} diff --git a/packages/transform/src/features/functional/FunctionalGenericTransformer.ts b/packages/transform/src/features/functional/FunctionalGenericTransformer.ts deleted file mode 100644 index 94af9bdf27a..00000000000 --- a/packages/transform/src/features/functional/FunctionalGenericTransformer.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ITypiaContext, TypeFactory } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace FunctionalGenericTransformer { - export interface IConfig { - equals: boolean; - } - export interface ISpecification { - method: string; - config: IConfig; - programmer: (p: { - context: ITypiaContext; - modulo: ts.LeftHandSideExpression; - expression: ts.Expression; - declaration: ts.FunctionDeclaration; - config: IConfig; - init?: ts.Expression; - }) => ts.Expression; - } - export const transform = - (spec: ISpecification) => - (props: ITransformProps): ts.Expression => { - // CHECK PARAMETER - if (props.expression.arguments.length === 0) - throw new TransformerError({ - code: `typia.functional.${spec.method}`, - message: `no input value.`, - }); - - // GET TYPE INFO - const type: ts.Type = - props.expression.typeArguments && props.expression.typeArguments[0] - ? props.context.checker.getTypeFromTypeNode( - props.expression.typeArguments[0], - ) - : props.context.checker.getTypeAtLocation( - props.expression.arguments[0]!, - ); - if (TypeFactory.isFunction(type) === false) - throw new TransformerError({ - code: `typia.functional.${spec.method}`, - message: `input value is not a function.`, - }); - return spec.programmer({ - ...props, - config: spec.config, - expression: props.expression.arguments[0] as ts.Expression, - declaration: type.symbol!.declarations![0] as ts.FunctionDeclaration, - init: props.expression.arguments[1], - }); - }; -} diff --git a/packages/transform/src/features/http/CreateHttpAssertFormDataTransformer.ts b/packages/transform/src/features/http/CreateHttpAssertFormDataTransformer.ts deleted file mode 100644 index 32f789dec21..00000000000 --- a/packages/transform/src/features/http/CreateHttpAssertFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpAssertFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpAssertFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createAssertFormData", - write: HttpAssertFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpAssertHeadersTransformer.ts b/packages/transform/src/features/http/CreateHttpAssertHeadersTransformer.ts deleted file mode 100644 index 0d907807329..00000000000 --- a/packages/transform/src/features/http/CreateHttpAssertHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpAssertHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpAssertHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createAssertHeaders", - write: HttpAssertHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpAssertQueryTransformer.ts b/packages/transform/src/features/http/CreateHttpAssertQueryTransformer.ts deleted file mode 100644 index f87126bc3a0..00000000000 --- a/packages/transform/src/features/http/CreateHttpAssertQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpAssertQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpAssertQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createAssertQuery", - write: HttpAssertQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpFormDataTransformer.ts b/packages/transform/src/features/http/CreateHttpFormDataTransformer.ts deleted file mode 100644 index b67971834a7..00000000000 --- a/packages/transform/src/features/http/CreateHttpFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createFormData", - write: HttpFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpHeadersTransformer.ts b/packages/transform/src/features/http/CreateHttpHeadersTransformer.ts deleted file mode 100644 index 2db19e86045..00000000000 --- a/packages/transform/src/features/http/CreateHttpHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createHeaders", - write: HttpHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpIsFormDataTransformer.ts b/packages/transform/src/features/http/CreateHttpIsFormDataTransformer.ts deleted file mode 100644 index 220107001df..00000000000 --- a/packages/transform/src/features/http/CreateHttpIsFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpIsFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpIsFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createIsFormData", - write: HttpIsFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpIsHeadersTransformer.ts b/packages/transform/src/features/http/CreateHttpIsHeadersTransformer.ts deleted file mode 100644 index f965861143f..00000000000 --- a/packages/transform/src/features/http/CreateHttpIsHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpIsHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpIsHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createIsHeaders", - write: HttpIsHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpIsQueryTransformer.ts b/packages/transform/src/features/http/CreateHttpIsQueryTransformer.ts deleted file mode 100644 index 070505e689c..00000000000 --- a/packages/transform/src/features/http/CreateHttpIsQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpIsQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpIsQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createIsQuery", - write: HttpIsQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpParameterTransformer.ts b/packages/transform/src/features/http/CreateHttpParameterTransformer.ts deleted file mode 100644 index 239b81666fd..00000000000 --- a/packages/transform/src/features/http/CreateHttpParameterTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpParameterProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpParameterTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createParameter", - write: HttpParameterProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpQueryTransformer.ts b/packages/transform/src/features/http/CreateHttpQueryTransformer.ts deleted file mode 100644 index 5e00609966d..00000000000 --- a/packages/transform/src/features/http/CreateHttpQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createQuery", - write: HttpQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpValidateFormDataTransformer.ts b/packages/transform/src/features/http/CreateHttpValidateFormDataTransformer.ts deleted file mode 100644 index 524ef93ec54..00000000000 --- a/packages/transform/src/features/http/CreateHttpValidateFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpValidateFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpValidateFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createValidateFormData", - write: HttpValidateFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpValidateHeadersTransformer.ts b/packages/transform/src/features/http/CreateHttpValidateHeadersTransformer.ts deleted file mode 100644 index d490abba97d..00000000000 --- a/packages/transform/src/features/http/CreateHttpValidateHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpValidateHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpValidateHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createValidateHeaders", - write: HttpValidateHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/CreateHttpValidateQueryTransformer.ts b/packages/transform/src/features/http/CreateHttpValidateQueryTransformer.ts deleted file mode 100644 index 300b6f28494..00000000000 --- a/packages/transform/src/features/http/CreateHttpValidateQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpValidateQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace CreateHttpValidateQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "http.createValidateQuery", - write: HttpValidateQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpAssertFormDataTransformer.ts b/packages/transform/src/features/http/HttpAssertFormDataTransformer.ts deleted file mode 100644 index fcd01fffd91..00000000000 --- a/packages/transform/src/features/http/HttpAssertFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpAssertFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpAssertFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.assertFormData", - write: HttpAssertFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpAssertHeadersTransformer.ts b/packages/transform/src/features/http/HttpAssertHeadersTransformer.ts deleted file mode 100644 index e2a0bbdc306..00000000000 --- a/packages/transform/src/features/http/HttpAssertHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpAssertHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpAssertHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.assertHeaders", - write: HttpAssertHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpAssertQueryTransformer.ts b/packages/transform/src/features/http/HttpAssertQueryTransformer.ts deleted file mode 100644 index 67710103730..00000000000 --- a/packages/transform/src/features/http/HttpAssertQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpAssertQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpAssertQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.assertQuery", - write: HttpAssertQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpFormDataTransformer.ts b/packages/transform/src/features/http/HttpFormDataTransformer.ts deleted file mode 100644 index 55ed9a6304b..00000000000 --- a/packages/transform/src/features/http/HttpFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.formdata", - write: HttpFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpHeadersTransformer.ts b/packages/transform/src/features/http/HttpHeadersTransformer.ts deleted file mode 100644 index 1215dff3e50..00000000000 --- a/packages/transform/src/features/http/HttpHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.headers", - write: HttpHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpIsFormDataTransformer.ts b/packages/transform/src/features/http/HttpIsFormDataTransformer.ts deleted file mode 100644 index 1fa4f0af14d..00000000000 --- a/packages/transform/src/features/http/HttpIsFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpIsFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpIsFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.isFormData", - write: HttpIsFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpIsHeadersTransformer.ts b/packages/transform/src/features/http/HttpIsHeadersTransformer.ts deleted file mode 100644 index 632122b206b..00000000000 --- a/packages/transform/src/features/http/HttpIsHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpIsHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpIsHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.isHeaders", - write: HttpIsHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpIsQueryTransformer.ts b/packages/transform/src/features/http/HttpIsQueryTransformer.ts deleted file mode 100644 index 0585d6d379c..00000000000 --- a/packages/transform/src/features/http/HttpIsQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpIsQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpIsQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.isQuery", - write: HttpIsQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpParameterTransformer.ts b/packages/transform/src/features/http/HttpParameterTransformer.ts deleted file mode 100644 index ef87d22abbe..00000000000 --- a/packages/transform/src/features/http/HttpParameterTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpParameterProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpParameterTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.parameter", - write: HttpParameterProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpQueryTransformer.ts b/packages/transform/src/features/http/HttpQueryTransformer.ts deleted file mode 100644 index da0131f3dd0..00000000000 --- a/packages/transform/src/features/http/HttpQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.query", - write: HttpQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpValidateFormDataTransformer.ts b/packages/transform/src/features/http/HttpValidateFormDataTransformer.ts deleted file mode 100644 index 22fc10c40ac..00000000000 --- a/packages/transform/src/features/http/HttpValidateFormDataTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpValidateFormDataProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpValidateFormDataTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.validateFormData", - write: HttpValidateFormDataProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpValidateHeadersTransformer.ts b/packages/transform/src/features/http/HttpValidateHeadersTransformer.ts deleted file mode 100644 index 79acc785b7e..00000000000 --- a/packages/transform/src/features/http/HttpValidateHeadersTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpValidateHeadersProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpValidateHeadersTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.validateHeaders", - write: HttpValidateHeadersProgrammer.write, - }); -} diff --git a/packages/transform/src/features/http/HttpValidateQueryTransformer.ts b/packages/transform/src/features/http/HttpValidateQueryTransformer.ts deleted file mode 100644 index 9aea04cf2eb..00000000000 --- a/packages/transform/src/features/http/HttpValidateQueryTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpValidateQueryProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace HttpValidateQueryTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "http.validateQuery", - write: HttpValidateQueryProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonApplicationTransformer.ts b/packages/transform/src/features/json/JsonApplicationTransformer.ts deleted file mode 100644 index 8fec13c6873..00000000000 --- a/packages/transform/src/features/json/JsonApplicationTransformer.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - JsonApplicationProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace JsonApplicationTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.json.application", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - const version: "3.0" | "3.1" = get_parameter<"3.0" | "3.1">({ - checker: props.context.checker, - name: "Version", - is: (str) => str === "3.0" || str === "3.1", - cast: (str) => str as "3.0" | "3.1", - default: () => "3.1", - })(props.expression.typeArguments[1]); - - // GET TYPE - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - const collection: MetadataCollection = new MetadataCollection({ - replace: MetadataCollection.replace, - }); - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: true, - constant: true, - absorb: false, - functional: true, - validate: JsonApplicationProgrammer.validate, - }, - components: collection, - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.json.application", - errors: result.errors, - }); - - // GENERATE JSON APPLICATION - return JsonApplicationProgrammer.write({ - context: props.context, - version, - metadata: result.data, - }); - }; - - const get_parameter = - (props: { - checker: ts.TypeChecker; - name: string; - is: (value: string) => boolean; - cast: (value: string) => Value; - default: () => Value; - }) => - (node: ts.TypeNode | undefined): Value => { - if (!node) return props.default(); - - // CHECK LITERAL TYPE - const type: ts.Type = props.checker.getTypeFromTypeNode(node); - if ( - !type.isLiteral() && - (type.getFlags() & ts.TypeFlags.BooleanLiteral) === 0 - ) - throw new TransformerError({ - code: "typia.json.application", - message: `generic argument "${props.name}" must be constant.`, - }); - - // GET VALUE AND VALIDATE IT - const value = type.isLiteral() - ? type.value - : props.checker.typeToString(type); - if (typeof value !== "string" || props.is(value) === false) - throw new TransformerError({ - code: "typia.json.application", - message: `invalid value on generic argument "${props.name}".`, - }); - return props.cast(value); - }; -} diff --git a/packages/transform/src/features/json/JsonAssertParseTransformer.ts b/packages/transform/src/features/json/JsonAssertParseTransformer.ts deleted file mode 100644 index 4e92c519cae..00000000000 --- a/packages/transform/src/features/json/JsonAssertParseTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonAssertParseProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonAssertParseTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.assertParse", - write: JsonAssertParseProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonAssertStringifyTransformer.ts b/packages/transform/src/features/json/JsonAssertStringifyTransformer.ts deleted file mode 100644 index a887a384ee3..00000000000 --- a/packages/transform/src/features/json/JsonAssertStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonAssertStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonAssertStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.assertStringify", - write: JsonAssertStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateAssertParseTransformer.ts b/packages/transform/src/features/json/JsonCreateAssertParseTransformer.ts deleted file mode 100644 index 00d98ebe5ad..00000000000 --- a/packages/transform/src/features/json/JsonCreateAssertParseTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonAssertParseProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateAssertParseTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.createAssertParse", - write: JsonAssertParseProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateAssertStringifyTransformer.ts b/packages/transform/src/features/json/JsonCreateAssertStringifyTransformer.ts deleted file mode 100644 index 7f9e1bc31fc..00000000000 --- a/packages/transform/src/features/json/JsonCreateAssertStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonAssertStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateAssertStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.createAssertStringify", - write: JsonAssertStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateIsParseTransformer.ts b/packages/transform/src/features/json/JsonCreateIsParseTransformer.ts deleted file mode 100644 index 45b139acfc8..00000000000 --- a/packages/transform/src/features/json/JsonCreateIsParseTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonIsParseProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateIsParseTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.createIsParse", - write: JsonIsParseProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateIsStringifyTransformer.ts b/packages/transform/src/features/json/JsonCreateIsStringifyTransformer.ts deleted file mode 100644 index 8a9a6a6c011..00000000000 --- a/packages/transform/src/features/json/JsonCreateIsStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonIsStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateIsStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.stringify", - write: JsonIsStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateStringifyTransformer.ts b/packages/transform/src/features/json/JsonCreateStringifyTransformer.ts deleted file mode 100644 index bd9baea41bf..00000000000 --- a/packages/transform/src/features/json/JsonCreateStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.createStringify", - write: JsonStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateValidateParseTransformer.ts b/packages/transform/src/features/json/JsonCreateValidateParseTransformer.ts deleted file mode 100644 index 65021695372..00000000000 --- a/packages/transform/src/features/json/JsonCreateValidateParseTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonValidateParseProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateValidateParseTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.createValidateParse", - write: JsonValidateParseProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonCreateValidateStringifyProgrammer.ts b/packages/transform/src/features/json/JsonCreateValidateStringifyProgrammer.ts deleted file mode 100644 index 0506830e85c..00000000000 --- a/packages/transform/src/features/json/JsonCreateValidateStringifyProgrammer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonValidateStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonCreateValidateStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "json.createValidateStringify", - write: JsonValidateStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonIsParseTransformer.ts b/packages/transform/src/features/json/JsonIsParseTransformer.ts deleted file mode 100644 index ce809875d39..00000000000 --- a/packages/transform/src/features/json/JsonIsParseTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonIsParseProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonIsParseTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.isParse", - write: JsonIsParseProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonIsStringifyTransformer.ts b/packages/transform/src/features/json/JsonIsStringifyTransformer.ts deleted file mode 100644 index 0395d9e6b10..00000000000 --- a/packages/transform/src/features/json/JsonIsStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonIsStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonIsStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.isStringify", - write: JsonIsStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonSchemaTransformer.ts b/packages/transform/src/features/json/JsonSchemaTransformer.ts deleted file mode 100644 index fc4e3300724..00000000000 --- a/packages/transform/src/features/json/JsonSchemaTransformer.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { - JsonSchemaProgrammer, - JsonSchemasProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace JsonSchemaTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.json.schema", - message: "no generic argument.", - }); - - //---- - // GET ARGUMENTS - //---- - // VALIDATE TUPLE ARGUMENTS - const top: ts.Node | undefined = props.expression.typeArguments[0]; - if (top === undefined || ts.isTypeNode(top) === false) - return props.expression; - - // GET TYPES - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.json.schema", - message: "non-specified generic argument.", - }); - - // ADDITIONAL PARAMETERS - const version: "3.0" | "3.1" = getParameter<"3.0" | "3.1">({ - checker: props.context.checker, - name: "Version", - is: (str) => str === "3.0" || str === "3.1", - cast: (str) => str as "3.0" | "3.1", - default: () => "3.1", - })(props.expression.typeArguments[1]); - - //---- - // GENERATORS - //---- - // METADATA - const analyze = (validate: boolean): MetadataSchema => { - const results: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - constant: true, - escape: true, - validate: - validate === true ? JsonSchemasProgrammer.validate : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (results.success === false) - throw TransformerError.from({ - code: "typia.json.schema", - errors: results.errors, - }); - return results.data; - }; - analyze(true); - - // APPLICATION - return JsonSchemaProgrammer.write({ - context: props.context, - version, - metadata: analyze(false), - }); - }; - - const getParameter = - (props: { - checker: ts.TypeChecker; - name: string; - is: (value: string) => boolean; - cast: (value: string) => Value; - default: () => Value; - }) => - (node: ts.TypeNode | undefined): Value => { - if (!node) return props.default(); - - // CHECK LITERAL TYPE - const type: ts.Type = props.checker.getTypeFromTypeNode(node); - if ( - !type.isLiteral() && - (type.getFlags() & ts.TypeFlags.BooleanLiteral) === 0 - ) - throw new TransformerError({ - code: "typia.json.schema", - message: `generic argument "${props.name}" must be constant.`, - }); - - // GET VALUE AND VALIDATE IT - const value = type.isLiteral() - ? type.value - : props.checker.typeToString(type); - if (typeof value !== "string" || props.is(value) === false) - throw new TransformerError({ - code: "typia.json.schema", - message: `invalid value on generic argument "${props.name}".`, - }); - return props.cast(value); - }; -} diff --git a/packages/transform/src/features/json/JsonSchemasTransformer.ts b/packages/transform/src/features/json/JsonSchemasTransformer.ts deleted file mode 100644 index cdedaaae5fc..00000000000 --- a/packages/transform/src/features/json/JsonSchemasTransformer.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { - JsonSchemasProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace JsonSchemasTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.json.schemas", - message: "no generic argument.", - }); - - //---- - // GET ARGUMENTS - //---- - // VALIDATE TUPLE ARGUMENTS - const top: ts.Node = props.expression.typeArguments[0]!; - if (!ts.isTupleTypeNode(top)) return props.expression; - else if (top.elements.some((child) => !ts.isTypeNode(child))) - return props.expression; - - // GET TYPES - const types: ts.Type[] = top.elements.map((child) => - props.context.checker.getTypeFromTypeNode(child as ts.TypeNode), - ); - if (types.some((t) => t.isTypeParameter())) - throw new TransformerError({ - code: "typia.json.schemas", - message: "non-specified generic argument(s).", - }); - - // ADDITIONAL PARAMETERS - const version: "3.0" | "3.1" = getParameter<"3.0" | "3.1">({ - checker: props.context.checker, - name: "Version", - is: (str) => str === "3.0" || str === "3.1", - cast: (str) => str as "3.0" | "3.1", - default: () => "3.1", - })(props.expression.typeArguments[1]); - - //---- - // GENERATORS - //---- - // METADATA - const analyze = (validate: boolean): MetadataSchema[] => { - const results: ValidationPipe[] = - types.map((type) => - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - constant: true, - escape: true, - validate: - validate === true ? JsonSchemasProgrammer.validate : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }), - ); - const metadatas: MetadataSchema[] = []; - const errors: MetadataFactory.IError[] = []; - for (const r of results) { - if (r.success === false) errors.push(...r.errors); - else metadatas.push(r.data); - } - if (errors.length) - throw TransformerError.from({ - code: "typia.json.schemas", - errors, - }); - return metadatas; - }; - analyze(true); - - // APPLICATION - return JsonSchemasProgrammer.write({ - context: props.context, - version, - metadatas: analyze(false), - }); - }; - - const getParameter = - (props: { - checker: ts.TypeChecker; - name: string; - is: (value: string) => boolean; - cast: (value: string) => Value; - default: () => Value; - }) => - (node: ts.TypeNode | undefined): Value => { - if (!node) return props.default(); - - // CHECK LITERAL TYPE - const type: ts.Type = props.checker.getTypeFromTypeNode(node); - if ( - !type.isLiteral() && - (type.getFlags() & ts.TypeFlags.BooleanLiteral) === 0 - ) - throw new TransformerError({ - code: "typia.json.schemas", - message: `generic argument "${props.name}" must be constant.`, - }); - - // GET VALUE AND VALIDATE IT - const value = type.isLiteral() - ? type.value - : props.checker.typeToString(type); - if (typeof value !== "string" || props.is(value) === false) - throw new TransformerError({ - code: "typia.json.schemas", - message: `invalid value on generic argument "${props.name}".`, - }); - return props.cast(value); - }; -} diff --git a/packages/transform/src/features/json/JsonStringifyTransformer.ts b/packages/transform/src/features/json/JsonStringifyTransformer.ts deleted file mode 100644 index d84bf35eb56..00000000000 --- a/packages/transform/src/features/json/JsonStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.stringify", - write: JsonStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonValidateParseTransformer.ts b/packages/transform/src/features/json/JsonValidateParseTransformer.ts deleted file mode 100644 index 1f7771aa926..00000000000 --- a/packages/transform/src/features/json/JsonValidateParseTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonValidateParseProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonValidateParseTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.validateParse", - write: JsonValidateParseProgrammer.write, - }); -} diff --git a/packages/transform/src/features/json/JsonValidateStringifyTransformer.ts b/packages/transform/src/features/json/JsonValidateStringifyTransformer.ts deleted file mode 100644 index c95671d7f58..00000000000 --- a/packages/transform/src/features/json/JsonValidateStringifyTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonValidateStringifyProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace JsonValidateStringifyTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "json.validateStringify", - write: JsonValidateStringifyProgrammer.write, - }); -} diff --git a/packages/transform/src/features/llm/LlmApplicationTransformer.ts b/packages/transform/src/features/llm/LlmApplicationTransformer.ts deleted file mode 100644 index cefa5e0e4cc..00000000000 --- a/packages/transform/src/features/llm/LlmApplicationTransformer.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - LlmApplicationProgrammer, - LlmMetadataFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmApplicationTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.application", - message: "no generic argument.", - }); - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET CONFIG - const config: - | Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - > - | undefined = LlmMetadataFactory.getConfig({ - context: props.context, - method: "application", - node: props.expression.typeArguments[1], - }); - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - functional: true, - validate: - validate === true - ? (next) => - LlmApplicationProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - top: next.top, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.application", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE LLM APPLICATION - return LlmApplicationProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: analyze(false), - name: top.getFullText().trim(), - config, - configArgument: - props.expression.arguments?.[0] !== undefined - ? props.expression.arguments[0] - : undefined, - }); - }; - -} diff --git a/packages/transform/src/features/llm/LlmCoerceTransformer.ts b/packages/transform/src/features/llm/LlmCoerceTransformer.ts deleted file mode 100644 index 7908046b758..00000000000 --- a/packages/transform/src/features/llm/LlmCoerceTransformer.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - LlmCoerceProgrammer, - LlmMetadataFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmCoerceTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // CHECK PARAMETER - if (props.expression.arguments.length === 0) - throw new TransformerError({ - code: "typia.llm.coerce", - message: "no input value.", - }); - - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.coerce", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE AND CONFIG - const config: Partial = LlmMetadataFactory.getConfig({ - context: props.context, - method: "coerce", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.llm.coerce", - message: "non-specified generic argument.", - }); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - validate: - validate === true - ? (next) => - LlmCoerceProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.coerce", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE CODE - return ts.factory.createCallExpression( - LlmCoerceProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: analyze(false), - name: (top as ts.TypeNode).getFullText().trim(), - config, - }), - undefined, - props.expression.arguments, - ); - }; -} diff --git a/packages/transform/src/features/llm/LlmControllerTransformer.ts b/packages/transform/src/features/llm/LlmControllerTransformer.ts deleted file mode 100644 index b87bf98efeb..00000000000 --- a/packages/transform/src/features/llm/LlmControllerTransformer.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - LlmApplicationProgrammer, - LlmControllerProgrammer, - LlmMetadataFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmControllerTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.controller", - message: "no generic argument.", - }); - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - if (props.expression.arguments[0] === undefined) - throw new TransformerError({ - code: "typia.llm.controller", - message: "no identifier name.", - }); - if (props.expression.arguments[1] === undefined) - throw new TransformerError({ - code: "typia.llm.controller", - message: "no executor.", - }); - - // GET CONFIG - const config: - | Partial< - ILlmSchema.IConfig & { - equals: boolean; - } - > - | undefined = LlmMetadataFactory.getConfig({ - context: props.context, - method: "controller", - node: props.expression.typeArguments[1], - }); - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - functional: true, - validate: - validate === true - ? (next) => - LlmApplicationProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - top: next.top, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.controller", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE LLM CONTROLLER - return LlmControllerProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: analyze(false), - className: top.getFullText().trim(), - config, - node: top, - nameArgument: props.expression.arguments[0], - executeArgument: props.expression.arguments[1], - configArgument: props.expression.arguments[2], - }); - }; -} diff --git a/packages/transform/src/features/llm/LlmCreateCoerceTransformer.ts b/packages/transform/src/features/llm/LlmCreateCoerceTransformer.ts deleted file mode 100644 index c5831f4a99b..00000000000 --- a/packages/transform/src/features/llm/LlmCreateCoerceTransformer.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - LlmCoerceProgrammer, - LlmMetadataFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmCreateCoerceTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.createCoerce", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE AND CONFIG - const config: Partial = LlmMetadataFactory.getConfig({ - context: props.context, - method: "createCoerce", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.llm.createCoerce", - message: "non-specified generic argument.", - }); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - validate: - validate === true - ? (next) => - LlmCoerceProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.createCoerce", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE CODE (factory returns the function directly) - return LlmCoerceProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: analyze(false), - name: (top as ts.TypeNode).getFullText().trim(), - config, - }); - }; -} diff --git a/packages/transform/src/features/llm/LlmCreateParseTransformer.ts b/packages/transform/src/features/llm/LlmCreateParseTransformer.ts deleted file mode 100644 index c6fa4f152ac..00000000000 --- a/packages/transform/src/features/llm/LlmCreateParseTransformer.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - LlmMetadataFactory, - LlmParseProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmCreateParseTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.createParse", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE AND CONFIG - const config: Partial = LlmMetadataFactory.getConfig({ - context: props.context, - method: "createParse", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.llm.createParse", - message: "non-specified generic argument.", - }); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - validate: - validate === true - ? (next) => - LlmParseProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.createParse", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE CODE (factory returns the function directly) - return LlmParseProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: analyze(false), - name: (top as ts.TypeNode).getFullText().trim(), - config, - }); - }; -} diff --git a/packages/transform/src/features/llm/LlmParametersTransformer.ts b/packages/transform/src/features/llm/LlmParametersTransformer.ts deleted file mode 100644 index 3a56f679135..00000000000 --- a/packages/transform/src/features/llm/LlmParametersTransformer.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - LlmMetadataFactory, - LlmParametersProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmParametersTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.parameters", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE - const config: Partial = LlmMetadataFactory.getConfig({ - context: props.context, - method: "parameters", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - validate: - validate === true - ? (next) => - LlmParametersProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.parameters", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE LLM PARAMETERS SCHEMA - return LlmParametersProgrammer.write({ - context: props.context, - metadata: analyze(false), - config, - }); - }; -} diff --git a/packages/transform/src/features/llm/LlmParseTransformer.ts b/packages/transform/src/features/llm/LlmParseTransformer.ts deleted file mode 100644 index cf97a9392c1..00000000000 --- a/packages/transform/src/features/llm/LlmParseTransformer.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - LlmMetadataFactory, - LlmParseProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmParseTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // CHECK PARAMETER - if (props.expression.arguments.length === 0) - throw new TransformerError({ - code: "typia.llm.parse", - message: "no input value.", - }); - - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.parse", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE AND CONFIG - const config: Partial = LlmMetadataFactory.getConfig({ - context: props.context, - method: "parse", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.llm.parse", - message: "non-specified generic argument.", - }); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - validate: - validate === true - ? (next) => - LlmParseProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.parse", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE CODE - return ts.factory.createCallExpression( - LlmParseProgrammer.write({ - context: props.context, - modulo: props.modulo, - metadata: analyze(false), - name: (top as ts.TypeNode).getFullText().trim(), - config, - }), - undefined, - props.expression.arguments, - ); - }; -} diff --git a/packages/transform/src/features/llm/LlmSchemaTransformer.ts b/packages/transform/src/features/llm/LlmSchemaTransformer.ts deleted file mode 100644 index 92598830bbb..00000000000 --- a/packages/transform/src/features/llm/LlmSchemaTransformer.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - LlmMetadataFactory, - LlmSchemaProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmSchemaTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.schema", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE - const config: Partial = LlmMetadataFactory.getConfig({ - context: props.context, - method: "schema", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - constant: true, - escape: true, - validate: - validate === true - ? (next) => - LlmSchemaProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.schema", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE LLM SCHEMA - const expr = LlmSchemaProgrammer.write({ - context: props.context, - metadata: analyze(false), - config, - }); - - // If user provided $defs argument and expression is a function, call it with $defs - if ( - props.expression.arguments?.[0] !== undefined && - ts.isArrowFunction(expr) - ) { - return ts.factory.createCallExpression(expr, undefined, [ - props.expression.arguments[0]!, - ]); - } - - return expr; - }; -} diff --git a/packages/transform/src/features/llm/LlmStructuredOutputTransformer.ts b/packages/transform/src/features/llm/LlmStructuredOutputTransformer.ts deleted file mode 100644 index e53f550e89a..00000000000 --- a/packages/transform/src/features/llm/LlmStructuredOutputTransformer.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - LlmMetadataFactory, - LlmStructuredOutputProgrammer, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ILlmSchema, ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace LlmStructuredOutputTransformer { - export const transform = (props: ITransformProps): ts.Expression => { - // GET GENERIC ARGUMENT - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.llm.structuredOutput", - message: "no generic argument.", - }); - - const top: ts.Node = props.expression.typeArguments[0]!; - if (ts.isTypeNode(top) === false) return props.expression; - - // GET TYPE AND CONFIG - const config: Partial = - LlmMetadataFactory.getConfig({ - context: props.context, - method: "structuredOutput", - node: props.expression.typeArguments[1], - }) as Partial; - - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.llm.structuredOutput", - message: "non-specified generic argument.", - }); - - // VALIDATE TYPE - const analyze = (validate: boolean): MetadataSchema => { - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: validate, - escape: true, - constant: true, - validate: - validate === true - ? (next) => - LlmStructuredOutputProgrammer.validate({ - config, - metadata: next.metadata, - explore: next.explore, - }) - : undefined, - }, - components: new MetadataCollection({ - replace: MetadataCollection.replace, - }), - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.llm.structuredOutput", - errors: result.errors, - }); - return result.data; - }; - analyze(true); - - // GENERATE CODE - return LlmStructuredOutputProgrammer.write({ - context: props.context, - modulo: props.modulo, - type, - metadata: analyze(false), - config, - name: (top as ts.TypeNode).getFullText().trim(), - }); - }; -} diff --git a/packages/transform/src/features/misc/MiscAssertCloneTransformer.ts b/packages/transform/src/features/misc/MiscAssertCloneTransformer.ts deleted file mode 100644 index 2dc7d171918..00000000000 --- a/packages/transform/src/features/misc/MiscAssertCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscAssertCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscAssertCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.assertClone", - write: MiscAssertCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscAssertPruneTransformer.ts b/packages/transform/src/features/misc/MiscAssertPruneTransformer.ts deleted file mode 100644 index 3e2f8ea4dbf..00000000000 --- a/packages/transform/src/features/misc/MiscAssertPruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscAssertPruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscAssertPruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.assertPrune", - write: MiscAssertPruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCloneTransformer.ts b/packages/transform/src/features/misc/MiscCloneTransformer.ts deleted file mode 100644 index 9ba9554a19e..00000000000 --- a/packages/transform/src/features/misc/MiscCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.clone", - write: MiscCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateAssertCloneTransformer.ts b/packages/transform/src/features/misc/MiscCreateAssertCloneTransformer.ts deleted file mode 100644 index 80865548ff4..00000000000 --- a/packages/transform/src/features/misc/MiscCreateAssertCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscAssertCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateAssertCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createAssertClone", - write: MiscAssertCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateAssertPruneTransformer.ts b/packages/transform/src/features/misc/MiscCreateAssertPruneTransformer.ts deleted file mode 100644 index 9c84ce497fd..00000000000 --- a/packages/transform/src/features/misc/MiscCreateAssertPruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscAssertPruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateAssertPruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createAssertPrune", - write: MiscAssertPruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateCloneTransformer.ts b/packages/transform/src/features/misc/MiscCreateCloneTransformer.ts deleted file mode 100644 index 1fdce4fb434..00000000000 --- a/packages/transform/src/features/misc/MiscCreateCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createClone", - write: MiscCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateIsCloneTransformer.ts b/packages/transform/src/features/misc/MiscCreateIsCloneTransformer.ts deleted file mode 100644 index fb2404128db..00000000000 --- a/packages/transform/src/features/misc/MiscCreateIsCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscIsCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateIsCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createIsClone", - write: MiscIsCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateIsPruneTransformer.ts b/packages/transform/src/features/misc/MiscCreateIsPruneTransformer.ts deleted file mode 100644 index 689a58138f1..00000000000 --- a/packages/transform/src/features/misc/MiscCreateIsPruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscIsPruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateIsPruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createIsPrune", - write: MiscIsPruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreatePruneTransformer.ts b/packages/transform/src/features/misc/MiscCreatePruneTransformer.ts deleted file mode 100644 index 4a99eff04c6..00000000000 --- a/packages/transform/src/features/misc/MiscCreatePruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscPruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreatePruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createPrune", - write: MiscPruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateValidateCloneTransformer.ts b/packages/transform/src/features/misc/MiscCreateValidateCloneTransformer.ts deleted file mode 100644 index 3dec7c8f0b3..00000000000 --- a/packages/transform/src/features/misc/MiscCreateValidateCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscValidateCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateValidateCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createValidateClone", - write: MiscValidateCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscCreateValidatePruneTransformer.ts b/packages/transform/src/features/misc/MiscCreateValidatePruneTransformer.ts deleted file mode 100644 index f889e2dfd6b..00000000000 --- a/packages/transform/src/features/misc/MiscCreateValidatePruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscValidatePruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscCreateValidatePruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "misc.createValidatePrune", - write: MiscValidatePruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscIsCloneTransformer.ts b/packages/transform/src/features/misc/MiscIsCloneTransformer.ts deleted file mode 100644 index 925f7c77047..00000000000 --- a/packages/transform/src/features/misc/MiscIsCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscIsCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscIsCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.isClone", - write: MiscIsCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscIsPruneTransformer.ts b/packages/transform/src/features/misc/MiscIsPruneTransformer.ts deleted file mode 100644 index d3ab6c0b419..00000000000 --- a/packages/transform/src/features/misc/MiscIsPruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscIsPruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscIsPruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.isPrune", - write: MiscIsPruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscLiteralsTransformer.ts b/packages/transform/src/features/misc/MiscLiteralsTransformer.ts deleted file mode 100644 index 7f1bfd37cc9..00000000000 --- a/packages/transform/src/features/misc/MiscLiteralsTransformer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { MiscLiteralsProgrammer } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace MiscLiteralsTransformer { - export const transform = ( - props: Omit, - ): ts.Expression => { - // CHECK GENERIC ARGUMENT EXISTENCE - if (!props.expression.typeArguments?.[0]) - throw new TransformerError({ - code: "typia.misc.literals", - message: "generic argument is not specified.", - }); - - // GET TYPE INFO - const node: ts.TypeNode = props.expression.typeArguments[0]; - const type: ts.Type = props.context.checker.getTypeFromTypeNode(node); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.misc.literals", - message: "non-specified generic argument.", - }); - - // DO TRANSFORM - return MiscLiteralsProgrammer.write({ - context: props.context, - type, - }); - }; -} diff --git a/packages/transform/src/features/misc/MiscPruneTransformer.ts b/packages/transform/src/features/misc/MiscPruneTransformer.ts deleted file mode 100644 index 4eb8acbde28..00000000000 --- a/packages/transform/src/features/misc/MiscPruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscPruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscPruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.prune", - write: MiscPruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscValidateCloneTransformer.ts b/packages/transform/src/features/misc/MiscValidateCloneTransformer.ts deleted file mode 100644 index e0750d0d563..00000000000 --- a/packages/transform/src/features/misc/MiscValidateCloneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscValidateCloneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscValidateCloneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.validateClone", - write: MiscValidateCloneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/misc/MiscValidatePruneTransformer.ts b/packages/transform/src/features/misc/MiscValidatePruneTransformer.ts deleted file mode 100644 index 1425bec97cf..00000000000 --- a/packages/transform/src/features/misc/MiscValidatePruneTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MiscValidatePruneProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace MiscValidatePruneTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "misc.validatePrune", - write: MiscValidatePruneProgrammer.write, - }); -} diff --git a/packages/transform/src/features/notations/NotationAssertGeneralTransformer.ts b/packages/transform/src/features/notations/NotationAssertGeneralTransformer.ts deleted file mode 100644 index c32dd7ef115..00000000000 --- a/packages/transform/src/features/notations/NotationAssertGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationAssertGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationAssertGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: `notations.assert${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationAssertGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationCreateAssertGeneralTransformer.ts b/packages/transform/src/features/notations/NotationCreateAssertGeneralTransformer.ts deleted file mode 100644 index e3dcb3fa6a2..00000000000 --- a/packages/transform/src/features/notations/NotationCreateAssertGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationAssertGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationCreateAssertGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: `notations.createAssert${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationAssertGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationCreateGeneralTransformer.ts b/packages/transform/src/features/notations/NotationCreateGeneralTransformer.ts deleted file mode 100644 index 2a2912f9c17..00000000000 --- a/packages/transform/src/features/notations/NotationCreateGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationCreateGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: `notations.create${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationCreateIsGeneralTransformer.ts b/packages/transform/src/features/notations/NotationCreateIsGeneralTransformer.ts deleted file mode 100644 index e78d0771942..00000000000 --- a/packages/transform/src/features/notations/NotationCreateIsGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationIsGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationCreateIsGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: `notations.createIs${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationIsGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationCreateValidateGeneralTransformer.ts b/packages/transform/src/features/notations/NotationCreateValidateGeneralTransformer.ts deleted file mode 100644 index 762fbbf7d21..00000000000 --- a/packages/transform/src/features/notations/NotationCreateValidateGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationValidateGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationCreateValidateGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: `notations.createValidate${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationValidateGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationGeneralTransformer.ts b/packages/transform/src/features/notations/NotationGeneralTransformer.ts deleted file mode 100644 index 7747da4972f..00000000000 --- a/packages/transform/src/features/notations/NotationGeneralTransformer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NotationGeneralProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: `notations.${rename.name}`, - write: (x) => - NotationGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationIsGeneralTransformer.ts b/packages/transform/src/features/notations/NotationIsGeneralTransformer.ts deleted file mode 100644 index 170bed1a06d..00000000000 --- a/packages/transform/src/features/notations/NotationIsGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationIsGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationIsGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: `notations.is${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationIsGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/notations/NotationValidateGeneralTransformer.ts b/packages/transform/src/features/notations/NotationValidateGeneralTransformer.ts deleted file mode 100644 index 809b7e16e4d..00000000000 --- a/packages/transform/src/features/notations/NotationValidateGeneralTransformer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NotationValidateGeneralProgrammer } from "@typia/core"; -import { NamingConvention } from "@typia/utils"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace NotationValidateGeneralTransformer { - export const transform = - (rename: (str: string) => string) => (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: `notations.validate${NamingConvention.capitalize(rename.name)}`, - write: (x) => - NotationValidateGeneralProgrammer.write({ - ...x, - rename, - }), - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufAssertDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufAssertDecodeTransformer.ts deleted file mode 100644 index d7e8274cb3a..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufAssertDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufAssertDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufAssertDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.assertDecode", - write: ProtobufAssertDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufAssertEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufAssertEncodeTransformer.ts deleted file mode 100644 index 7619ef0b852..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufAssertEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufAssertEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufAssertEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.assertEncode", - write: ProtobufAssertEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateAssertDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateAssertDecodeTransformer.ts deleted file mode 100644 index b3a6e6709b5..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateAssertDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufAssertDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateAssertDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createAssertDecode", - write: ProtobufAssertDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateAssertEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateAssertEncodeTransformer.ts deleted file mode 100644 index 9d09c28029d..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateAssertEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufAssertEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateAssertEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createAssertEncode", - write: ProtobufAssertEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateDecodeTransformer.ts deleted file mode 100644 index 835ff2427f8..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createDecode", - write: ProtobufDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateEncodeTransformer.ts deleted file mode 100644 index f619ea3c2c8..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createEncode", - write: ProtobufEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateIsDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateIsDecodeTransformer.ts deleted file mode 100644 index df941a198c5..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateIsDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufIsDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateIsDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createIsDecode", - write: ProtobufIsDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateIsEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateIsEncodeTransformer.ts deleted file mode 100644 index 3dd500306a6..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateIsEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufIsEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateIsEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createIsEncode", - write: ProtobufIsEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateValidateDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateValidateDecodeTransformer.ts deleted file mode 100644 index 297521bb821..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateValidateDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufValidateDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateValidateDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createValidateDecode", - write: ProtobufValidateDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufCreateValidateEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufCreateValidateEncodeTransformer.ts deleted file mode 100644 index fe5f82fb525..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufCreateValidateEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufValidateEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufCreateValidateEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.factory({ - ...props, - method: "protobuf.createValidateEncode", - write: ProtobufValidateEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufDecodeTransformer.ts deleted file mode 100644 index 7568b20b625..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.decode", - write: ProtobufDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufEncodeTransformer.ts deleted file mode 100644 index eefbc8d2f15..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.encode", - write: ProtobufEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufIsDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufIsDecodeTransformer.ts deleted file mode 100644 index d9e6142b867..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufIsDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufIsDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufIsDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.isDecode", - write: ProtobufIsDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufIsEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufIsEncodeTransformer.ts deleted file mode 100644 index 7577042adca..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufIsEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufIsEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufIsEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.isEncode", - write: ProtobufIsEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufMessageTransformer.ts b/packages/transform/src/features/protobuf/ProtobufMessageTransformer.ts deleted file mode 100644 index d20c5ac6a2c..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufMessageTransformer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ProtobufMessageProgrammer } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace ProtobufMessageTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - // CHECK GENERIC ARGUMENT EXISTENCE - if (!props.expression.typeArguments || !props.expression.typeArguments[0]) - throw new TransformerError({ - code: "typia.protobuf.message", - message: "generic argument is not specified.", - }); - - // GET TYPE INFO - const type: ts.Type = props.context.checker.getTypeFromTypeNode( - props.expression.typeArguments[0], - ); - if (type.isTypeParameter()) - throw new TransformerError({ - code: "tyipa.protobuf.message", - message: "non-specified generic argument.", - }); - - // DO TRANSFORM - return ProtobufMessageProgrammer.write({ - context: props.context, - type, - }); - }; -} diff --git a/packages/transform/src/features/protobuf/ProtobufValidateDecodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufValidateDecodeTransformer.ts deleted file mode 100644 index 625edc1c4ed..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufValidateDecodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufValidateDecodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufValidateDecodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.validateDecode", - write: ProtobufValidateDecodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/protobuf/ProtobufValidateEncodeTransformer.ts b/packages/transform/src/features/protobuf/ProtobufValidateEncodeTransformer.ts deleted file mode 100644 index 1168c05adf8..00000000000 --- a/packages/transform/src/features/protobuf/ProtobufValidateEncodeTransformer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ProtobufValidateEncodeProgrammer } from "@typia/core"; - -import { ITransformProps } from "../../ITransformProps"; -import { GenericTransformer } from "../../internal/GenericTransformer"; - -export namespace ProtobufValidateEncodeTransformer { - export const transform = (props: ITransformProps) => - GenericTransformer.scalar({ - ...props, - method: "protobuf.validateEncode", - write: ProtobufValidateEncodeProgrammer.write, - }); -} diff --git a/packages/transform/src/features/reflect/ReflectMetadataTransformer.ts b/packages/transform/src/features/reflect/ReflectMetadataTransformer.ts deleted file mode 100644 index a2bf0f0dd6c..00000000000 --- a/packages/transform/src/features/reflect/ReflectMetadataTransformer.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - LiteralFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { IMetadataSchemaCollection } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace ReflectMetadataTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.reflect.metadata", - message: "no generic argument.", - }); - - // VALIDATE TUPLE ARGUMENTS - const top: ts.Node = props.expression.typeArguments[0]!; - if (!ts.isTupleTypeNode(top)) return props.expression; - else if (top.elements.some((child) => !ts.isTypeNode(child))) - return props.expression; - - // GET TYPES - const types: ts.Type[] = top.elements.map((child) => - props.context.checker.getTypeFromTypeNode(child as ts.TypeNode), - ); - if (types.some((t) => t.isTypeParameter())) - throw new TransformerError({ - code: "typia.reflect.metadata", - message: "non-specified generic argument(s).", - }); - - // METADATA - const components: MetadataCollection = new MetadataCollection(); - const schemas: Array = types.map((type) => { - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: true, - constant: true, - absorb: true, - functional: true, - }, - components: components, - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.reflect.metadata", - errors: result.errors, - }); - return result.data; - }); - - // CONVERT TO PRIMITIVE TYPE - const collection: IMetadataSchemaCollection = { - schemas: schemas.map((s) => s.toJSON()), - components: components.toJSON(), - }; - return LiteralFactory.write(collection); - }; -} diff --git a/packages/transform/src/features/reflect/ReflectNameTransformer.ts b/packages/transform/src/features/reflect/ReflectNameTransformer.ts deleted file mode 100644 index 2f5df5469d1..00000000000 --- a/packages/transform/src/features/reflect/ReflectNameTransformer.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - ITypiaContext, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { ValidationPipe } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace ReflectNameTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.reflect.metadata", - message: "no generic argument.", - }); - const top: ts.Node = props.expression.typeArguments[0]!; - const regular: boolean = (() => { - // CHECK SECOND ARGUMENT EXISTENCE - const second: ts.Node | undefined = props.expression.typeArguments[1]!; - if (second === undefined) return false; - - // GET BOOLEAN VALUE - const value: MetadataSchema = getMetadata({ - context: props.context, - node: second, - }); - return value.size() === 1 && - value.constants.length === 1 && - value.constants[0]!.type === "boolean" && - value.constants[0]!.values.length === 1 - ? (value.constants[0]!.values[0]!.value as boolean) - : false; - })(); - - // RETURNS NAME - return ts.factory.createStringLiteral( - regular - ? getMetadata({ - context: props.context, - node: top, - }).getName() - : top.getFullText(), - ); - }; -} - -const getMetadata = (props: { - context: ITypiaContext; - node: ts.Node; -}): MetadataSchema => { - const type: ts.Type = props.context.checker.getTypeFromTypeNode( - props.node as ts.TypeNode, - ); - const collection: MetadataCollection = new MetadataCollection({ - replace: MetadataCollection.replace, - }); - const result: ValidationPipe = - MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - absorb: false, - constant: true, - escape: false, - }, - components: collection, - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.reflect.name", - errors: result.errors, - }); - return result.data; -}; diff --git a/packages/transform/src/features/reflect/ReflectSchemaTransformer.ts b/packages/transform/src/features/reflect/ReflectSchemaTransformer.ts deleted file mode 100644 index 599f65ce67f..00000000000 --- a/packages/transform/src/features/reflect/ReflectSchemaTransformer.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - LiteralFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { IMetadataSchemaUnit } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace ReflectSchemaTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.reflect.schema", - message: "no generic argument.", - }); - - // VALIDATE ARGUMENT - const top: ts.Node | undefined = props.expression.typeArguments[0]; - if (top === undefined || ts.isTypeNode(top) === false) - return props.expression; - - // GET TYPE - const type: ts.Type = props.context.checker.getTypeFromTypeNode(top); - if (type.isTypeParameter()) - throw new TransformerError({ - code: "typia.reflect.schema", - message: "non-specified generic argument.", - }); - - // METADATA - const components: MetadataCollection = new MetadataCollection(); - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: true, - constant: true, - absorb: true, - functional: true, - }, - components: components, - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.reflect.schema", - errors: result.errors, - }); - - const schema: MetadataSchema = result.data; - - // CONVERT TO PRIMITIVE TYPE - const unit: IMetadataSchemaUnit = { - schema: schema.toJSON(), - components: components.toJSON(), - }; - return LiteralFactory.write(unit); - }; -} diff --git a/packages/transform/src/features/reflect/ReflectSchemasTransformer.ts b/packages/transform/src/features/reflect/ReflectSchemasTransformer.ts deleted file mode 100644 index 8158e708865..00000000000 --- a/packages/transform/src/features/reflect/ReflectSchemasTransformer.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - LiteralFactory, - MetadataCollection, - MetadataFactory, - MetadataSchema, -} from "@typia/core"; -import { IMetadataSchemaCollection } from "@typia/interface"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../../ITransformProps"; -import { TransformerError } from "../../TransformerError"; - -export namespace ReflectSchemasTransformer { - export const transform = ( - props: Pick, - ): ts.Expression => { - if (!props.expression.typeArguments?.length) - throw new TransformerError({ - code: "typia.reflect.schemas", - message: "no generic argument.", - }); - - // VALIDATE TUPLE ARGUMENTS - const top: ts.Node = props.expression.typeArguments[0]!; - if (!ts.isTupleTypeNode(top)) return props.expression; - else if (top.elements.some((child) => !ts.isTypeNode(child))) - return props.expression; - - // GET TYPES - const types: ts.Type[] = top.elements.map((child) => - props.context.checker.getTypeFromTypeNode(child as ts.TypeNode), - ); - if (types.some((t) => t.isTypeParameter())) - throw new TransformerError({ - code: "typia.reflect.schemas", - message: "non-specified generic argument(s).", - }); - - // METADATA - const components: MetadataCollection = new MetadataCollection(); - const schemas: Array = types.map((type) => { - const result = MetadataFactory.analyze({ - checker: props.context.checker, - transformer: props.context.transformer, - options: { - escape: true, - constant: true, - absorb: true, - functional: true, - }, - components: components, - type, - }); - if (result.success === false) - throw TransformerError.from({ - code: "typia.reflect.schemas", - errors: result.errors, - }); - return result.data; - }); - - // CONVERT TO PRIMITIVE TYPE - const collection: IMetadataSchemaCollection = { - schemas: schemas.map((s) => s.toJSON()), - components: components.toJSON(), - }; - return LiteralFactory.write(collection); - }; -} diff --git a/packages/transform/src/index.ts b/packages/transform/src/index.ts deleted file mode 100644 index 640e56e27da..00000000000 --- a/packages/transform/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { transform } from "./transform"; - -export default transform; - -export * from "./ImportTransformer"; -export * from "./TypiaGenerator"; -export * from "./transform"; diff --git a/packages/transform/src/internal/GenericTransformer.ts b/packages/transform/src/internal/GenericTransformer.ts deleted file mode 100644 index 89fa291debe..00000000000 --- a/packages/transform/src/internal/GenericTransformer.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { IProgrammerProps } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { ITransformProps } from "../ITransformProps"; -import { TransformerError } from "../TransformerError"; - -export namespace GenericTransformer { - export interface IProps extends ITransformProps { - method: string; - write: (props: IProgrammerProps) => ts.Expression | ts.ArrowFunction; - } - - export const scalar = (props: IProps) => { - // CHECK PARAMETER - if (props.expression.arguments.length === 0) - throw new TransformerError({ - code: `typia.${props.method}`, - message: `no input value.`, - }); - - // GET TYPE INFO - const [type, node, generic]: [ts.Type, ts.Node, boolean] = - props.expression.typeArguments && props.expression.typeArguments[0] - ? [ - props.context.checker.getTypeFromTypeNode( - props.expression.typeArguments[0], - ), - props.expression.typeArguments[0], - true, - ] - : [ - props.context.checker.getTypeAtLocation( - props.expression.arguments[0]!, - ), - props.expression.arguments[0]!, - false, - ]; - if (type.isTypeParameter()) - throw new TransformerError({ - code: `typia.${props.method}`, - message: `non-specified generic argument.`, - }); - - // DO TRANSFORM - return ts.factory.createCallExpression( - props.write({ - context: props.context, - modulo: props.modulo, - type, - name: generic - ? node.getFullText().trim() - : getTypeName({ - checker: props.context.checker, - type, - node, - }), - }), - undefined, - props.expression.arguments, - ); - }; - - export const factory = (props: IProps) => { - // CHECK GENERIC ARGUMENT EXISTENCE - if (!props.expression.typeArguments?.[0]) - throw new TransformerError({ - code: `typia.${props.method}`, - message: `generic argument is not specified.`, - }); - - // GET TYPE INFO - const node: ts.TypeNode = props.expression.typeArguments[0]; - const type: ts.Type = props.context.checker.getTypeFromTypeNode(node); - - if (type.isTypeParameter()) - throw new TransformerError({ - code: `typia.${props.method}`, - message: `non-specified generic argument.`, - }); - - // DO TRANSFORM - return props.write({ - context: props.context, - modulo: props.modulo, - type, - name: node.getFullText().trim(), - init: props.expression.arguments[0], - }); - }; - - const getTypeName = (props: { - checker: ts.TypeChecker; - type: ts.Type; - node: ts.Node; - }): string => - props.checker.typeToString( - props.type, - props.node, - ts.TypeFormatFlags.NodeBuilderFlagsMask, - ); -} diff --git a/packages/transform/src/transform.ts b/packages/transform/src/transform.ts deleted file mode 100644 index fdcc045374f..00000000000 --- a/packages/transform/src/transform.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ITransformOptions, ITypiaContext } from "@typia/core"; -import ts from "@typescript/native-preview"; - -import { FileTransformer } from "./FileTransformer"; - -/** - * TypeScript transformer for typia runtime code generation. - * - * `transform` is the entry point for typia's compile-time transformation. It - * converts `typia.*()` function calls (e.g., `typia.is(data)`) into - * optimized runtime validation, serialization, or transformation code. - * - * The transformer analyzes the generic type parameter `T` at compile time and - * generates specialized code that performs type checking without runtime - * reflection. This approach provides both type safety and high performance. - * - * **Requirements:** - * - * - TypeScript's `strictNullChecks` or `strict` compiler option must be enabled - * - The transformer must be configured in `tsconfig.json` via - * `compilerOptions.plugins` using `typia/lib/ttsc/plugin` - * - * **Configuration example (tsconfig.json):** - * - * ```json - * { - * "compilerOptions": { - * "strict": true, - * "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] - * } - * } - * ``` - * - * @author Jeongho Nam - https://github.com/samchon - * @param program TypeScript program instance from the compiler - * @param options Optional transformer configuration (finite, numeric, - * functional, undefined) - * @param extras Diagnostic utilities for error reporting during transformation - * @returns Transformer factory that processes source files - */ -export const transform = ( - program: ts.Program, - options: ITransformOptions | undefined, - extras: ITypiaContext["extras"], -): ts.TransformerFactory => { - const compilerOptions: ts.CompilerOptions = program.getCompilerOptions(); - const strict: boolean = - compilerOptions.strictNullChecks !== undefined - ? !!compilerOptions.strictNullChecks - : !!compilerOptions.strict; - if (strict === false) - extras.addDiagnostic({ - category: ts.DiagnosticCategory.Error, - code: "(typia)" as any, - file: undefined, - start: undefined, - length: undefined, - messageText: "strict mode is required.", - }); - return FileTransformer.transform({ - program, - compilerOptions, - checker: program.getTypeChecker(), - printer: ts.createPrinter(), - options: options ?? {}, - extras, - }); -}; diff --git a/packages/transform/tsconfig.json b/packages/transform/tsconfig.json deleted file mode 100644 index 31e11708d92..00000000000 --- a/packages/transform/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../config/tsconfig.json", - "compilerOptions": { - "outDir": "lib", - "rootDir": "src" - }, - "include": ["src"], -} diff --git a/packages/typia/.gitignore b/packages/typia/.gitignore index b85d0b65922..53385547d48 100644 --- a/packages/typia/.gitignore +++ b/packages/typia/.gitignore @@ -1,2 +1,2 @@ -bin/ttsc-typia-native -README.md \ No newline at end of file +bin/ +README.md diff --git a/packages/typia/package.json b/packages/typia/package.json index 06e0da3d3fd..3794f056992 100644 --- a/packages/typia/package.json +++ b/packages/typia/package.json @@ -2,36 +2,19 @@ "name": "typia", "version": "12.0.2", "description": "Superfast runtime validators with only one line", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "src/index.ts", + "types": "src/index.ts", "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "default": "./lib/index.js" - }, - "./lib/transform": { - "types": "./lib/transform.d.ts", - "import": "./lib/transform.js", - "default": "./lib/transform.js" - }, - "./lib/ttsc/plugin": { - "types": "./lib/ttsc/plugin.d.ts", - "import": "./lib/ttsc/plugin.js", - "default": "./lib/ttsc/plugin.js" - }, - "./lib/internal/*": { - "types": "./lib/internal/*.d.ts", - "import": "./lib/internal/*.js", - "default": "./lib/internal/*.js" - }, + ".": "./src/index.ts", + "./lib/transform": "./src/transform.ts", + "./lib/internal/*": "./src/internal/*.ts", "./package.json": "./package.json" }, "bin": { "typia": "./src/executable/typia.ts" }, "scripts": { - "build": "rimraf lib && ttsc build && rollup -c && cp ../../README.md README.md", + "build": "rimraf lib && ttsc && rollup -c && cp ../../README.md README.md", "dev": "ttsc --watch", "prepack": "pnpm run build" }, @@ -47,9 +30,7 @@ "homepage": "https://typia.io", "dependencies": { "@standard-schema/spec": "^1.0.0", - "@typia/core": "workspace:^", "@typia/interface": "workspace:^", - "@typia/transform": "workspace:^", "@typia/ttsc": "workspace:^", "@typia/utils": "workspace:^", "commander": "^10.0.0", @@ -79,13 +60,7 @@ "tinyglobby": "^0.2.12" }, "sideEffects": false, - "files": [ - "README.md", - "bin", - "package.json", - "lib", - "src" - ], + "files": ["README.md", "bin", "package.json", "lib", "src"], "keywords": [ "fast", "json", @@ -141,11 +116,6 @@ "import": "./lib/transform.mjs", "default": "./lib/transform.js" }, - "./lib/ttsc/plugin": { - "types": "./lib/ttsc/plugin.d.ts", - "import": "./lib/ttsc/plugin.mjs", - "default": "./lib/ttsc/plugin.js" - }, "./lib/internal/*": { "types": "./lib/internal/*.d.ts", "import": "./lib/internal/*.mjs", diff --git a/packages/typia/src/executable/TypiaGenerateWizard.ts b/packages/typia/src/executable/TypiaGenerateWizard.ts index 1ac2eddb781..9922e1f9590 100644 --- a/packages/typia/src/executable/TypiaGenerateWizard.ts +++ b/packages/typia/src/executable/TypiaGenerateWizard.ts @@ -1,8 +1,7 @@ +import { transform } from "@typia/ttsc"; import fs from "fs"; import path from "path"; -import { transform } from "@typia/ttsc"; - import { ArgumentParser } from "./setup/ArgumentParser"; import { PackageManager } from "./setup/PackageManager"; @@ -119,11 +118,10 @@ export namespace TypiaGenerateWizard { cwd, file, tsconfig: location.project, - plugins: [{ transform: "typia/lib/ttsc/plugin" }], - rewriteMode: "typia", + plugins: [{ transform: "typia/lib/transform" }], }); await fs.promises.mkdir(path.dirname(target), { recursive: true }); - await fs.promises.writeFile(target, output, "utf8"); + await fs.promises.writeFile(target, formatOutput(output), "utf8"); } } @@ -165,6 +163,12 @@ export namespace TypiaGenerateWizard { function isSupportedExtension(filename: string): boolean { return TS_PATTERN.test(filename) && !DTS_PATTERN.test(filename); } + + function formatOutput(output: string): string { + return output.startsWith("// @ts-nocheck") + ? output + : `// @ts-nocheck\n${output}`; + } } const TS_PATTERN = /\.[cm]?tsx?$/; diff --git a/packages/typia/src/executable/TypiaSetupWizard.ts b/packages/typia/src/executable/TypiaSetupWizard.ts index f371ce8a44e..0260f38dcab 100644 --- a/packages/typia/src/executable/TypiaSetupWizard.ts +++ b/packages/typia/src/executable/TypiaSetupWizard.ts @@ -7,6 +7,7 @@ import { PluginConfigurator } from "./setup/PluginConfigurator"; export namespace TypiaSetupWizard { const TTSC_PACKAGE = "@typia/ttsc"; + const TTSX_PACKAGE = "@typia/ttsx"; const TSGO_COMPILER_PACKAGE = "@typescript/native-preview"; export interface IArguments { @@ -23,13 +24,6 @@ export namespace TypiaSetupWizard { const pack: PackageManager = await PackageManager.mount(); const args: IArguments = await ArgumentParser.parse(pack, inquiry); - // INSTALL TSGO TOOLCHAIN - pack.install({ - dev: true, - modulo: TSGO_COMPILER_PACKAGE, - version: "latest", - }); - pack.install({ dev: true, modulo: TTSC_PACKAGE, version: "latest" }); args.project ??= (() => { fs.writeFileSync( "tsconfig.json", @@ -39,33 +33,37 @@ export namespace TypiaSetupWizard { return (args.project = "tsconfig.json"); })(); + // CONFIGURE TYPIA + await PluginConfigurator.configure(args); + // NORMALIZE PROJECT SETTINGS await pack.save((data) => { - data.scripts ??= {}; - if (typeof data.scripts.prepare === "string") { - const prepare = data.scripts.prepare - .split("&&") - .map((str) => str.trim()) - .filter( - (str) => str.length !== 0 && isLegacyCompilerPatchStep(str) === false, - ) - .join(" && "); + if (typeof data.scripts?.prepare === "string") { + const prepare = removeLegacyCompilerPatchSteps(data.scripts.prepare); if (prepare.length !== 0) data.scripts.prepare = prepare; else delete data.scripts.prepare; + if (Object.keys(data.scripts).length === 0) delete data.scripts; + } + if (data.devDependencies?.["ts-patch"] !== undefined) { + delete data.devDependencies["ts-patch"]; + if (Object.keys(data.devDependencies).length === 0) + delete data.devDependencies; } - if (typeof data.scripts.postinstall === "string") { - data.scripts.postinstall = data.scripts.postinstall - .split("&&") - .map((str) => str.trim()) - .filter((str) => isLegacyCompilerPatchStep(str) === false) - .join(" && "); - if (data.scripts.postinstall.length === 0) - delete data.scripts.postinstall; + if (data.dependencies?.["ts-patch"] !== undefined) { + delete data.dependencies["ts-patch"]; + if (Object.keys(data.dependencies).length === 0) + delete data.dependencies; } }); - // CONFIGURE TYPIA - await PluginConfigurator.configure(args); + // INSTALL TSGO TOOLCHAIN + pack.install({ + dev: true, + modulo: TSGO_COMPILER_PACKAGE, + version: "latest", + }); + pack.install({ dev: true, modulo: TTSC_PACKAGE, version: "latest" }); + pack.install({ dev: true, modulo: TTSX_PACKAGE, version: "latest" }); }; const inquiry: ArgumentParser.Inquiry = async ( @@ -158,5 +156,14 @@ export namespace TypiaSetupWizard { }; const isLegacyCompilerPatchStep = (str: string): boolean => - str === "typia patch" || /\b[a-z-]+-patch install\b/.test(str); + str === "typia patch" || str.includes("ts-patch install"); + + const removeLegacyCompilerPatchSteps = (script: string): string => + script + .split("&&") + .map((str) => str.trim()) + .filter( + (str) => str.length !== 0 && isLegacyCompilerPatchStep(str) === false, + ) + .join(" && "); } diff --git a/packages/typia/bin/ttsc-typia.js b/packages/typia/src/executable/generate/ttsc.ts old mode 100755 new mode 100644 similarity index 83% rename from packages/typia/bin/ttsc-typia.js rename to packages/typia/src/executable/generate/ttsc.ts index 370f749c090..ff49b9365a0 --- a/packages/typia/bin/ttsc-typia.js +++ b/packages/typia/src/executable/generate/ttsc.ts @@ -1,15 +1,20 @@ -#!/usr/bin/env node -"use strict"; +const { spawnSync } = require( + "node:child_process", +) as typeof import("node:child_process"); +const fs = require("node:fs") as typeof import("node:fs"); +const path = require("node:path") as typeof import("node:path"); -const fs = require("node:fs"); -const path = require("node:path"); -const { spawnSync } = require("node:child_process"); - -const packageRoot = path.resolve(__dirname, ".."); +const packageRoot = path.resolve( + path.dirname(path.resolve(process.argv[1] ?? "")), + "..", + "..", + "..", +); const repoRoot = path.resolve(packageRoot, "..", ".."); const nativeProject = path.resolve(repoRoot, "packages", "transform", "native"); const nativeBinary = path.resolve( - __dirname, + packageRoot, + "bin", process.platform === "win32" ? "ttsc-typia-native.exe" : "ttsc-typia-native", ); const nativeEntrypoint = path.resolve( @@ -24,7 +29,7 @@ const argv = [...process.argv.slice(2)]; if ( argv.length > 0 && - needsCwd(argv[0]) && + needsCwd(argv[0]!) && !argv.some((value) => value === "--cwd" || value.startsWith("--cwd=")) ) { argv.push(`--cwd=${invocationCwd}`); @@ -73,7 +78,7 @@ if (!hasNativeBinary && !hasSourceEntrypoint) { } } -function needsCwd(commandName) { +function needsCwd(commandName: string): boolean { return ( commandName === "build" || commandName === "check" || diff --git a/packages/typia/src/executable/setup/PluginConfigurator.ts b/packages/typia/src/executable/setup/PluginConfigurator.ts index 916a80606d6..dcd7aa31274 100644 --- a/packages/typia/src/executable/setup/PluginConfigurator.ts +++ b/packages/typia/src/executable/setup/PluginConfigurator.ts @@ -4,8 +4,7 @@ import fs from "fs"; import type { TypiaSetupWizard } from "../TypiaSetupWizard"; export namespace PluginConfigurator { - const LEGACY_TRANSFORM = "typia/lib/transform"; - const TTSC_TYPIA_PLUGIN = "typia/lib/ttsc/plugin"; + const TYPIA_TRANSFORM = "typia/lib/transform"; export async function configure( args: TypiaSetupWizard.IArguments, @@ -14,13 +13,9 @@ export namespace PluginConfigurator { const config: comments.CommentObject = comments.parse( await fs.promises.readFile(args.project!, "utf8"), ) as comments.CommentObject; - const compilerOptions = config.compilerOptions as - | comments.CommentObject - | undefined; - if (compilerOptions === undefined) - throw new ReferenceError( - `${args.project} file does not have "compilerOptions" property.`, - ); + const compilerOptions = ( + config.compilerOptions ??= {} as comments.CommentObject + ) as comments.CommentObject; // PREPARE PLUGINS const plugins: comments.CommentArray = (() => { @@ -45,12 +40,11 @@ export namespace PluginConfigurator { | undefined; const filtered: comments.CommentObject[] = plugins.filter( (p) => - typeof p === "object" && - p !== null && - p.transform !== LEGACY_TRANSFORM && - p.transform !== TTSC_TYPIA_PLUGIN, + typeof p === "object" && p !== null && p.transform !== TYPIA_TRANSFORM, ) as comments.CommentObject[]; - filtered.push({ transform: TTSC_TYPIA_PLUGIN } as comments.CommentObject); + filtered.push({ + transform: TYPIA_TRANSFORM, + } as unknown as comments.CommentObject); const changed: boolean = JSON.stringify(filtered) !== JSON.stringify(plugins) || strictNullChecks === false || diff --git a/packages/typia/src/programmers/TypiaProgrammer.ts b/packages/typia/src/programmers/TypiaProgrammer.ts deleted file mode 100644 index c84cfb03db8..00000000000 --- a/packages/typia/src/programmers/TypiaProgrammer.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TypiaGenerator } from "@typia/transform"; - -/** - * For legacy supporting. - * - * @internal - */ -export { TypiaGenerator as TypiaProgrammer }; diff --git a/packages/typia/src/transform.ts b/packages/typia/src/transform.ts index e598c5f99a3..52d2e2ecda3 100644 --- a/packages/typia/src/transform.ts +++ b/packages/typia/src/transform.ts @@ -1,5 +1,73 @@ -import transform from "@typia/transform"; +import { definePlugin } from "@typia/ttsc"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; -export default transform; +const filename: string = currentFilename(); +const dirname: string = path.dirname(filename); +const extension: string = path.extname(filename); -export * from "@typia/transform"; +export default definePlugin((_config, context) => { + const root: string = + resolvePackageRoot(context.projectRoot) ?? inferPackageRoot(); + const source: boolean = extension === ".ts"; + return { + name: "typia", + native: { + binary: path.resolve( + root, + source + ? "src/executable/generate/ttsc.ts" + : "lib/executable/generate/ttsc.js", + ), + capabilities: ["rewrite", "diagnostics", "assets"], + contractVersion: 1, + mode: "typia", + }, + }; +}); + +function resolvePackageRoot(projectRoot: string): string | null { + try { + return path.dirname( + require.resolve("typia/package.json", { paths: [projectRoot] }), + ); + } catch { + return null; + } +} + +function inferPackageRoot(): string { + if (extension === ".ts") { + return path.resolve(dirname, ".."); + } + return path.resolve(dirname, ".."); +} + +function currentFilename(): string { + if ( + typeof __filename === "string" && + __filename !== "[stdin]" && + __filename.length !== 0 + ) { + return normalizeFilename(__filename); + } + const line = new Error().stack + ?.split("\n") + .find( + (entry) => + entry.includes("/src/transform.ts") || + entry.includes("/lib/transform.js") || + entry.includes("/lib/transform.mjs") || + entry.includes("/lib/transform2.mjs"), + ); + const matched = line?.match(/\(([^()]+):\d+:\d+\)|at ([^\s()]+):\d+:\d+/); + const fallback: string = typeof __filename === "string" ? __filename : ""; + return normalizeFilename(matched?.[1] ?? matched?.[2] ?? fallback); +} + +function normalizeFilename(value: string): string { + if (value.startsWith("file:")) { + return fileURLToPath(value); + } + return value; +} diff --git a/packages/typia/src/ttsc/plugin.ts b/packages/typia/src/ttsc/plugin.ts deleted file mode 100644 index 265529fa896..00000000000 --- a/packages/typia/src/ttsc/plugin.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as path from "node:path"; - -import { definePlugin } from "@typia/ttsc/plugin"; - -export default definePlugin(() => ({ - name: "typia", - nativeBinary: path.resolve(__dirname, "../../bin/ttsc-typia.js"), - nativeMode: "typia", -})); diff --git a/packages/typia/tsconfig.json b/packages/typia/tsconfig.json index 31e11708d92..0deb515430f 100644 --- a/packages/typia/tsconfig.json +++ b/packages/typia/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../config/tsconfig.json", "compilerOptions": { + "ignoreDeprecations": "6.0", + "moduleResolution": "bundler", "outDir": "lib", "rootDir": "src" }, diff --git a/packages/unplugin/package.json b/packages/unplugin/package.json index 73fdd205b81..5949da1a2d4 100644 --- a/packages/unplugin/package.json +++ b/packages/unplugin/package.json @@ -10,75 +10,25 @@ "url": "https://github.com/samchon/typia" }, "sideEffects": false, - "main": "./lib/index.js", - "types": "./lib/index.d.ts", + "main": "./src/index.ts", + "types": "./src/index.ts", "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "default": "./lib/index.js" - }, - "./api": { - "types": "./lib/api.d.ts", - "import": "./lib/api.js", - "default": "./lib/api.js" - }, - "./bun": { - "types": "./lib/bun.d.ts", - "import": "./lib/bun.js", - "default": "./lib/bun.js" - }, - "./esbuild": { - "types": "./lib/esbuild.d.ts", - "import": "./lib/esbuild.js", - "default": "./lib/esbuild.js" - }, - "./farm": { - "types": "./lib/farm.d.ts", - "import": "./lib/farm.js", - "default": "./lib/farm.js" - }, - "./next": { - "types": "./lib/next.d.ts", - "import": "./lib/next.js", - "default": "./lib/next.js" - }, - "./rolldown": { - "types": "./lib/rolldown.d.ts", - "import": "./lib/rolldown.js", - "default": "./lib/rolldown.js" - }, - "./rollup": { - "types": "./lib/rollup.d.ts", - "import": "./lib/rollup.js", - "default": "./lib/rollup.js" - }, - "./rspack": { - "types": "./lib/rspack.d.ts", - "import": "./lib/rspack.js", - "default": "./lib/rspack.js" - }, - "./vite": { - "types": "./lib/vite.d.ts", - "import": "./lib/vite.js", - "default": "./lib/vite.js" - }, - "./webpack": { - "types": "./lib/webpack.d.ts", - "import": "./lib/webpack.js", - "default": "./lib/webpack.js" - }, + ".": "./src/index.ts", + "./api": "./src/api.ts", + "./bun": "./src/bun.ts", + "./esbuild": "./src/esbuild.ts", + "./farm": "./src/farm.ts", + "./next": "./src/next.ts", + "./rolldown": "./src/rolldown.ts", + "./rollup": "./src/rollup.ts", + "./rspack": "./src/rspack.ts", + "./vite": "./src/vite.ts", + "./webpack": "./src/webpack.ts", "./package.json": "./package.json" }, - "files": [ - "LICENSE", - "README.md", - "lib", - "package.json", - "src" - ], + "files": ["LICENSE", "README.md", "lib", "package.json", "src"], "scripts": { - "build": "rimraf lib && ttsc build", + "build": "rimraf lib && ttsc", "prepack": "pnpm run build" }, "peerDependencies": { @@ -111,8 +61,6 @@ "devDependencies": { "@types/bun": "^1.2.15", "@types/node": "^20.17.57", - "@typia/core": "workspace:^", - "@typia/transform": "workspace:^", "@typia/ttsc": "workspace:^", "fs-fixture": "^2.7.1", "next": "^16.1.6", diff --git a/packages/unplugin/src/core/options.ts b/packages/unplugin/src/core/options.ts index 92137637b00..b91333a322a 100644 --- a/packages/unplugin/src/core/options.ts +++ b/packages/unplugin/src/core/options.ts @@ -1,8 +1,15 @@ import type { FilterPattern } from "@rollup/pluginutils"; -import type { ITransformOptions } from "@typia/core"; import { createDefu } from "defu"; import type { OverrideProperties, RequiredDeep } from "type-fest"; +export interface TypiaTransformOptions { + finite?: undefined | boolean; + numeric?: undefined | boolean; + functional?: undefined | boolean; + undefined?: undefined | boolean; + runtime?: "ts" | "js"; +} + /** Represents the options for the plugin. */ export type Options = { /** @@ -37,7 +44,7 @@ export type Options = { tsconfig?: string; /** The options for the typia transformer. */ - typia?: ITransformOptions; + typia?: TypiaTransformOptions; /** * The options for cache. The cache-dir-searching feature is powered by diff --git a/packages/unplugin/src/core/typia.ts b/packages/unplugin/src/core/typia.ts index f8d6f5b8106..2ca658637a2 100644 --- a/packages/unplugin/src/core/typia.ts +++ b/packages/unplugin/src/core/typia.ts @@ -1,6 +1,6 @@ +import { transform as ttscTransform } from "@typia/ttsc"; import { existsSync } from "node:fs"; import { dirname, isAbsolute, join, resolve } from "pathe"; -import { transform as ttscTransform } from "@typia/ttsc"; import type { Alias } from "vite"; import type { ResolvedOptions } from "./options.js"; @@ -33,10 +33,9 @@ export async function transformTypia( tsconfig, plugins: [ { - transform: "typia/lib/ttsc/plugin", + transform: "typia/lib/transform", }, ], - rewriteMode: "typia", }); return wrap(result); } diff --git a/packages/utils/package.json b/packages/utils/package.json index 230c528c872..020c9f7afd3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -2,18 +2,14 @@ "name": "@typia/utils", "version": "12.0.2", "description": "Superfast runtime validators with only one line", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "src/index.ts", + "types": "src/index.ts", "exports": { - ".": { - "types": "./lib/index.d.ts", - "import": "./lib/index.js", - "default": "./lib/index.js" - }, + ".": "./src/index.ts", "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && ttsc build && rollup -c", + "build": "rimraf lib && ttsc && rollup -c", "dev": "ttsc --watch", "prepack": "pnpm run build" }, diff --git a/packages/utils/src/converters/internal/OpenApiV3_1Upgrader.ts b/packages/utils/src/converters/internal/OpenApiV3_1Upgrader.ts index 54ce7421667..d05910e4005 100644 --- a/packages/utils/src/converters/internal/OpenApiV3_1Upgrader.ts +++ b/packages/utils/src/converters/internal/OpenApiV3_1Upgrader.ts @@ -532,10 +532,13 @@ export namespace OpenApiV3_1Upgrader { items: undefined!, prefixItems: schema.prefixItems.map(convertSchema(components)), additionalItems: - typeof schema.additionalItems === "object" && - schema.additionalItems !== null - ? convertSchema(components)(schema.additionalItems) - : schema.additionalItems, + schema.items !== undefined && + Array.isArray(schema.items) === false + ? convertSchema(components)(schema.items) + : typeof schema.additionalItems === "object" && + schema.additionalItems !== null + ? convertSchema(components)(schema.additionalItems) + : schema.additionalItems, }, }); else if (schema.items === undefined) diff --git a/packages/vercel/package.json b/packages/vercel/package.json index 92b4a0ae9ea..35f5aa73392 100644 --- a/packages/vercel/package.json +++ b/packages/vercel/package.json @@ -8,7 +8,7 @@ "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && ttsc build && rollup -c", + "build": "rimraf lib && ttsc && rollup -c", "dev": "ttsc --watch", "prepack": "pnpm run build" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d14a8734621..2221bdca2bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,46 +244,6 @@ importers: specifier: catalog:utils version: 6.1.3 - packages/core: - dependencies: - '@typia/interface': - specifier: workspace:^ - version: link:../interface - '@typia/utils': - specifier: workspace:^ - version: link:../utils - devDependencies: - '@rollup/plugin-commonjs': - specifier: catalog:rollup - version: 29.0.2(rollup@4.60.2) - '@rollup/plugin-node-resolve': - specifier: catalog:rollup - version: 16.0.3(rollup@4.60.2) - '@types/node': - specifier: catalog:utils - version: 25.6.0 - '@typescript/native-preview': - specifier: catalog:typescript - version: 7.0.0-dev.20260421.2 - '@typia/ttsc': - specifier: workspace:^ - version: link:../../toolchain/ttsc - rimraf: - specifier: catalog:utils - version: 6.1.3 - rollup: - specifier: catalog:rollup - version: 4.60.2 - rollup-plugin-auto-external: - specifier: catalog:rollup - version: 2.0.0(rollup@4.60.2) - rollup-plugin-node-externals: - specifier: catalog:rollup - version: 8.1.2(rollup@4.60.2) - tinyglobby: - specifier: ^0.2.12 - version: 0.2.16 - packages/interface: devDependencies: '@typescript/native-preview': @@ -382,63 +342,14 @@ importers: specifier: ^3.25.0 version: 3.25.76 - packages/transform: - dependencies: - '@typia/core': - specifier: workspace:^ - version: link:../core - '@typia/interface': - specifier: workspace:^ - version: link:../interface - '@typia/utils': - specifier: workspace:^ - version: link:../utils - devDependencies: - '@rollup/plugin-commonjs': - specifier: catalog:rollup - version: 29.0.2(rollup@4.60.2) - '@rollup/plugin-node-resolve': - specifier: catalog:rollup - version: 16.0.3(rollup@4.60.2) - '@types/node': - specifier: catalog:utils - version: 25.6.0 - '@typescript/native-preview': - specifier: catalog:typescript - version: 7.0.0-dev.20260421.2 - '@typia/ttsc': - specifier: workspace:^ - version: link:../../toolchain/ttsc - rimraf: - specifier: catalog:utils - version: 6.1.3 - rollup: - specifier: catalog:rollup - version: 4.60.2 - rollup-plugin-auto-external: - specifier: catalog:rollup - version: 2.0.0(rollup@4.60.2) - rollup-plugin-node-externals: - specifier: catalog:rollup - version: 8.1.2(rollup@4.60.2) - tinyglobby: - specifier: ^0.2.12 - version: 0.2.16 - packages/typia: dependencies: '@standard-schema/spec': specifier: ^1.0.0 version: 1.1.0 - '@typia/core': - specifier: workspace:^ - version: link:../core '@typia/interface': specifier: workspace:^ version: link:../interface - '@typia/transform': - specifier: workspace:^ - version: link:../transform '@typia/ttsc': specifier: workspace:^ version: link:../../toolchain/ttsc @@ -555,12 +466,6 @@ importers: '@types/node': specifier: ^20.17.57 version: 20.19.39 - '@typia/core': - specifier: workspace:^ - version: link:../core - '@typia/transform': - specifier: workspace:^ - version: link:../transform '@typia/ttsc': specifier: workspace:^ version: link:../../toolchain/ttsc @@ -665,15 +570,9 @@ importers: tests/debug: dependencies: - '@typia/core': - specifier: workspace:^ - version: link:../../packages/core '@typia/interface': specifier: workspace:^ version: link:../../packages/interface - '@typia/transform': - specifier: workspace:^ - version: link:../../packages/transform '@typia/utils': specifier: workspace:^ version: link:../../packages/utils @@ -865,9 +764,6 @@ importers: randexp: specifier: ^0.5.3 version: 0.5.3 - tgrid: - specifier: catalog:utils - version: 1.2.1 tstl: specifier: catalog:utils version: 3.0.0 @@ -893,6 +789,9 @@ importers: '@typia/ttsx': specifier: workspace:^ version: link:../../toolchain/ttsx + rimraf: + specifier: catalog:utils + version: 6.1.3 tests/test-typia-generate: dependencies: @@ -1053,9 +952,6 @@ importers: '@typia/utils': specifier: workspace:^ version: link:../../packages/utils - tgrid: - specifier: catalog:utils - version: 1.2.1 typia: specifier: workspace:^ version: link:../../packages/typia @@ -1078,6 +974,9 @@ importers: '@typia/ttsx': specifier: workspace:^ version: link:../../toolchain/ttsx + rimraf: + specifier: catalog:utils + version: 6.1.3 tests/test-vercel: dependencies: diff --git a/tests/config/tsconfig.json b/tests/config/tsconfig.json index ca6a4807f38..ee8d044d97f 100644 --- a/tests/config/tsconfig.json +++ b/tests/config/tsconfig.json @@ -29,16 +29,17 @@ /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ + "ignoreDeprecations": "6.0", // "baseUrl": "./", "plugins": [ { - "transform": "typia/lib/ttsc/plugin" + "transform": "typia/lib/transform" } ], // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": ["*"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ diff --git a/tests/debug/package.json b/tests/debug/package.json index 1ab5a67dbf3..b7e063c2ad7 100644 --- a/tests/debug/package.json +++ b/tests/debug/package.json @@ -11,10 +11,7 @@ "type": "git", "url": "https://github.com/samchon/typia" }, - "keywords": [ - "typia", - "test" - ], + "keywords": ["typia", "test"], "author": "Jeongho Nam", "license": "MIT", "bugs": { @@ -30,9 +27,7 @@ "rimraf": "catalog:utils" }, "dependencies": { - "@typia/core": "workspace:^", "@typia/interface": "workspace:^", - "@typia/transform": "workspace:^", "@typia/utils": "workspace:^", "tstl": "catalog:utils", "typia": "workspace:^", diff --git a/tests/template/src/utils/TestServant.ts b/tests/template/src/utils/TestServant.ts index aafc4acac91..8fc1fd7fe72 100644 --- a/tests/template/src/utils/TestServant.ts +++ b/tests/template/src/utils/TestServant.ts @@ -26,7 +26,7 @@ export class TestServant { (props.exclude.length ? props.exclude.every((str) => !name.includes(str)) : true), - extension: __filename.substr(-2), + extension: props.extension ?? __filename.substr(-2), }); exceptions.push( ...report.executions.map((e) => e.error).filter((e) => e !== null), @@ -43,5 +43,6 @@ export namespace TestServant { location: string; include: string[]; exclude: string[]; + extension?: string; } } diff --git a/tests/test-error/src/examples/llm.schema.additionalProperties.ts b/tests/test-error/src/examples/llm.schema.additionalProperties.ts index df918b814b5..38c15efd8a1 100644 --- a/tests/test-error/src/examples/llm.schema.additionalProperties.ts +++ b/tests/test-error/src/examples/llm.schema.additionalProperties.ts @@ -1,4 +1,4 @@ import typia from "typia"; -typia.llm.schema, "chatgpt">({}); -typia.llm.schema, "gemini">({}); +typia.llm.schema, { strict: true }>({}); +typia.llm.schema, { strict: true }>({}); diff --git a/tests/test-error/src/examples/llm.schema.bigint-and-tuple.ts b/tests/test-error/src/examples/llm.schema.bigint-and-tuple.ts index df90542b393..28f46cc6c5e 100644 --- a/tests/test-error/src/examples/llm.schema.bigint-and-tuple.ts +++ b/tests/test-error/src/examples/llm.schema.bigint-and-tuple.ts @@ -1,4 +1,4 @@ import typia from "typia"; -typia.llm.schema({}); -typia.llm.schema<[number, string], "claude">({}); +typia.llm.schema({}); +typia.llm.schema<[number, string]>({}); diff --git a/tests/test-typia-automated/package.json b/tests/test-typia-automated/package.json index cf9ecdc3256..c85a6db81d0 100644 --- a/tests/test-typia-automated/package.json +++ b/tests/test-typia-automated/package.json @@ -4,6 +4,7 @@ "version": "12.0.2", "description": "Automated test features of typia.", "scripts": { + "build": "rimraf bin && ttsc", "dev": "ttsc --watch", "start": "ttsx src/index.ts" }, @@ -26,16 +27,16 @@ "@types/uuid": "catalog:utils", "@typescript/native-preview": "catalog:typescript", "@typia/ttsc": "workspace:^", - "@typia/ttsx": "workspace:^" + "@typia/ttsx": "workspace:^", + "rimraf": "catalog:utils" }, "dependencies": { "@nestia/e2e": "catalog:utils", "@standard-schema/spec": "^1.0.0", - "@typia/utils": "workspace:^", "@typia/template": "workspace:^", + "@typia/utils": "workspace:^", "protobufjs": "7.2.5", "randexp": "^0.5.3", - "tgrid": "catalog:utils", "tstl": "catalog:utils", "typia": "workspace:^", "uuid": "catalog:utils" diff --git a/tests/test-typia-automated/src/index.ts b/tests/test-typia-automated/src/index.ts index f271bf254c1..0e3ace5dbc5 100644 --- a/tests/test-typia-automated/src/index.ts +++ b/tests/test-typia-automated/src/index.ts @@ -1,31 +1,22 @@ import { TestServant } from "@typia/template"; -import { WorkerConnector } from "tgrid"; +import cp from "child_process"; import { TestGlobal } from "./TestGlobal"; import { TestAutomationController } from "./build/TestAutomationController"; async function main(): Promise { - const include: string[] = TestGlobal.getArguments("include") ?? []; - const exclude: string[] = TestGlobal.getArguments("exclude") ?? []; - - const exceptions: Error[] = []; - - await TestAutomationController.iterate(async (location) => { - const connector = new WorkerConnector(null, null, "process"); - await connector.connect(`${__dirname}/servant/index.ts`); + await TestAutomationController.iterate(async () => {}); + cp.execSync("pnpm build", { + cwd: TestGlobal.ROOT, + stdio: "inherit", + }); - const servant = connector.getDriver(); - try { - exceptions.push( - ...(await servant.execute({ - location, - include, - exclude, - })), - ); - } finally { - await connector.close(); - } + const servant: TestServant = new TestServant(); + const exceptions: Error[] = await servant.execute({ + location: `${TestGlobal.ROOT}/bin/features`, + include: TestGlobal.getArguments("include") ?? [], + exclude: TestGlobal.getArguments("exclude") ?? [], + extension: "js", }); if (exceptions.length === 0) { console.log("Success"); diff --git a/tests/test-typia-automated/src/internal/_test_notation_validateGeneral.ts b/tests/test-typia-automated/src/internal/_test_notation_validateGeneral.ts index 7160760631e..f9d16d94010 100644 --- a/tests/test-typia-automated/src/internal/_test_notation_validateGeneral.ts +++ b/tests/test-typia-automated/src/internal/_test_notation_validateGeneral.ts @@ -1,5 +1,5 @@ import { TestStructure } from "@typia/template"; -import typia, { IValidation } from "typia"; +import type { IValidation } from "typia"; import { assertValidationFailure, diff --git a/tests/test-typia-automated/src/internal/_test_validateEquals.ts b/tests/test-typia-automated/src/internal/_test_validateEquals.ts index b6efa4c5a01..54f8c066d09 100644 --- a/tests/test-typia-automated/src/internal/_test_validateEquals.ts +++ b/tests/test-typia-automated/src/internal/_test_validateEquals.ts @@ -1,6 +1,6 @@ import { TestStructure } from "@typia/template"; import { NamingConvention } from "@typia/utils"; -import typia, { IValidation } from "typia"; +import type { IValidation } from "typia"; import { assertValidationFailure, diff --git a/tests/test-typia-automated/src/servant/index.ts b/tests/test-typia-automated/src/servant/index.ts deleted file mode 100644 index 8d8c4ced3f4..00000000000 --- a/tests/test-typia-automated/src/servant/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -const { register } = require("@typia/ttsx"); -const { WorkerServer } = require("tgrid"); - -register(); - -const main = async (): Promise => { - const { TestServant } = require("@typia/template"); - const server = new WorkerServer(); - await server.open(new TestServant()); -}; -main().catch(console.log); diff --git a/tests/test-typia-automated/tsconfig.json b/tests/test-typia-automated/tsconfig.json index 945fa1ec05c..2b94021fb2f 100644 --- a/tests/test-typia-automated/tsconfig.json +++ b/tests/test-typia-automated/tsconfig.json @@ -1,4 +1,9 @@ { "extends": "../config/tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "bin", + "rootDir": "src", + }, "include": ["src"], } diff --git a/tests/test-typia-generate/package.json b/tests/test-typia-generate/package.json index b69a4904313..6e6cc452635 100644 --- a/tests/test-typia-generate/package.json +++ b/tests/test-typia-generate/package.json @@ -5,7 +5,7 @@ "description": "Test typia generation command", "scripts": { "build": "rimraf bin && ttsc build --emit --rewrite-mode none --tsconfig tsconfig.json", - "generate": "ttsx node_modules/typia/src/executable/typia.ts generate --input src/input --output src/output", + "generate": "rimraf src/output && ttsx node_modules/typia/src/executable/typia.ts generate --input src/input --output src/output", "start": "pnpm run generate && pnpm run build" }, "repository": { diff --git a/tests/test-unplugin/src/fixtures/tsconfig.json b/tests/test-unplugin/src/fixtures/tsconfig.json index 6c83847f5be..213422957a6 100644 --- a/tests/test-unplugin/src/fixtures/tsconfig.json +++ b/tests/test-unplugin/src/fixtures/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig", "include": ["**/*"], - "exclude": [], + "exclude": ["__snapshots__"], "compilerOptions": { "paths": { "@/*": ["./*"] diff --git a/tests/test-utils-automated/package.json b/tests/test-utils-automated/package.json index 2e461d65b89..78d1b2a9cf5 100644 --- a/tests/test-utils-automated/package.json +++ b/tests/test-utils-automated/package.json @@ -4,6 +4,7 @@ "version": "12.0.2", "description": "Automated test features of openapi.", "scripts": { + "build": "rimraf bin && ttsc", "dev": "ttsc --watch", "start": "ttsx src/index.ts" }, @@ -26,14 +27,14 @@ "@types/uuid": "catalog:utils", "@typescript/native-preview": "catalog:typescript", "@typia/ttsc": "workspace:^", - "@typia/ttsx": "workspace:^" + "@typia/ttsx": "workspace:^", + "rimraf": "catalog:utils" }, "dependencies": { "@nestia/e2e": "catalog:utils", "@typia/interface": "workspace:^", "@typia/template": "workspace:^", "@typia/utils": "workspace:^", - "tgrid": "catalog:utils", "typia": "workspace:^", "uuid": "catalog:utils" } diff --git a/tests/test-utils-automated/src/index.ts b/tests/test-utils-automated/src/index.ts index 11b49ff5b98..dee317c0485 100644 --- a/tests/test-utils-automated/src/index.ts +++ b/tests/test-utils-automated/src/index.ts @@ -1,32 +1,23 @@ import { TestServant } from "@typia/template"; -import { WorkerConnector } from "tgrid"; +import cp from "child_process"; import { TestAutomation } from "./TestAutomation"; import { TestGlobal } from "./TestGlobal"; async function main(): Promise { await TestAutomation.generate(); + cp.execSync("pnpm build", { + cwd: TestGlobal.ROOT, + stdio: "inherit", + }); - const include: string[] = TestGlobal.getArguments("include") ?? []; - const exclude: string[] = TestGlobal.getArguments("exclude") ?? []; - const exceptions: Error[] = []; - - const connector = new WorkerConnector(null, null, "process"); - await connector.connect(`${__dirname}/servant/index.ts`); - - const servant = connector.getDriver(); - try { - exceptions.push( - ...(await servant.execute({ - location: `${TestGlobal.ROOT}/src/features`, - include, - exclude, - })), - ); - } finally { - await connector.close(); - } - + const servant: TestServant = new TestServant(); + const exceptions: Error[] = await servant.execute({ + location: `${TestGlobal.ROOT}/bin/features`, + include: TestGlobal.getArguments("include") ?? [], + exclude: TestGlobal.getArguments("exclude") ?? [], + extension: "js", + }); if (exceptions.length === 0) { console.log("Success"); } else { diff --git a/tests/test-utils-automated/src/internal/_test_validate.ts b/tests/test-utils-automated/src/internal/_test_validate.ts index 6bba2f61064..1b8f04a4c61 100644 --- a/tests/test-utils-automated/src/internal/_test_validate.ts +++ b/tests/test-utils-automated/src/internal/_test_validate.ts @@ -1,6 +1,6 @@ import { IValidation, OpenApi } from "@typia/interface"; import { Spoiler } from "@typia/template"; -import { OpenApiValidator } from "@typia/utils"; +import { OpenApiConverter, OpenApiValidator } from "@typia/utils"; export const _test_validate = (props: { schema: OpenApi.IJsonSchema; @@ -11,10 +11,17 @@ export const _test_validate = (props: { }; name: string; }): void => { + const components: OpenApi.IComponents = OpenApiConverter.upgradeComponents( + props.components as any, + ); + const schema: OpenApi.IJsonSchema = OpenApiConverter.upgradeSchema({ + components: props.components as any, + schema: props.schema as any, + }); const input: T = props.factory.generate(); const validate = OpenApiValidator.create({ - components: props.components, - schema: props.schema, + components, + schema, required: true, }); const result: IValidation = validate(input); diff --git a/tests/test-utils-automated/src/internal/_test_validateEquals.ts b/tests/test-utils-automated/src/internal/_test_validateEquals.ts index f606c165c94..a991ab86264 100644 --- a/tests/test-utils-automated/src/internal/_test_validateEquals.ts +++ b/tests/test-utils-automated/src/internal/_test_validateEquals.ts @@ -1,7 +1,11 @@ import { TestValidator } from "@nestia/e2e"; import { IValidation, OpenApi } from "@typia/interface"; import { Spoiler } from "@typia/template"; -import { NamingConvention, OpenApiValidator } from "@typia/utils"; +import { + NamingConvention, + OpenApiConverter, + OpenApiValidator, +} from "@typia/utils"; export const _test_validateEquals = (props: { schema: OpenApi.IJsonSchema; @@ -12,9 +16,16 @@ export const _test_validateEquals = (props: { }; name: string; }): void => { + const components: OpenApi.IComponents = OpenApiConverter.upgradeComponents( + props.components as any, + ); + const schema: OpenApi.IJsonSchema = OpenApiConverter.upgradeSchema({ + components: props.components as any, + schema: props.schema as any, + }); const validate = OpenApiValidator.create({ - components: props.components, - schema: props.schema, + components, + schema, required: true, equals: true, }); diff --git a/tests/test-utils-automated/src/servant/index.ts b/tests/test-utils-automated/src/servant/index.ts deleted file mode 100644 index 1fe0738099f..00000000000 --- a/tests/test-utils-automated/src/servant/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TestServant } from "@typia/template"; -import { WorkerServer } from "tgrid"; - -const main = async (): Promise => { - const server = new WorkerServer(); - await server.open(new TestServant()); -}; -main().catch(console.log); diff --git a/tests/test-utils-automated/tsconfig.json b/tests/test-utils-automated/tsconfig.json index 0678e0e8f3e..2b94021fb2f 100644 --- a/tests/test-utils-automated/tsconfig.json +++ b/tests/test-utils-automated/tsconfig.json @@ -1,4 +1,9 @@ { "extends": "../config/tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "bin", + "rootDir": "src", + }, "include": ["src"], -} \ No newline at end of file +} diff --git a/tests/test-utils/src/features/llm/schema/test_llm_type_checker_cover_array.ts b/tests/test-utils/src/features/llm/schema/test_llm_type_checker_cover_array.ts index 1e5f04a267b..b538dfaba2c 100644 --- a/tests/test-utils/src/features/llm/schema/test_llm_type_checker_cover_array.ts +++ b/tests/test-utils/src/features/llm/schema/test_llm_type_checker_cover_array.ts @@ -6,7 +6,6 @@ import typia, { IJsonSchemaCollection } from "typia"; export const test_llm_type_checker_cover_array = () => { const collection: IJsonSchemaCollection = typia.json.schemas<[Plan2D, Plan3D, Box2D, Box3D]>(); - const components: OpenApi.IComponents = collection.components; const [plan2D, plan3D, box2D, box3D] = collection.schemas as [ OpenApi.IJsonSchema, OpenApi.IJsonSchema, diff --git a/toolchain-tests/test-typia-ttsc/fixtures/advanced/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/advanced/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/advanced/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/advanced/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/assert-validate/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/assert-validate/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/assert-validate/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/assert-validate/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/combined/src/main.ts b/toolchain-tests/test-typia-ttsc/fixtures/combined/src/main.ts index ae4c39fee0a..b2e4789b237 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/combined/src/main.ts +++ b/toolchain-tests/test-typia-ttsc/fixtures/combined/src/main.ts @@ -44,5 +44,5 @@ export const schema = typia.json.schema
(); export const statuses = typia.misc.literals(); // misc.prune on article shape -export const prune = (input: Record) => +export const prune = (input: Article) => typia.misc.prune
(input); diff --git a/toolchain-tests/test-typia-ttsc/fixtures/combined/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/combined/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/combined/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/combined/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/factory/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/factory/tsconfig.json index 5178ea9bbe9..6522a469f0c 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/factory/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/factory/tsconfig.json @@ -1 +1 @@ -{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"node","esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/ttsc/plugin"}]},"include":["src"]} +{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"bundler","ignoreDeprecations":"6.0","types":["*"],"esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/transform"}]},"include":["src"]} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/formats/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/formats/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/formats/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/formats/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/http/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/http/tsconfig.json index 5178ea9bbe9..6522a469f0c 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/http/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/http/tsconfig.json @@ -1 +1 @@ -{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"node","esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/ttsc/plugin"}]},"include":["src"]} +{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"bundler","ignoreDeprecations":"6.0","types":["*"],"esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/transform"}]},"include":["src"]} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/json/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/json/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/json/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/json/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/misc/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/misc/tsconfig.json index 5178ea9bbe9..6522a469f0c 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/misc/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/misc/tsconfig.json @@ -1 +1 @@ -{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"node","esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/ttsc/plugin"}]},"include":["src"]} +{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"bundler","ignoreDeprecations":"6.0","types":["*"],"esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/transform"}]},"include":["src"]} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/native/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/native/tsconfig.json index 5178ea9bbe9..6522a469f0c 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/native/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/native/tsconfig.json @@ -1 +1 @@ -{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"node","esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/ttsc/plugin"}]},"include":["src"]} +{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"bundler","ignoreDeprecations":"6.0","types":["*"],"esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/transform"}]},"include":["src"]} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/noemit/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/noemit/tsconfig.json index 5e5ff74bcb0..d44890f3c80 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/noemit/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/noemit/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }], + "plugins": [{ "transform": "typia/lib/transform" }], "noEmit": true }, "include": ["src"] diff --git a/toolchain-tests/test-typia-ttsc/fixtures/objects/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/objects/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/objects/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/objects/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/outdir/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/outdir/tsconfig.json index ada21b41ffc..73b1fbfb89d 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/outdir/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/outdir/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "build/output", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/plugins/banner-plugin.cjs b/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/plugins/banner-plugin.cjs index 00f3463bb4a..f1e6fe30225 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/plugins/banner-plugin.cjs +++ b/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/plugins/banner-plugin.cjs @@ -1,11 +1,12 @@ "use strict"; -const { definePlugin } = require("@typia/ttsc/plugin"); +const { definePlugin } = require("@typia/ttsc"); module.exports = definePlugin((config) => ({ name: "banner-plugin", transformOutput(context) { - const banner = typeof config.banner === "string" ? config.banner : "/* plugin */"; + const banner = + typeof config.banner === "string" ? config.banner : "/* plugin */"; return `${banner}\n${context.code}`; }, })); diff --git a/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/tsconfig.json index 58d4464621b..12b3e13a25b 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/plugin-js/tsconfig.json @@ -1,6 +1,9 @@ { "compilerOptions": { "module": "commonjs", + "ignoreDeprecations": "6.0", + "types": ["*"], + "rootDir": ".", "outDir": "dist", "strict": true, "target": "ES2020", diff --git a/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/plugins/banner-plugin.cjs b/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/plugins/banner-plugin.cjs index 00f3463bb4a..f1e6fe30225 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/plugins/banner-plugin.cjs +++ b/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/plugins/banner-plugin.cjs @@ -1,11 +1,12 @@ "use strict"; -const { definePlugin } = require("@typia/ttsc/plugin"); +const { definePlugin } = require("@typia/ttsc"); module.exports = definePlugin((config) => ({ name: "banner-plugin", transformOutput(context) { - const banner = typeof config.banner === "string" ? config.banner : "/* plugin */"; + const banner = + typeof config.banner === "string" ? config.banner : "/* plugin */"; return `${banner}\n${context.code}`; }, })); diff --git a/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/tsconfig.json index 7d3e38baf30..2c04c6a618b 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/plugin-mixed/tsconfig.json @@ -1,11 +1,14 @@ { "compilerOptions": { "module": "commonjs", + "ignoreDeprecations": "6.0", + "types": ["*"], + "rootDir": "src", "strict": true, "target": "ES2020", "plugins": [ { - "transform": "typia/lib/ttsc/plugin" + "transform": "typia/lib/transform" }, { "transform": "./plugins/banner-plugin.cjs", diff --git a/toolchain-tests/test-typia-ttsc/fixtures/primitives/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/primitives/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/primitives/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/primitives/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner-entry-error/src/main.ts b/toolchain-tests/test-typia-ttsc/fixtures/runner-entry-error/src/main.ts new file mode 100644 index 00000000000..1206d3200cc --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner-entry-error/src/main.ts @@ -0,0 +1,5 @@ +console.log("TTSX_ENTRY_SHOULD_NOT_RUN"); + +const value: string = 3; + +export { value }; diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner-entry-error/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/runner-entry-error/tsconfig.json new file mode 100644 index 00000000000..8487ca6063c --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner-entry-error/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"] +} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner-esm/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/runner-esm/tsconfig.json index 742c3bb4a84..5b861756ade 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/runner-esm/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner-esm/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/src/helper.ts b/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/src/helper.ts new file mode 100644 index 00000000000..8457943dc33 --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/src/helper.ts @@ -0,0 +1 @@ +export const value: string = 3; diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/src/main.ts b/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/src/main.ts new file mode 100644 index 00000000000..98d24daad91 --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/src/main.ts @@ -0,0 +1,3 @@ +import { value } from "./helper"; + +console.log("TTSX_IMPORT_SHOULD_NOT_RUN", value); diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/tsconfig.json new file mode 100644 index 00000000000..8487ca6063c --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner-import-error/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"] +} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/runner/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/runner/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/runner/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/runner/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/schema/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/schema/tsconfig.json index 5178ea9bbe9..6522a469f0c 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/schema/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/schema/tsconfig.json @@ -1 +1 @@ -{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"node","esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/ttsc/plugin"}]},"include":["src"]} +{"compilerOptions":{"target":"ES2022","module":"commonjs","moduleResolution":"bundler","ignoreDeprecations":"6.0","types":["*"],"esModuleInterop":true,"strict":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist","plugins":[{"transform":"typia/lib/transform"}]},"include":["src"]} diff --git a/toolchain-tests/test-typia-ttsc/fixtures/stress/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/stress/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/stress/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/stress/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/fixtures/tagged/tsconfig.json b/toolchain-tests/test-typia-ttsc/fixtures/tagged/tsconfig.json index d61e21edbfc..f9c70f552db 100644 --- a/toolchain-tests/test-typia-ttsc/fixtures/tagged/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/fixtures/tagged/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", - "moduleResolution": "node", + "moduleResolution": "bundler", + "ignoreDeprecations": "6.0", + "types": ["*"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "src", "outDir": "dist", - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["src"] } diff --git a/toolchain-tests/test-typia-ttsc/package.json b/toolchain-tests/test-typia-ttsc/package.json index 708de7d7a36..df17724c3d4 100644 --- a/toolchain-tests/test-typia-ttsc/package.json +++ b/toolchain-tests/test-typia-ttsc/package.json @@ -7,7 +7,7 @@ "dev": "ttsx src/index.ts", "build:backend": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; cd ../../packages/transform/native && go build -o ../../../packages/typia/bin/ttsc-typia-native ./cmd/ttsc-typia'", "build:packages": "pnpm --filter @typia/ttsc build && pnpm --filter @typia/ttsx build && pnpm --filter typia build", - "build:go": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; cd ../../toolchain/ttsc && go build -o bin/ttsc-native ./cmd/ttsc'", + "build:go": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; cd ../../toolchain/ttsc && mkdir -p native && go build -o native/ttsc-native ./cmd/ttsc'", "prestart": "pnpm run build:packages && pnpm run build:go && pnpm run build:backend", "start": "ttsx src/index.ts", "test": "pnpm run start" diff --git a/toolchain-tests/test-typia-ttsc/src/TestGlobal.ts b/toolchain-tests/test-typia-ttsc/src/TestGlobal.ts index ca57f77efd4..1df76f8baa6 100644 --- a/toolchain-tests/test-typia-ttsc/src/TestGlobal.ts +++ b/toolchain-tests/test-typia-ttsc/src/TestGlobal.ts @@ -6,7 +6,7 @@ export class TestGlobal { /** Absolute path to the compiled Go binary. */ public static readonly TTSC_BINARY: string = path.resolve( - `${__dirname}/../../../toolchain/ttsc/bin/ttsc-native`, + `${__dirname}/../../../toolchain/ttsc/native/ttsc-native`, ); /** Absolute path to the workspace-linked public ttsc launcher. */ diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_build_primitives.ts b/toolchain-tests/test-typia-ttsc/src/features/test_build_primitives.ts index c28c75b05e0..4e307986717 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_build_primitives.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_build_primitives.ts @@ -9,12 +9,15 @@ export async function test_build_primitives(): Promise { const fixture = path.join(TestGlobal.ROOT, "fixtures", "primitives"); const dist = path.join(fixture, "dist"); fs.rmSync(dist, { recursive: true, force: true }); - const result = runTtsc(["build", "--tsconfig=tsconfig.json"], fixture); + const result = runTtsc( + ["--tsconfig=tsconfig.json", "--verbose"], + fixture, + ); assert.equal( result.status, 0, - `ttsc build should succeed; stderr=\n${result.stderr}`, + `ttsc should build the project; stderr=\n${result.stderr}`, ); assert.ok( diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_advanced.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_advanced.ts index 29c212225d4..af03097ef52 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_advanced.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_advanced.ts @@ -18,7 +18,7 @@ export async function test_emit_advanced(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_assert_validate.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_assert_validate.ts index 9fca54389ef..216b451b72e 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_assert_validate.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_assert_validate.ts @@ -18,7 +18,7 @@ export async function test_emit_assert_validate(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); @@ -59,6 +59,7 @@ export async function test_emit_assert_validate(): Promise { // validate: failure const bad = mod.validate_string(42); assert.equal(bad.success, false); + assert.ok(bad.errors); assert.equal(bad.errors.length, 1); assert.equal(bad.errors[0]!.path, "$input"); assert.ok(bad.errors[0]!.expected.includes("string")); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_combined.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_combined.ts index 996f863b6dd..849642630a4 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_combined.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_combined.ts @@ -17,7 +17,7 @@ export async function test_emit_combined(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_factory.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_factory.ts index 1da24024341..c5ff99b43f4 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_factory.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_factory.ts @@ -16,7 +16,7 @@ export async function test_emit_factory(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_formats.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_formats.ts index fd1c13d01fd..f13947f57f2 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_formats.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_formats.ts @@ -16,7 +16,7 @@ export async function test_emit_formats(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_http.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_http.ts index 61bf0e17863..ad946615ad1 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_http.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_http.ts @@ -14,7 +14,7 @@ export async function test_emit_http(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_json.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_json.ts index 5b52ecc17e1..bf42c0ef9a2 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_json.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_json.ts @@ -20,7 +20,7 @@ export async function test_emit_json(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_json_schema.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_json_schema.ts index d71132bdd73..2247d487bb7 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_json_schema.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_json_schema.ts @@ -16,7 +16,7 @@ export async function test_emit_json_schema(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_misc.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_misc.ts index 3d37cb26919..6a8a4981f4a 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_misc.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_misc.ts @@ -14,7 +14,7 @@ export async function test_emit_misc(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_native.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_native.ts index c195f40f035..32aad6a2fb8 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_native.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_native.ts @@ -16,7 +16,7 @@ export async function test_emit_native(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_noemit_override.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_noemit_override.ts index ba8c0614a58..565388681c3 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_noemit_override.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_noemit_override.ts @@ -11,7 +11,7 @@ export async function test_emit_noemit_override(): Promise { fs.rmSync(outDir, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_objects.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_objects.ts index 8e8361e9f05..752e4f8d3d3 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_objects.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_objects.ts @@ -16,7 +16,7 @@ export async function test_emit_objects(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_outdir.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_outdir.ts index 12e92d6291a..30acaabca23 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_outdir.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_outdir.ts @@ -11,7 +11,7 @@ export async function test_emit_outdir(): Promise { fs.rmSync(outDir, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_plugin_js.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_plugin_js.ts index eb7bb5d1570..06da6f3f2c0 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_plugin_js.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_plugin_js.ts @@ -10,8 +10,8 @@ export async function test_emit_plugin_js(): Promise { const outDir = path.join(fixture, "dist"); fs.rmSync(outDir, { recursive: true, force: true }); - const result = runTtsc(["build", "--emit", "--tsconfig=tsconfig.json"], fixture); - assert.equal(result.status, 0, `ttsc build should succeed; stderr:\n${result.stderr}`); + const result = runTtsc(["--emit", "--tsconfig=tsconfig.json"], fixture); + assert.equal(result.status, 0, `ttsc should succeed; stderr:\n${result.stderr}`); const output = path.join(outDir, "src", "main.js"); assert.equal(fs.existsSync(output), true, `expected emitted file at ${output}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_primitives.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_primitives.ts index ffb5b491489..6049c48d483 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_primitives.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_primitives.ts @@ -6,7 +6,7 @@ import { TestGlobal } from "../TestGlobal"; import { runTtsc } from "../utils/runTtsc"; /** - * End-to-end emit test: run `ttsc build --emit` on the primitives fixture, + * End-to-end emit test: run `ttsc --emit` on the primitives fixture, * then require the generated dist/main.js and assert the compiled validators * return the expected booleans for a handful of inputs. This is the core * proof that ttsc produces runnable TS7/tsgo output byte-for-byte compatible @@ -19,13 +19,13 @@ export async function test_emit_primitives(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal( result.status, 0, - `ttsc build --emit should succeed; stderr=\n${result.stderr}`, + `ttsc --emit should succeed; stderr=\n${result.stderr}`, ); const mainPath = path.join(dist, "main.js"); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_stress.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_stress.ts index 5f02affaba4..2275db79a3d 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_stress.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_stress.ts @@ -17,7 +17,7 @@ export async function test_emit_stress(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_emit_tagged.ts b/toolchain-tests/test-typia-ttsc/src/features/test_emit_tagged.ts index 195b2f7876c..8f449440daa 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_emit_tagged.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_emit_tagged.ts @@ -16,7 +16,7 @@ export async function test_emit_tagged(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const result = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(result.status, 0, `stderr:\n${result.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_js_api.ts b/toolchain-tests/test-typia-ttsc/src/features/test_js_api.ts index a0b11f6941e..1e52c1e62cf 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_js_api.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_js_api.ts @@ -44,7 +44,7 @@ export async function test_js_api(): Promise { "..", "toolchain", "ttsc", - "bin", + "native", process.platform === "win32" ? "ttsc-native.exe" : "ttsc-native", ); const fixture = path.join(TestGlobal.ROOT, "fixtures", "primitives"); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_missing_tsconfig.ts b/toolchain-tests/test-typia-ttsc/src/features/test_missing_tsconfig.ts index 3748b1fcb4f..2eb1eecf272 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_missing_tsconfig.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_missing_tsconfig.ts @@ -4,9 +4,9 @@ import * as os from "os"; import { runTtsc } from "../utils/runTtsc"; export async function test_missing_tsconfig(): Promise { - // Run build in /tmp where no tsconfig lives. + // Run in /tmp where no tsconfig lives. const { stderr, status } = runTtsc( - ["build", "--tsconfig=tsconfig-does-not-exist.json"], + ["--tsconfig=tsconfig-does-not-exist.json"], os.tmpdir(), ); assert.equal(status, 2, "missing tsconfig must exit 2"); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_rewrite_idempotent.ts b/toolchain-tests/test-typia-ttsc/src/features/test_rewrite_idempotent.ts index dd52a69796e..cbc280712e1 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_rewrite_idempotent.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_rewrite_idempotent.ts @@ -17,7 +17,7 @@ export async function test_rewrite_idempotent(): Promise { fs.rmSync(dist, { recursive: true, force: true }); const first = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(first.status, 0, `first run failed:\n${first.stderr}`); @@ -32,7 +32,7 @@ export async function test_rewrite_idempotent(): Promise { // Second emit into the same tree: the sentinel must short-circuit the // rewrite pass and leave the file textually identical. const second = runTtsc( - ["build", "--tsconfig=tsconfig.json", "--emit", "--quiet"], + ["--tsconfig=tsconfig.json", "--emit", "--quiet"], fixture, ); assert.equal(second.status, 0, `second run failed:\n${second.stderr}`); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_setup_contract.ts b/toolchain-tests/test-typia-ttsc/src/features/test_setup_contract.ts index 0ff0d16a5c2..9be31ecd252 100644 --- a/toolchain-tests/test-typia-ttsc/src/features/test_setup_contract.ts +++ b/toolchain-tests/test-typia-ttsc/src/features/test_setup_contract.ts @@ -8,7 +8,10 @@ import { TestGlobal } from "../TestGlobal"; export async function test_setup_contract(): Promise { const root = path.resolve(TestGlobal.ROOT, "..", ".."); const { PluginConfigurator } = require( - path.join(root, "packages/typia/lib/executable/setup/PluginConfigurator.js"), + path.join( + root, + "packages/typia/lib/executable/setup/PluginConfigurator.js", + ), ) as { PluginConfigurator: { configure(args: { manager: string; project: string }): Promise; @@ -93,7 +96,7 @@ export async function test_setup_contract(): Promise { }; assert.deepEqual(parsed.compilerOptions.plugins, [ { name: "keep-me" }, - { transform: "typia/lib/ttsc/plugin" }, + { transform: "typia/lib/transform" }, ]); assert.equal(parsed.compilerOptions.skipLibCheck, true); assert.equal(parsed.compilerOptions.strictNullChecks, true); diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_ttsx_blocks_entry_type_error.ts b/toolchain-tests/test-typia-ttsc/src/features/test_ttsx_blocks_entry_type_error.ts new file mode 100644 index 00000000000..26742da5a7e --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/src/features/test_ttsx_blocks_entry_type_error.ts @@ -0,0 +1,19 @@ +import * as assert from "assert"; +import * as path from "path"; + +import { TestGlobal } from "../TestGlobal"; +import { runTtsx } from "../utils/runTtsx"; + +export async function test_ttsx_blocks_entry_type_error(): Promise { + const fixture = path.join(TestGlobal.ROOT, "fixtures", "runner-entry-error"); + const entry = path.join(fixture, "src", "main.ts"); + + const result = runTtsx(["--project", "tsconfig.json", entry], fixture); + assert.notEqual(result.status, 0, "ttsx should fail before execution"); + assert.equal(result.stdout.includes("TTSX_ENTRY_SHOULD_NOT_RUN"), false); + assert.match(result.stderr, /ttsx: project check failed/); + assert.match( + result.stderr, + /Type 'number' is not assignable to type 'string'/, + ); +} diff --git a/toolchain-tests/test-typia-ttsc/src/features/test_ttsx_blocks_import_type_error.ts b/toolchain-tests/test-typia-ttsc/src/features/test_ttsx_blocks_import_type_error.ts new file mode 100644 index 00000000000..dc589fa202a --- /dev/null +++ b/toolchain-tests/test-typia-ttsc/src/features/test_ttsx_blocks_import_type_error.ts @@ -0,0 +1,19 @@ +import * as assert from "assert"; +import * as path from "path"; + +import { TestGlobal } from "../TestGlobal"; +import { runTtsx } from "../utils/runTtsx"; + +export async function test_ttsx_blocks_import_type_error(): Promise { + const fixture = path.join(TestGlobal.ROOT, "fixtures", "runner-import-error"); + const entry = path.join(fixture, "src", "main.ts"); + + const result = runTtsx(["--project", "tsconfig.json", entry], fixture); + assert.notEqual(result.status, 0, "ttsx should fail before execution"); + assert.equal(result.stdout.includes("TTSX_IMPORT_SHOULD_NOT_RUN"), false); + assert.match(result.stderr, /ttsx: project check failed/); + assert.match( + result.stderr, + /Type 'number' is not assignable to type 'string'/, + ); +} diff --git a/toolchain-tests/test-typia-ttsc/tsconfig.json b/toolchain-tests/test-typia-ttsc/tsconfig.json index 6227b927df9..074b8068b99 100644 --- a/toolchain-tests/test-typia-ttsc/tsconfig.json +++ b/toolchain-tests/test-typia-ttsc/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../config/tsconfig.json", "compilerOptions": { + "ignoreDeprecations": "6.0", + "moduleResolution": "bundler", "rootDir": "src" }, "include": ["src"] diff --git a/toolchain/ttsc/README.md b/toolchain/ttsc/README.md index 8e3d89f9746..d366e57be78 100644 --- a/toolchain/ttsc/README.md +++ b/toolchain/ttsc/README.md @@ -1,33 +1,472 @@ # @typia/ttsc -`@typia/ttsc` is the standalone `tsgo` host in this monorepo. +`@typia/ttsc` is a standalone compiler adapter and plugin host for the `typescript-go` toolchain. -- It owns `build`, `check`, and `transform`. -- It loads `compilerOptions.plugins` from `tsconfig.json`. -- It selects the matching native backend binary for each consumer plugin. -- It keeps the standalone host code and the consumer-specific native code separate. +It is the package that owns: -typia is the first native consumer. Its TypeScript plugin entry is `typia/lib/ttsc/plugin`, its native implementation lives under `packages/core/native`, and its native rewrite backend lives under `packages/transform/native`. +- `ttsc` +- `ttsc --noEmit` +- `ttsc transform` +- tsconfig plugin loading +- native consumer binary selection +- JavaScript-side post-processing hooks for emitted output -## Layout +`typia` is the first consumer, but `@typia/ttsc` is not typia-specific. The host surface is intended to be reusable by any plugin that wants to sit on top of the same `typescript-go` lane. + +## Installation + +Today the compiler lane is: +```bash +npm install -D @typescript/native-preview @typia/ttsc +pnpm add -D @typescript/native-preview @typia/ttsc ``` -toolchain/ttsc/ -├── bin/ttsc.js # Public CLI / plugin host launcher -├── src/ # Node-side host, plugin, and platform code -├── driver/ # Generic tsgo host + emit rewrite mechanics -├── test/ # Node-side tests (node:test runner) -└── cmd/ttsc/ # Standalone native binary without consumer-specific rewrites + +Later the compiler package is expected to become `typescript@7`, but the current setup lane is still `@typescript/native-preview`. + +## Quick Start + +### 1. Add a plugin to `tsconfig.json` + +```json +{ + "compilerOptions": { + "plugins": [{ "transform": "typia/lib/transform" }] + } +} +``` + +### 2. Run `ttsc` + +```bash +ttsc +ttsc --noEmit +ttsc --watch +ttsc -p tsconfig.json +``` + +The CLI reads `compilerOptions.plugins`, resolves the plugin modules, chooses the matching native backend binary, and then runs the build or transform through that plugin-selected lane. + +## CLI + +### `ttsc` + +Build the project referenced by `tsconfig.json`. This follows the `tsc` / `tsgo` command shape: no subcommand means "compile the current project". + +```bash +ttsc +ttsc -p tsconfig.json +ttsc --emit +ttsc --noEmit +ttsc --outDir lib +ttsc --watch +ttsc --verbose +``` + +Notes: + +- `--quiet` is the default. +- `--verbose` prints the native build summary and emitted file list. +- `--watch` keeps the JS host alive and reruns the build when project files change. +- `ttsc build ...` remains a compatibility alias for the same project-build lane. + +### `ttsc --noEmit` + +Run the same host pipeline without emitting files. + +```bash +ttsc --noEmit +ttsc -p tsconfig.json --noEmit +ttsc --watch --noEmit +``` + +This is the CI / pre-commit lane. `ttsc check ...` remains a compatibility alias. + +### `ttsc transform` + +Transform a single source file and print the rewritten JavaScript to stdout. + +```bash +ttsc transform --file src/index.ts +ttsc transform --file src/index.ts --out tmp/index.js +ttsc transform --file src/index.ts --project tsconfig.json +``` + +This is the per-file hook bundler adapters consume. + +## Programmatic API + +`@typia/ttsc` exports a small JS API for bundlers and higher-level tools. + +```ts +import { build, check, transform, version } from "@typia/ttsc"; +``` + +### `transform(options)` + +Use this when you are writing a bundler adapter or any per-file transform hook. + +```ts +import { transform } from "@typia/ttsc"; + +const code = transform({ + file: "/project/src/index.ts", + tsconfig: "/project/tsconfig.json", + cwd: "/project", +}); +``` + +Important behavior: + +- `transform()` resolves the enclosing `tsconfig.json` unless `tsconfig` is given. +- `plugins` can override the tsconfig plugin list. +- `plugins: false` disables tsconfig plugin loading. +- plugin-declared `native.mode` selects the native rewrite backend. +- `rewriteMode` is a low-level override for tests and migration probes. +- `out` writes the final transformed text to a file instead of only returning it. + +### `build(options)` / `check(options)` + +```ts +import { build, check } from "@typia/ttsc"; + +const result = build({ + tsconfig: "tsconfig.json", + cwd: process.cwd(), + emit: true, +}); + +if (result.status !== 0) { + console.error(result.stderr); +} + +const checked = check({ + tsconfig: "tsconfig.json", +}); +``` + +`build()` and `check()` return `{ status, stdout, stderr }`. They do not throw on a non-zero compiler exit; callers decide how to surface the failure. + +### `version(options)` + +```ts +import { version } from "@typia/ttsc"; + +console.log(version()); +``` + +Useful for diagnostics or adapter user-agent strings. + +## tsconfig Plugin Contract + +`@typia/ttsc` reads `compilerOptions.plugins` from the resolved project config. + +Each plugin entry must have: + +```json +{ + "transform": "some-plugin-entry" +} +``` + +Optional extra keys are passed to the plugin factory unchanged. + +### Resolution rules + +`transform` may be: + +- a package specifier +- an absolute path +- a relative path from the project root + +The loader also has a source-checkout fallback for `*/lib/transform`, so workspace consumers can be developed before packing/publishing. + +## Writing a Plugin + +The host surface for plugin authors lives in: + +```ts +import { definePlugin } from "@typia/ttsc"; +``` + +There are two plugin shapes. + +- **Native transform plugin**: owns type analysis, call-site recognition, AST rewrite, + diagnostics, and emitted assets in a consumer-native backend. +- **JS output plugin**: receives emitted JavaScript text and returns edited JavaScript + text. + +The native shape is the TypeScript v7 path: the plugin describes the backend, +and the backend performs compiler work through the `typescript-go` lane. + +### Minimal native consumer plugin + +```ts +import { definePlugin } from "@typia/ttsc"; +import * as path from "node:path"; + +export default definePlugin(() => ({ + name: "my-consumer", + native: { + mode: "my-consumer", + binary: path.resolve(__dirname, "../native/ttsc-my-consumer.js"), + contractVersion: 1, + capabilities: ["rewrite", "diagnostics", "assets"], + }, +})); +``` + +This tells `ttsc`: + +- which native rewrite mode to request (`native.mode`) +- which consumer-owned native binary to run (`native.binary`) +- which plugin contract version the package was written for +- which capabilities the backend expects to own + +The backend binary is responsible for the work that old TypeScript +transformers used to do in-process: + +- loading the project through the `typescript-go` lane +- finding marker calls such as `typia.is()` +- analyzing types through native checker access or a serialized IR +- emitting the replacement JavaScript +- reporting plugin-specific diagnostics + +The plugin declaration should stay small. Keep compiler work in the native +backend and keep package discovery/configuration in the Node plugin. + +### Native backend descriptor + +```ts +interface TtscNativeBackend { + mode: string; + binary?: string; + contractVersion?: 1; + capabilities?: readonly string[]; +} ``` -## Commands +`mode` is the stable name passed to the native compiler process. `binary` +overrides the default `ttsc` binary when the plugin ships a consumer-specific +backend. `contractVersion` defaults to `1`; declaring it pins the plugin to the +current host protocol. `capabilities` is descriptive today and should be used by +plugin authors as a manifest of owned responsibilities. + +### Plugin with JS-side output post-processing + +```ts +import { definePlugin } from "@typia/ttsc"; + +export default definePlugin((config) => ({ + name: "banner-plugin", + transformOutput(context) { + if (context.command === "build") { + return `/* generated by ${config.transform} */\n${context.code}`; + } + return context.code; + }, +})); +``` + +`transformOutput()` receives JavaScript text, not a TypeScript AST. It runs: + +- after `ttsc transform` +- after emitted `.js` files are produced during `ttsc` + +Use this hook for text-level post-processing such as: + +- banner injection +- output string patching +- runtime helper import rewrites +- consumer-specific output normalization + +Put compiler-sensitive work in a native backend or in a separate IR bridge: + +- type analysis +- AST mutation +- call-site recognition +- `ts.Program` / `ts.TypeChecker` transformer compatibility + +## Transform Development Guide + +This is the recommended path for a new transform consumer. + +### 1. Publish a tiny Node plugin entry + +Create a package entry such as `my-lib/lib/transform`. + +```ts +import { definePlugin } from "@typia/ttsc"; +import * as path from "node:path"; + +export default definePlugin((config, context) => ({ + name: "my-lib", + native: { + mode: "my-lib", + binary: path.resolve(__dirname, "../../native/ttsc-my-lib.js"), + contractVersion: 1, + capabilities: ["rewrite", "diagnostics"], + }, + transformOutput(output) { + return output.code; + }, +})); +``` + +The factory receives the raw `compilerOptions.plugins[]` entry as `config` and +the resolved project locations as `context`. Use this entry for +package-relative asset lookup, feature flags, diagnostics labels, and manifest +construction. Keep type analysis and AST rewriting in the native backend. + +### 2. Add the plugin to `tsconfig.json` + +```json +{ + "compilerOptions": { + "plugins": [{ "transform": "my-lib/lib/transform" }] + } +} +``` + +The same entry is consumed by the CLI and by the JS API. + +### 3. Call `ttsc` from a build + +```bash +ttsc +ttsc --noEmit +ttsc --outDir bin +``` + +The host loads the plugin, selects `native.mode`, and runs the selected native +backend. The user should not have to pass `--rewrite-mode` for normal use. + +### 4. Reuse the same contract from a bundler + +```ts +import { transform } from "@typia/ttsc"; + +const code = transform({ + cwd: "/project", + file: "/project/src/index.ts", + tsconfig: "/project/tsconfig.json", + plugins: [{ transform: "my-lib/lib/transform" }], +}); +``` + +When `plugins` is supplied, it replaces the `tsconfig` plugin list. This is +useful for bundlers that want deterministic plugin state. + +### 5. Keep the compiler boundary honest + +`typescript-go` exposes a new compiler lane. A `ttsc` transform should exchange +stable artifacts: + +- plugin manifest +- serialized request/response +- generated JavaScript +- emitted asset list +- diagnostic payload + +Design transforms around those artifacts. Treat `typescript-go` internal Go +structs, TypeScript's old JS `ts.Node` objects, and typia-specific helper names +as implementation details. + +### Factory context + +A plugin factory receives: + +- `binary`: the fallback `ttsc` native binary path +- `cwd`: invocation working directory +- `projectRoot`: resolved project root +- `tsconfig`: resolved project config path + +Use this if the plugin needs to locate assets relative to the consumer package or project root. + +## Host Model + +The current host is deliberately split: + +- the public CLI / JS API lives in Node +- the generic compiler driver lives in Go +- consumer plugins may point the host at their own native binary + +This allows: + +- one shared Node-side plugin resolution pipeline +- consumer-specific native analyzers / rewriters +- bundlers and runners to reuse the same surface + +## Current Constraints + +These are real current constraints, not future ideals. + +### 1. One native mode per invocation + +Today `@typia/ttsc` allows only one `native.mode` / `native.binary` pair per invocation. + +If two loaded plugins request different native modes or binaries, the host throws. + +That means: + +- composing multiple native consumer plugins in one build is not supported yet +- text-only `transformOutput()` plugins can still stack + +### 2. `transformOutput()` is JS-text level only + +There is no JS-side AST hook in the plugin contract today. + +If you need: + +- type analysis +- AST mutation +- call-site recognition + +that work must happen in the native consumer backend, not in `transformOutput()`. + +### 3. `plugins` override replaces the tsconfig list + +In the JS API, passing `plugins` replaces the tsconfig plugin list rather than merging with it. + +This is deliberate but easy to miss when writing adapters. + +### 4. Stable `typescript@7` is not the current default + +The current documented and implemented setup lane is still `@typescript/native-preview`. + +## Typical Integration Patterns + +### Consumer package + +This is what `typia` does today: + +- publish a tsconfig plugin entry such as `typia/lib/transform` +- point that entry at a consumer-native binary +- optionally layer JS-side output transforms on top + +### Bundler adapter + +This is what `@typia/unplugin` does today: + +- call `transform()` for per-file rewrites +- supply an explicit plugin list when it wants to bypass tsconfig state +- let `@typia/ttsc` select the native backend + +### Runner + +This is what `@typia/ttsx` does today: + +- use `transform()` for single-file hot execution +- use `build()` when it has to emit an ESM project into a cache directory + +## Development ```bash # TypeScript side pnpm build pnpm test:ts -# Go side (requires Go 1.26+) +# Go side pnpm go:build pnpm go:test pnpm go:vet @@ -36,14 +475,20 @@ pnpm go:vet pnpm test ``` -## Philosophy +Go 1.26+ is expected for the native side. -- One Go binary per platform, distributed as `@typia/ttsc-{platform}-{arch}` optional dependencies. -- `ttsc` reads `compilerOptions.plugins` and composes native and JS plugins through one host surface. -- Native consumers supply their own backend binary; the standalone host no longer embeds typia-specific analyzers or emitters. -- The generic driver package hooks into typescript-go through the local shim layer. -- The public CLI lives in Node so `ttsx` and third-party plugins reuse the same host pipeline. +## Layout + +``` +toolchain/ttsc/ +├── src/launcher/ # public CLI launcher scripts +├── native/ # local generated Go binary from pnpm go:build +├── src/ # JS host, plugin contract, platform resolution, API +├── driver/ # generic native rewrite / emit driver +├── cmd/ttsc/ # standalone native binary +└── test/ # JS-side tests +``` ## License -MIT. See [../../LICENSE](../../LICENSE). Third-party attributions in [THIRD-PARTY-LICENSES.md](./THIRD-PARTY-LICENSES.md). +MIT. See [../../LICENSE](../../LICENSE). diff --git a/toolchain/ttsc/cmd/ttsc/build.go b/toolchain/ttsc/cmd/ttsc/build.go index 83d7d0c6616..ee956631b39 100644 --- a/toolchain/ttsc/cmd/ttsc/build.go +++ b/toolchain/ttsc/cmd/ttsc/build.go @@ -12,7 +12,7 @@ import ( "github.com/samchon/typia/toolchain/ttsc/driver" ) -// runBuild implements `ttsc build`. Two modes: +// runBuild implements the `ttsc` project-build lane. Two modes: // // - default (dry run): analyze quietly, surfacing only diagnostics/errors. // - --emit: also run tsgo's emit pipeline, patching every recognized native @@ -33,7 +33,7 @@ func runBuild(args []string) int { return 2 } if *emit && *noEmit { - fmt.Fprintln(stderr, "ttsc build: --emit and --noEmit are mutually exclusive") + fmt.Fprintln(stderr, "ttsc: --emit and --noEmit are mutually exclusive") return 2 } if *verbose { @@ -45,7 +45,7 @@ func runBuild(args []string) int { var err error cwd, err = os.Getwd() if err != nil { - fmt.Fprintf(stderr, "ttsc build: could not get working directory: %v\n", err) + fmt.Fprintf(stderr, "ttsc: could not get working directory: %v\n", err) return 2 } } @@ -56,26 +56,28 @@ func runBuild(args []string) int { OutDir: *outDir, }) if err != nil { - fmt.Fprintf(stderr, "ttsc build: %v\n", err) + fmt.Fprintf(stderr, "ttsc: %v\n", err) return 2 } if len(diags) > 0 { - for _, d := range diags { - fmt.Fprintln(stderr, " -", d.String()) - } + driver.WritePrettyDiagnostics(stderr, diags, cwd) return 2 } defer prog.Close() + if diags := prog.Diagnostics(); len(diags) > 0 { + driver.WritePrettyDiagnostics(stderr, diags, cwd) + return 2 + } rewrites := driver.NewRewriteSet() shouldEmit := !prog.ParsedConfig.ParsedConfig.CompilerOptions.NoEmit.IsTrue() if *rewriteMode != "none" { - fmt.Fprintf(stderr, "ttsc build: rewrite backend %q is not built into the standalone ttsc binary\n", *rewriteMode) - fmt.Fprintln(stderr, "ttsc build: run through the JS host with the matching compiler plugin so it can select the consumer-provided native binary") + fmt.Fprintf(stderr, "ttsc: rewrite backend %q is not built into the standalone ttsc binary\n", *rewriteMode) + fmt.Fprintln(stderr, "ttsc: run through the JS host with the matching compiler plugin so it can select the consumer-provided native binary") return 2 } if !*quiet { - fmt.Fprintf(stdout, "// ttsc build: tsconfig=%s cwd=%s sites=%d emit=%v rewrite=%s\n", *tsconfigPath, cwd, 0, shouldEmit, *rewriteMode) + fmt.Fprintf(stdout, "// ttsc: tsconfig=%s cwd=%s sites=%d emit=%v rewrite=%s\n", *tsconfigPath, cwd, 0, shouldEmit, *rewriteMode) } if shouldEmit { @@ -86,14 +88,14 @@ func runBuild(args []string) int { ) res, eDiags, err := prog.EmitAll(rewrites, writeFile) if err != nil { - fmt.Fprintf(stderr, "ttsc build: emit failed: %v\n", err) + fmt.Fprintf(stderr, "ttsc: emit failed: %v\n", err) return 3 } for _, d := range eDiags { fmt.Fprintln(stderr, " -", d.String()) } if !*quiet { - fmt.Fprintf(stdout, "// ttsc build: emitted=%d files\n", len(res.EmittedFiles)) + fmt.Fprintf(stdout, "// ttsc: emitted=%d files\n", len(res.EmittedFiles)) for _, f := range res.EmittedFiles { rel := f if abs, err := filepath.Rel(cwd, f); err == nil { @@ -105,22 +107,22 @@ func runBuild(args []string) int { if *manifestPath != "" { data, err := json.Marshal(res.EmittedFiles) if err != nil { - fmt.Fprintf(stderr, "ttsc build: manifest marshal failed: %v\n", err) + fmt.Fprintf(stderr, "ttsc: manifest marshal failed: %v\n", err) return 3 } if err := os.MkdirAll(filepath.Dir(*manifestPath), 0o755); err != nil { - fmt.Fprintf(stderr, "ttsc build: manifest mkdir failed: %v\n", err) + fmt.Fprintf(stderr, "ttsc: manifest mkdir failed: %v\n", err) return 3 } if err := os.WriteFile(*manifestPath, data, 0o644); err != nil { - fmt.Fprintf(stderr, "ttsc build: manifest write failed: %v\n", err) + fmt.Fprintf(stderr, "ttsc: manifest write failed: %v\n", err) return 3 } } } if !*quiet { - fmt.Fprintf(stdout, "// ttsc build: recognized=%d total=%d rewrites=%d\n", 0, 0, rewrites.Len()) + fmt.Fprintf(stdout, "// ttsc: recognized=%d total=%d rewrites=%d\n", 0, 0, rewrites.Len()) } return 0 } diff --git a/toolchain/ttsc/cmd/ttsc/main.go b/toolchain/ttsc/cmd/ttsc/main.go index f4fa827afc9..165199caa24 100644 --- a/toolchain/ttsc/cmd/ttsc/main.go +++ b/toolchain/ttsc/cmd/ttsc/main.go @@ -40,8 +40,7 @@ func main() { func run(args []string) int { if len(args) == 0 { - printHelp(stdout) - return 0 + return runBuild(nil) } switch args[0] { @@ -133,22 +132,20 @@ ttsc — standalone typescript-go host. Usage: ttsc - ttsc [file.ts] + ttsc -p tsconfig.json ttsc --watch - ttsc [command] [options] + ttsc --noEmit + ttsc transform --file=src/main.ts -Commands: - build Compile every .ts in a project and emit rewritten .js. - check Run the analyzer without emitting — type-only validation. +Project build: + ttsc compiles the current tsconfig.json, matching the tsc/tsgo shape. + build Compatibility alias for the same project build lane. + check Compatibility alias for --noEmit validation. transform Emit a single file's rewritten JS (bundler plugin hook). demo Run a native backend smoke pipeline with synthetic input. version Print version, build info, and platform. help Show this help. -Drop-in tsc aliases: - ttsc -p tsconfig.json ≡ ttsc build --tsconfig=tsconfig.json --emit - ttsc --project tsconfig.json (same) - Build options: --tsconfig=FILE Path to tsconfig.json (default: tsconfig.json). --cwd=DIR Override working directory. @@ -171,9 +168,9 @@ Demo options: Examples: ttsc --version - ttsc build --tsconfig=./tsconfig.json + ttsc ttsc -p ./tsconfig.json - ttsc check + ttsc --noEmit ttsc transform --file=src/main.ts ttsc demo --type=number @@ -181,7 +178,7 @@ Integration guide (bundlers): - vite / webpack / rollup / esbuild: shell out to "ttsc transform" per file, forward stdout as the bundler transform result. @typia/unplugin wraps this for you. - - Next.js / Nuxt / Bun: "ttsc build" in your pipeline replaces tsc and + - Next.js / Nuxt / Bun: "ttsc" in your pipeline replaces tsc and the rewritten .js feeds the runtime directly. - Monorepo / pnpm workspace: share one ttsc binary via a root script; per-package tsconfig.json references work unchanged. diff --git a/toolchain/ttsc/cmd/ttsc/main_test.go b/toolchain/ttsc/cmd/ttsc/main_test.go index bdc5a3db603..b0353a15b67 100644 --- a/toolchain/ttsc/cmd/ttsc/main_test.go +++ b/toolchain/ttsc/cmd/ttsc/main_test.go @@ -2,6 +2,8 @@ package main import ( "bytes" + "os" + "path/filepath" "strings" "testing" ) @@ -21,14 +23,17 @@ func withCapture(fn func()) (out, errOut string) { return outBuf.String(), errBuf.String() } -func TestRunNoArgsPrintsHelp(t *testing.T) { - out, _ := withCapture(func() { - if code := run(nil); code != 0 { - t.Errorf("expected exit 0 on no args, got %d", code) +func TestRunNoArgsAttemptsProjectBuild(t *testing.T) { + out, errOut := withCapture(func() { + if code := run(nil); code == 0 { + t.Errorf("expected no-args run without a local tsconfig to fail") } }) - if !strings.Contains(out, "Usage:") { - t.Errorf("expected Usage banner in stdout, got %q", out) + if strings.Contains(out, "Usage:") { + t.Errorf("no-args ttsc must build the project, not print help; got %q", out) + } + if !strings.Contains(errOut, "tsconfig") { + t.Errorf("expected no-args build to look for tsconfig, got stderr %q", errOut) } } @@ -79,6 +84,141 @@ func TestRunUnknownCommandExits2(t *testing.T) { } } +func TestRunBuildBlocksSemanticDiagnosticsBeforeEmit(t *testing.T) { + root := t.TempDir() + writeProjectFile(t, root, "tsconfig.json", `{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "bin", + "strict": true + } +} +`) + writeProjectFile(t, root, "index.ts", `const value: string = 123; +console.log(value); +`) + + _, errOut := withCapture(func() { + if code := runBuild([]string{"--cwd", root, "--emit"}); code != 2 { + t.Errorf("expected exit 2, got %d", code) + } + }) + if !strings.Contains(errOut, "Type 'number' is not assignable to type 'string'") { + t.Errorf("expected semantic diagnostic, got %q", errOut) + } + if !strings.Contains(errOut, "\x1b[91merror\x1b[0m") || !strings.Contains(errOut, "TS2322") { + t.Errorf("expected colored TypeScript-style diagnostic header, got %q", errOut) + } + if !strings.Contains(errOut, "\x1b[7m1\x1b[0m") || !strings.Contains(errOut, "~~~~~") { + t.Errorf("expected source context with highlighted span, got %q", errOut) + } + if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); !os.IsNotExist(err) { + t.Errorf("expected no emitted JS, stat err=%v", err) + } +} + +func TestRunBuildAllowsUnusedTypeParametersOnOverloadSignatures(t *testing.T) { + root := t.TempDir() + writeProjectFile(t, root, "tsconfig.json", `{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "bin", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true + } +} +`) + writeProjectFile(t, root, "index.ts", `export function marker(input: unknown): string; +export function marker(input: unknown): string { + return String(input); +} +`) + + _, errOut := withCapture(func() { + if code := runBuild([]string{"--cwd", root, "--emit", "--quiet"}); code != 0 { + t.Errorf("expected exit 0, got %d", code) + } + }) + if strings.Contains(errOut, "declared but never used") || strings.Contains(errOut, "All type parameters are unused") { + t.Errorf("overload signature type parameters must not fail noUnused checks, got %q", errOut) + } + if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); err != nil { + t.Errorf("expected emitted JS, stat err=%v", err) + } +} + +func TestRunBuildBlocksUnusedParametersBeforeEmit(t *testing.T) { + root := t.TempDir() + writeProjectFile(t, root, "tsconfig.json", `{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "bin", + "strict": true, + "noUnusedParameters": true + } +} +`) + writeProjectFile(t, root, "index.ts", `export function run(unused: string): number { + return 1; +} +`) + + _, errOut := withCapture(func() { + if code := runBuild([]string{"--cwd", root, "--emit"}); code != 2 { + t.Errorf("expected exit 2, got %d", code) + } + }) + if !strings.Contains(errOut, "'unused' is declared but its value is never read") && + !strings.Contains(errOut, "'unused' is declared but never used") { + t.Errorf("expected unused parameter diagnostic, got %q", errOut) + } + if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); !os.IsNotExist(err) { + t.Errorf("expected no emitted JS, stat err=%v", err) + } +} + +func TestRunBuildBlocksSyntacticDiagnosticsBeforeEmit(t *testing.T) { + root := t.TempDir() + writeProjectFile(t, root, "tsconfig.json", `{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "bin" + } +} +`) + writeProjectFile(t, root, "index.ts", `console.log("before"); +const = ; +`) + + _, errOut := withCapture(func() { + if code := runBuild([]string{"--cwd", root, "--emit"}); code != 2 { + t.Errorf("expected exit 2, got %d", code) + } + }) + if !strings.Contains(errOut, "Variable declaration expected") { + t.Errorf("expected syntactic diagnostic, got %q", errOut) + } + if _, err := os.Stat(filepath.Join(root, "bin", "index.js")); !os.IsNotExist(err) { + t.Errorf("expected no emitted JS, stat err=%v", err) + } +} + +func writeProjectFile(t *testing.T, root string, name string, contents string) { + t.Helper() + file := filepath.Join(root, name) + if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(file, []byte(contents), 0o644); err != nil { + t.Fatal(err) + } +} + func TestRunDemoString(t *testing.T) { out, _ := withCapture(func() { if code := run([]string{"demo", "--type=string"}); code != 0 { diff --git a/toolchain/ttsc/cmd/ttsc/transform.go b/toolchain/ttsc/cmd/ttsc/transform.go index 389cb712f76..b64bce3962e 100644 --- a/toolchain/ttsc/cmd/ttsc/transform.go +++ b/toolchain/ttsc/cmd/ttsc/transform.go @@ -60,12 +60,14 @@ func runTransform(args []string) int { return 2 } if len(diags) > 0 { - for _, d := range diags { - fmt.Fprintln(stderr, " -", d.String()) - } + driver.WritePrettyDiagnostics(stderr, diags, cwd) return 2 } defer prog.Close() + if diags := prog.Diagnostics(); len(diags) > 0 { + driver.WritePrettyDiagnostics(stderr, diags, cwd) + return 2 + } absFile := *file if !filepath.IsAbs(absFile) { @@ -80,23 +82,23 @@ func runTransform(args []string) int { return 2 } - // Filter WriteFile to keep only the target file's output in memory. + target := prog.SourceFile(absFile) + if target == nil { + fmt.Fprintf(stderr, "ttsc transform: source file is not in program: %s\n", absFile) + return 2 + } + + // Capture the requested source file's JS output. var captured []byte - bestScore := 0 capture := func(name, text string, _ *shimcompiler.WriteFileData) error { rel := filepath.ToSlash(name) if !strings.HasSuffix(rel, ".js") { return nil } - score := sharedSourceStemSegments(rel, absFile) - if score == 0 || score < bestScore { - return nil - } captured = []byte(text) - bestScore = score return nil } - _, eDiags, err := prog.EmitAll(rewrites, capture) + _, eDiags, err := prog.EmitFile(rewrites, target, capture) if err != nil { fmt.Fprintf(stderr, "ttsc transform: emit: %v\n", err) return 3 diff --git a/toolchain/ttsc/driver/program.go b/toolchain/ttsc/driver/program.go index 7aacad74a39..c449b7cae8b 100644 --- a/toolchain/ttsc/driver/program.go +++ b/toolchain/ttsc/driver/program.go @@ -4,11 +4,14 @@ import ( "context" "errors" "fmt" + "io" + "path/filepath" "github.com/microsoft/typescript-go/shim/ast" shimchecker "github.com/microsoft/typescript-go/shim/checker" shimcompiler "github.com/microsoft/typescript-go/shim/compiler" "github.com/microsoft/typescript-go/shim/core" + shimdiagnosticwriter "github.com/microsoft/typescript-go/shim/diagnosticwriter" shimscanner "github.com/microsoft/typescript-go/shim/scanner" "github.com/microsoft/typescript-go/shim/tsoptions" "github.com/microsoft/typescript-go/shim/tspath" @@ -22,6 +25,21 @@ type Diagnostic struct { Line int Column int Message string + raw *ast.Diagnostic +} + +// SourceFile returns the program source file matching filename. +func (p *Program) SourceFile(filename string) *ast.SourceFile { + if p == nil || p.TSProgram == nil { + return nil + } + normalized := filepath.ToSlash(filename) + for _, file := range p.TSProgram.SourceFiles() { + if filepath.ToSlash(file.FileName()) == normalized { + return file + } + } + return nil } // String returns a `path:line:col: message` formatted string. @@ -35,6 +53,24 @@ func (d Diagnostic) String() string { return fmt.Sprintf("%s: %s", d.File, d.Message) } +// WritePrettyDiagnostics renders diagnostics with TypeScript-style colors, +// source snippets and the trailing error summary when raw tsgo diagnostic +// objects are available. It falls back to the legacy one-line form for +// diagnostics assembled outside tsgo. +func WritePrettyDiagnostics(w io.Writer, diagnostics []Diagnostic, cwd string) { + raw := make([]*ast.Diagnostic, 0, len(diagnostics)) + for _, d := range diagnostics { + if d.raw == nil { + for _, d := range diagnostics { + fmt.Fprintln(w, " -", d.String()) + } + return + } + raw = append(raw, d.raw) + } + shimdiagnosticwriter.FormatASTDiagnosticsWithColorAndContext(w, raw, cwd) +} + // Program is the shim-agnostic facade the rest of the engine sees. type Program struct { TSProgram *shimcompiler.Program @@ -46,7 +82,7 @@ type Program struct { } // LoadProgramOptions controls tsconfig overrides applied before tsgo creates -// the program. `ForceEmit` is used by `ttsc build --emit` and `ttsc transform` +// the program. `ForceEmit` is used by `ttsc --emit` and `ttsc transform` // so runtime compilation still works when the project defaults to `noEmit`. type LoadProgramOptions struct { ForceEmit bool @@ -102,12 +138,17 @@ func CreateProgramFromConfig(parsed *tsoptions.ParsedCommandLine, host shimcompi return p, nil, nil } -// LoadProgram is the one-shot convenience used by `ttsc build`. +// LoadProgram is the one-shot convenience used by `ttsc`. // It parses the tsconfig, creates a program and a type-checker, and returns // the wrapped facade. // // cwd must be absolute; tsconfigPath may be relative to cwd. func LoadProgram(cwd, tsconfigPath string, options LoadProgramOptions) (*Program, []Diagnostic, error) { + if !filepath.IsAbs(cwd) { + if abs, err := filepath.Abs(cwd); err == nil { + cwd = abs + } + } cwd = tspath.ResolvePath(cwd) fs := DefaultFS() host := DefaultHost(cwd, fs) @@ -184,6 +225,53 @@ func (p *Program) SourceFiles() []*ast.SourceFile { return out } +// Diagnostics returns project diagnostics that must block compilation or +// runtime execution before any JavaScript is emitted or evaluated. +func (p *Program) Diagnostics() []Diagnostic { + if p == nil || p.TSProgram == nil { + return []Diagnostic{{Message: "driver: nil program"}} + } + ctx := context.Background() + raw := make([]*ast.Diagnostic, 0) + raw = append(raw, p.TSProgram.GetConfigFileParsingDiagnostics()...) + raw = append(raw, p.TSProgram.GetProgramDiagnostics()...) + raw = append(raw, p.TSProgram.GetSyntacticDiagnostics(ctx, nil)...) + raw = append(raw, p.TSProgram.GetSemanticDiagnostics(ctx, nil)...) + raw = append(raw, p.TSProgram.GetGlobalDiagnostics(ctx)...) + raw = filterDiagnostics(raw) + return convertDiagnostics(shimcompiler.SortAndDeduplicateDiagnostics(raw)) +} + +func filterDiagnostics(in []*ast.Diagnostic) []*ast.Diagnostic { + out := in[:0] + for _, d := range in { + if isUnusedOverloadSignatureTypeParameterDiagnostic(d) { + continue + } + out = append(out, d) + } + return out +} + +func isUnusedOverloadSignatureTypeParameterDiagnostic(d *ast.Diagnostic) bool { + if d == nil || d.File() == nil { + return false + } + switch d.Code() { + case 6196, 6205: // unused declaration / all type parameters are unused + default: + return false + } + node := ast.GetNodeAtPosition(d.File(), d.Pos(), false) + for node != nil { + if node.Kind == ast.KindFunctionDeclaration { + return node.Body() == nil + } + node = node.Parent + } + return false +} + // convertDiagnostics translates shim-specific diagnostics into the plain // Diagnostic struct with line/column populated via tsgo's ECMALineMap (the // same helper tsc uses for its "file:line:col: message" banner). @@ -193,7 +281,7 @@ func convertDiagnostics(in []*ast.Diagnostic) []Diagnostic { if d == nil { continue } - diag := Diagnostic{Message: d.String()} + diag := Diagnostic{Message: d.String(), raw: d} if file := d.File(); file != nil { diag.File = file.FileName() if pos := d.Pos(); pos >= 0 { diff --git a/toolchain/ttsc/driver/rewrite.go b/toolchain/ttsc/driver/rewrite.go index c056beb2dfc..975ed734405 100644 --- a/toolchain/ttsc/driver/rewrite.go +++ b/toolchain/ttsc/driver/rewrite.go @@ -72,6 +72,16 @@ const RewriteSentinel = "/* @ttsc-rewritten */" // `writeFile` is nil, the patched JS is written to disk via the standard // tsgo WriteFile. func (p *Program) EmitAll(rs *RewriteSet, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) { + return p.emit(rs, nil, writeFile) +} + +// EmitFile runs tsgo's emitter for one source file, applying the same rewrite +// pipeline as EmitAll. +func (p *Program) EmitFile(rs *RewriteSet, target *ast.SourceFile, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) { + return p.emit(rs, target, writeFile) +} + +func (p *Program) emit(rs *RewriteSet, target *ast.SourceFile, writeFile shimcompiler.WriteFile) (*shimcompiler.EmitResult, []Diagnostic, error) { if p == nil || p.TSProgram == nil { return nil, nil, errors.New("driver: nil program") } @@ -100,7 +110,8 @@ func (p *Program) EmitAll(rs *RewriteSet, writeFile shimcompiler.WriteFile) (*sh } result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{ - WriteFile: wf, + TargetSourceFile: target, + WriteFile: wf, }) if result == nil { return nil, nil, errors.New("driver: Emit returned nil") diff --git a/toolchain/ttsc/go.mod b/toolchain/ttsc/go.mod index 4e984b3ed29..a72a57dc351 100644 --- a/toolchain/ttsc/go.mod +++ b/toolchain/ttsc/go.mod @@ -12,6 +12,7 @@ replace ( github.com/microsoft/typescript-go/shim/checker => ./shim/checker github.com/microsoft/typescript-go/shim/compiler => ./shim/compiler github.com/microsoft/typescript-go/shim/core => ./shim/core + github.com/microsoft/typescript-go/shim/diagnosticwriter => ./shim/diagnosticwriter github.com/microsoft/typescript-go/shim/parser => ./shim/parser github.com/microsoft/typescript-go/shim/scanner => ./shim/scanner github.com/microsoft/typescript-go/shim/tsoptions => ./shim/tsoptions @@ -27,6 +28,7 @@ require ( github.com/microsoft/typescript-go/shim/checker v0.0.0 github.com/microsoft/typescript-go/shim/compiler v0.0.0 github.com/microsoft/typescript-go/shim/core v0.0.0 + github.com/microsoft/typescript-go/shim/diagnosticwriter v0.0.0 github.com/microsoft/typescript-go/shim/scanner v0.0.0 github.com/microsoft/typescript-go/shim/tsoptions v0.0.0 github.com/microsoft/typescript-go/shim/tspath v0.0.0 diff --git a/toolchain/ttsc/package.json b/toolchain/ttsc/package.json index 3a8a8a261c7..4ab1ab286e3 100644 --- a/toolchain/ttsc/package.json +++ b/toolchain/ttsc/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "bin": { - "ttsc": "bin/ttsc.js" + "ttsc": "src/launcher/ttsc.js" }, "exports": { ".": { @@ -21,14 +21,14 @@ "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; go run ./cmd/ttsc build'", + "build": "rimraf lib && bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; go run ./cmd/ttsc'", "test": "pnpm run test:ts && pnpm run test:go", "test:ts": "node --test --test-reporter=spec --experimental-strip-types --experimental-specifier-resolution=node test/**/*.test.ts", "test:go": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; go test ./...'", - "go:build": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; go build -o bin/ttsc-native ./cmd/ttsc'", + "go:build": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; mkdir -p native && go build -o native/ttsc-native ./cmd/ttsc'", "go:test": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; go test ./...'", "go:vet": "bash -c 'export PATH=\"$HOME/go-sdk/go/bin:$PATH\"; go vet ./...'", - "prepack": "pnpm run build" + "prepack": "pnpm run build && pnpm run go:build" }, "repository": { "type": "git", @@ -50,8 +50,8 @@ "README.md", "THIRD-PARTY-LICENSES.md", "package.json", - "bin", "lib", + "native", "src" ], "keywords": [ diff --git a/toolchain/ttsc/shim/ast/shim.go b/toolchain/ttsc/shim/ast/shim.go index 0381f3e4fb3..15011b131ab 100644 --- a/toolchain/ttsc/shim/ast/shim.go +++ b/toolchain/ttsc/shim/ast/shim.go @@ -101,6 +101,7 @@ const ( KindExportSpecifier = innerast.KindExportSpecifier KindModuleBlock = innerast.KindModuleBlock KindModuleDeclaration = innerast.KindModuleDeclaration + KindFunctionDeclaration = innerast.KindFunctionDeclaration KindMinusToken = innerast.KindMinusToken SymbolFlagsOptional = innerast.SymbolFlagsOptional @@ -111,3 +112,6 @@ const ( //go:linkname GetSourceFileOfNode github.com/microsoft/typescript-go/internal/ast.GetSourceFileOfNode func GetSourceFileOfNode(node *innerast.Node) *innerast.SourceFile + +//go:linkname GetNodeAtPosition github.com/microsoft/typescript-go/internal/ast.GetNodeAtPosition +func GetNodeAtPosition(file *innerast.SourceFile, position int, includeJSDoc bool) *innerast.Node diff --git a/toolchain/ttsc/shim/diagnosticwriter/go.mod b/toolchain/ttsc/shim/diagnosticwriter/go.mod new file mode 100644 index 00000000000..2c5dc6187f5 --- /dev/null +++ b/toolchain/ttsc/shim/diagnosticwriter/go.mod @@ -0,0 +1,5 @@ +module github.com/microsoft/typescript-go/shim/diagnosticwriter + +go 1.26 + +require github.com/microsoft/typescript-go v0.0.0-20260408193441-2a5e1cf9fe22 diff --git a/toolchain/ttsc/shim/diagnosticwriter/shim.go b/toolchain/ttsc/shim/diagnosticwriter/shim.go new file mode 100644 index 00000000000..b3ee99c9df8 --- /dev/null +++ b/toolchain/ttsc/shim/diagnosticwriter/shim.go @@ -0,0 +1,29 @@ +package diagnosticwriter + +import ( + "io" + + "github.com/microsoft/typescript-go/internal/ast" + inner "github.com/microsoft/typescript-go/internal/diagnosticwriter" + "github.com/microsoft/typescript-go/internal/locale" + "github.com/microsoft/typescript-go/internal/tspath" +) + +// FormatASTDiagnosticsWithColorAndContext writes TypeScript-style pretty +// diagnostics using the same internal formatter as typescript-go. +func FormatASTDiagnosticsWithColorAndContext(output io.Writer, diagnostics []*ast.Diagnostic, currentDirectory string) { + if len(diagnostics) == 0 { + return + } + formatted := inner.FromASTDiagnostics(diagnostics) + options := &inner.FormattingOptions{ + Locale: locale.Default, + ComparePathsOptions: tspath.ComparePathsOptions{ + CurrentDirectory: currentDirectory, + UseCaseSensitiveFileNames: true, + }, + NewLine: "\n", + } + inner.FormatDiagnosticsWithColorAndContext(output, formatted, options) + inner.WriteErrorSummaryText(output, formatted, options) +} diff --git a/toolchain/ttsc/src/api.ts b/toolchain/ttsc/src/api.ts index a52b325f2cd..8bec5b2ea58 100644 --- a/toolchain/ttsc/src/api.ts +++ b/toolchain/ttsc/src/api.ts @@ -7,36 +7,36 @@ * adapters never have to spawn the process themselves. * * Contract: - * - `transform()` emits one file's rewritten JS, returning the text. - * - `build()` runs the whole project (tsgo + ttsc rewrite + --emit). - * - `check()` runs the analysis pass without emitting (CI gate use). - * - `version()` returns the binary's version banner for user-agent logs. + * + * - `transform()` emits one file's rewritten JS, returning the text. + * - `build()` runs the whole project (tsgo + ttsc rewrite + --emit). + * - `check()` runs the analysis pass without emitting (CI gate use). + * - `version()` returns the binary's version banner for user-agent logs. * * All helpers accept a `binary` override so tests can point at a specific * executable without touching PATH or node_modules. */ - import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { type ResolveOptions, installHint, resolveBinary } from "./platform"; import { + type TtscPlugin, applyPluginTransforms, loadProjectPlugins, - type TtscPlugin, } from "./plugin"; -import { resolveBinary, installHint, type ResolveOptions } from "./platform"; import { + type ProjectPluginConfig, resolveProjectConfig, resolveProjectRoot, - type ProjectPluginConfig, } from "./project"; /** * Options shared by every API call. `binary` takes precedence over platform - * resolution; `cwd` defaults to `process.cwd()`; `env` layers on top of - * the current process env. + * resolution; `cwd` defaults to `process.cwd()`; `env` layers on top of the + * current process env. */ export interface CommonOptions extends ResolveOptions { /** Absolute path to an already-resolved ttsc binary. Skips resolution. */ @@ -46,11 +46,16 @@ export interface CommonOptions extends ResolveOptions { /** Extra environment variables; merged onto `process.env`. */ env?: NodeJS.ProcessEnv; /** - * Override project plugin loading. `false` disables tsconfig plugins; - * an array replaces the tsconfig `compilerOptions.plugins` list. + * Override project plugin loading. `false` disables tsconfig plugins; an + * array replaces the tsconfig `compilerOptions.plugins` list. */ plugins?: readonly ProjectPluginConfig[] | false; - /** Override the native rewrite backend. Defaults to the loaded plugin mode. */ + /** + * Override the native rewrite backend. Defaults to the loaded plugin mode. + * + * @deprecated Prefer plugin-declared `native.mode`; this override is for + * low-level tests and migration probes. + */ rewriteMode?: string; } @@ -61,8 +66,8 @@ export interface TransformOptions extends CommonOptions { /** Path to the tsconfig owning `file`. Default: `tsconfig.json`. */ tsconfig?: string; /** - * When provided, the binary writes JS directly to this path instead of - * piping stdout. Useful when the emitted text is large. + * When provided, the binary writes JS directly to this path instead of piping + * stdout. Useful when the emitted text is large. */ out?: string; } @@ -71,7 +76,10 @@ export interface TransformOptions extends CommonOptions { export interface BuildOptions extends CommonOptions { /** Path to tsconfig.json. Default: `tsconfig.json`. */ tsconfig?: string; - /** Emit override. `true` forces emit, `false` forces noEmit, `undefined` follows tsconfig. */ + /** + * Emit override. `true` forces emit, `false` forces noEmit, `undefined` + * follows tsconfig. + */ emit?: boolean; /** Override compilerOptions.outDir for this invocation. */ outDir?: string; @@ -83,11 +91,15 @@ export interface BuildOptions extends CommonOptions { export type CheckOptions = Omit; /** - * Resolve the binary or throw a user-friendly error. Extracted so every - * API helper shares the same failure mode. + * Resolve the binary or throw a user-friendly error. Extracted so every API + * helper shares the same failure mode. */ function resolveOrThrow(opts: CommonOptions): string { - if (opts.binary && path.isAbsolute(opts.binary) && fs.existsSync(opts.binary)) { + if ( + opts.binary && + path.isAbsolute(opts.binary) && + fs.existsSync(opts.binary) + ) { return opts.binary; } const bin = resolveBinary(opts); @@ -116,13 +128,17 @@ function spawnBinary( if (!viaNode) { ensureExecutable(binary); } - return spawnSync(viaNode ? process.execPath : binary, viaNode ? [binary, ...args] : [...args], { - cwd: options.cwd, - env: options.env, - encoding: options.encoding, - maxBuffer: 1024 * 1024 * 256, - windowsHide: true, - }); + return spawnSync( + viaNode ? process.execPath : binary, + viaNode ? [binary, ...args] : [...args], + { + cwd: options.cwd, + env: options.env, + encoding: options.encoding, + maxBuffer: 1024 * 1024 * 256, + windowsHide: true, + }, + ); } function shouldSpawnViaNode(binary: string): boolean { @@ -144,21 +160,33 @@ function ensureExecutable(binary: string): void { } } +function outputText(value: string | Buffer | null | undefined): string { + if (value == null) { + return ""; + } + return typeof value === "string" ? value : value.toString("utf8"); +} + /** * Transform a single .ts file and return the rewritten JS as a string. * - * Intended for bundler per-file transforms (unplugin `transform()`). The - * caller passes the absolute path; ttsc loads the enclosing tsconfig, - * compiles with tsgo, and returns the rewritten JS. + * Intended for bundler per-file transforms (unplugin `transform()`). The caller + * passes the absolute path; ttsc loads the enclosing tsconfig, compiles with + * tsgo, and returns the rewritten JS. * - * Throws when the binary exits non-zero — the error includes stderr so - * bundler error overlays surface the real cause. + * Throws when the binary exits non-zero — the error includes stderr so bundler + * error overlays surface the real cause. */ export function transform(options: TransformOptions): string { const execution = resolveExecutionContext(options); + const sourceFile = realpathIfExists( + path.isAbsolute(options.file) + ? options.file + : path.resolve(options.cwd ?? process.cwd(), options.file), + ); const args = [ "transform", - "--file=" + options.file, + "--file=" + sourceFile, "--rewrite-mode=" + execution.nativeMode, ]; if (options.tsconfig) args.push("--tsconfig=" + options.tsconfig); @@ -170,15 +198,17 @@ export function transform(options: TransformOptions): string { }); if (res.error) { throw new Error( - "ttsc.transform: failed to spawn " + execution.bin + ": " + res.error.message, + "ttsc.transform: failed to spawn " + + execution.bin + + ": " + + res.error.message, ); } if (res.status !== 0) { - throw new Error("ttsc.transform exited " + res.status + "\n" + (res.stderr || "")); + throw new Error( + "ttsc.transform exited " + res.status + "\n" + (res.stderr || ""), + ); } - const sourceFile = path.isAbsolute(options.file) - ? options.file - : path.resolve(options.cwd ?? process.cwd(), options.file); const transformed = finalizeTransformText( execution.plugins, { @@ -188,7 +218,7 @@ export function transform(options: TransformOptions): string { sourceFile, tsconfig: execution.tsconfig, }, - res.stdout, + outputText(res.stdout), ); if (options.out) { fs.mkdirSync(path.dirname(options.out), { recursive: true }); @@ -197,6 +227,14 @@ export function transform(options: TransformOptions): string { return transformed; } +function realpathIfExists(file: string): string { + try { + return fs.realpathSync(file); + } catch { + return file; + } +} + /** Result of `build()`. Non-zero `status` means the build failed. */ export interface BuildResult { status: number; @@ -205,9 +243,9 @@ export interface BuildResult { } /** - * Run `ttsc build` against a tsconfig. Returns once the binary exits so the - * caller can decide how to surface diagnostics. Does not throw on non-zero - * exit — bundler pipelines often want to continue and collect errors. + * Run `ttsc` against a tsconfig. Returns once the binary exits so the caller + * can decide how to surface diagnostics. Does not throw on non-zero exit — + * bundler pipelines often want to continue and collect errors. */ export function build(options: BuildOptions = {}): BuildResult { const execution = resolveExecutionContext(options); @@ -228,17 +266,25 @@ export function build(options: BuildOptions = {}): BuildResult { encoding: "utf8", }); if (res.error) { - throw new Error("ttsc.build: failed to spawn " + execution.bin + ": " + res.error.message); + throw new Error( + "ttsc.build: failed to spawn " + execution.bin + ": " + res.error.message, + ); } if ((res.status ?? 1) === 0 && manifest) { try { - const emittedFiles = JSON.parse(fs.readFileSync(manifest, "utf8")) as string[]; + const emittedFiles = JSON.parse( + fs.readFileSync(manifest, "utf8"), + ) as string[]; applyBuildPlugins(execution.plugins, execution, emittedFiles); } finally { fs.rmSync(path.dirname(manifest), { recursive: true, force: true }); } } - return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" }; + return { + status: res.status ?? 1, + stdout: outputText(res.stdout), + stderr: outputText(res.stderr), + }; } /** @@ -256,15 +302,17 @@ export function version(options: CommonOptions = {}): string { encoding: "utf8", }); if (res.error || res.status !== 0) { - throw new Error("ttsc.version: failed: " + (res.stderr || res.error?.message)); + throw new Error( + "ttsc.version: failed: " + (outputText(res.stderr) || res.error?.message), + ); } - return res.stdout.trim(); + return outputText(res.stdout).trim(); } /** - * Promise-facing variant of `transform()`. The plugin host keeps the - * transform pipeline synchronous so plugin modules can stay dependency-free, - * but many adapter surfaces still prefer a Promise-returning function. + * Promise-facing variant of `transform()`. The plugin host keeps the transform + * pipeline synchronous so plugin modules can stay dependency-free, but many + * adapter surfaces still prefer a Promise-returning function. */ export function transformAsync(options: TransformOptions): Promise { return Promise.resolve().then(() => transform(options)); @@ -284,7 +332,9 @@ function resolveExecutionContext( ): ExecutionContext { const cwd = path.resolve(options.cwd ?? process.cwd()); const fallbackBinary = - options.binary && path.isAbsolute(options.binary) && fs.existsSync(options.binary) + options.binary && + path.isAbsolute(options.binary) && + fs.existsSync(options.binary) ? options.binary : resolveBinary(options); const tsconfig = resolveProjectConfig({ diff --git a/toolchain/ttsc/src/cli.ts b/toolchain/ttsc/src/cli.ts index 48446a20ee3..c9d8ad94939 100644 --- a/toolchain/ttsc/src/cli.ts +++ b/toolchain/ttsc/src/cli.ts @@ -1,18 +1,25 @@ import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as path from "node:path"; -import { build, check, transform, version, type BuildOptions, type TransformOptions } from "./api"; -import { resolveProjectConfig, resolveProjectRoot } from "./project"; + +import { + type BuildOptions, + type TransformOptions, + build, + check, + transform, + version, +} from "./api"; import { installHint, resolveBinary } from "./platform"; +import { resolveProjectConfig, resolveProjectRoot } from "./project"; export function main(argv: readonly string[] = process.argv.slice(2)): number { try { if (argv.length === 0) { - printHelp(); - return 0; + return runCompatibleBuild([], false); } - const [command, ...rest] = argv; + const [command, ...rest] = argv as [string, ...string[]]; switch (command) { case "-h": case "--help": @@ -39,8 +46,12 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number { if (isBuildAlias(command)) { return runCompatibleBuild(argv, false); } - process.stderr.write(`ttsc: unknown command ${JSON.stringify(command)}\n`); - process.stderr.write(`ttsc: run "ttsc --help" to see supported commands\n`); + process.stderr.write( + `ttsc: unknown command ${JSON.stringify(command)}\n`, + ); + process.stderr.write( + `ttsc: run "ttsc --help" to see supported commands\n`, + ); return 2; } } catch (error) { @@ -62,7 +73,10 @@ interface BuildInvocation extends BuildOptions { watch: boolean; } -function runCompatibleBuild(argv: readonly string[], checkOnly: boolean): number { +function runCompatibleBuild( + argv: readonly string[], + checkOnly: boolean, +): number { const options = parseBuildArgs(argv, checkOnly); if (options.watch) { return runWatch(options, checkOnly); @@ -92,11 +106,15 @@ function delegateToNative(argv: readonly string[]): number { return 1; } const viaNode = /\.(?:[cm]?js|ts)$/i.test(bin); - const result = spawnSync(viaNode ? process.execPath : bin, viaNode ? [bin, ...argv] : [...argv], { - stdio: "inherit", - env: process.env, - windowsHide: true, - }); + const result = spawnSync( + viaNode ? process.execPath : bin, + viaNode ? [bin, ...argv] : [...argv], + { + stdio: "inherit", + env: process.env, + windowsHide: true, + }, + ); if (result.error) { process.stderr.write(`${result.error.message}\n`); return 1; @@ -104,7 +122,10 @@ function delegateToNative(argv: readonly string[]): number { return result.status ?? 1; } -function parseBuildArgs(argv: readonly string[], checkOnly: boolean): BuildInvocation { +function parseBuildArgs( + argv: readonly string[], + checkOnly: boolean, +): BuildInvocation { let binary: string | undefined; let cwd: string | undefined; let emit: boolean | undefined = checkOnly ? false : undefined; @@ -164,13 +185,16 @@ function parseBuildArgs(argv: readonly string[], checkOnly: boolean): BuildInvoc } else if (current === "-w") { watch = true; } else if (current.startsWith("--rewrite-mode=")) { - rewriteMode = takeRewriteMode(current.slice("--rewrite-mode=".length)); + rewriteMode = takeRewriteMode( + current.slice("--rewrite-mode=".length), + ); } else if (current.startsWith("--tsconfig=")) { tsconfig = current.slice("--tsconfig=".length); } else if (current.startsWith("--project=")) { tsconfig = current.slice("--project=".length); } else if (current.startsWith("--preserveWatchOutput=")) { - preserveWatchOutput = current.slice("--preserveWatchOutput=".length) !== "false"; + preserveWatchOutput = + current.slice("--preserveWatchOutput=".length) !== "false"; } else if (current.startsWith("--binary=")) { binary = current.slice("--binary=".length); } else if (current === "--verbose") { @@ -238,7 +262,9 @@ function parseTransformArgs(argv: readonly string[]): TransformOptions { } else if (current.startsWith("--out=")) { out = current.slice("--out=".length); } else if (current.startsWith("--rewrite-mode=")) { - rewriteMode = takeRewriteMode(current.slice("--rewrite-mode=".length)); + rewriteMode = takeRewriteMode( + current.slice("--rewrite-mode=".length), + ); } else if (current.startsWith("--tsconfig=")) { tsconfig = current.slice("--tsconfig=".length); } else if (current.startsWith("--project=")) { @@ -269,13 +295,12 @@ function printHelp(): void { "", "Usage:", " ttsc", - " ttsc [file.ts]", - " ttsc --watch", " ttsc -p tsconfig.json", - " ttsc build [options]", - " ttsc check [options]", + " ttsc --watch", + " ttsc --noEmit", " ttsc transform --file [options]", " ttsc version", + " ttsc --help", "", "Options:", " -p, --project Resolve project settings from this tsconfig", @@ -294,8 +319,12 @@ function printHelp(): void { "", "Plugin contract:", " ttsc reads compilerOptions.plugins from tsconfig.json.", - " TS plugins may export `definePlugin(...)` from @typia/ttsc/plugin.", + " TS plugins may export `definePlugin(...)` from @typia/ttsc.", " Native consumer plugins may also provide their own binary path.", + "", + "Compatibility aliases:", + " ttsc build [options] Same project build lane as `ttsc [options]`.", + " ttsc check [options] Same as `ttsc --noEmit [options]`.", ].join("\n"), ); process.stdout.write("\n"); @@ -303,7 +332,9 @@ function printHelp(): void { function runSingleFile(options: BuildInvocation): number { if (options.files.length !== 1) { - throw new Error("ttsc: single-file mode currently accepts exactly one input file"); + throw new Error( + "ttsc: single-file mode currently accepts exactly one input file", + ); } const cwd = path.resolve(options.cwd ?? process.cwd()); const file = path.resolve(cwd, options.files[0]!); @@ -324,7 +355,11 @@ function runSingleFile(options: BuildInvocation): number { return 0; } -function resolveSingleFileOut(file: string, cwd: string, outDir?: string): string { +function resolveSingleFileOut( + file: string, + cwd: string, + outDir?: string, +): string { const relative = path.relative(cwd, file); const jsRelative = relative.replace(/\.[cm]?tsx?$/i, ".js"); if (outDir) { @@ -363,7 +398,9 @@ function runWatch(options: BuildInvocation, checkOnly: boolean): number { if (!options.preserveWatchOutput) { process.stdout.write("\x1bc"); } - process.stdout.write(`[ttsc] rebuilding at ${new Date().toLocaleTimeString()}\n`); + process.stdout.write( + `[ttsc] rebuilding at ${new Date().toLocaleTimeString()}\n`, + ); const status = invocation.files.length !== 0 ? runSingleFile(invocation) @@ -373,7 +410,9 @@ function runWatch(options: BuildInvocation, checkOnly: boolean): number { if (result.stderr) process.stderr.write(result.stderr); return result.status; })(); - process.stdout.write(`[ttsc] ${status === 0 ? "watch build complete" : "watch build failed"}\n`); + process.stdout.write( + `[ttsc] ${status === 0 ? "watch build complete" : "watch build failed"}\n`, + ); running = false; if (rerun) { rerun = false; diff --git a/toolchain/ttsc/src/index.ts b/toolchain/ttsc/src/index.ts index f0e2eb5e599..df86ab934a8 100644 --- a/toolchain/ttsc/src/index.ts +++ b/toolchain/ttsc/src/index.ts @@ -3,7 +3,7 @@ * * Exports: * - platform helpers (`resolveBinary`, `installHint`, …) — used by the - * `bin/ttsc.js` launcher and any external tool that wants to know where + * public launcher and any external tool that wants to know where * the native binary lives without spawning it. * - programmatic API (`transform`, `build`, `check`, `version`) — a thin * TS wrapper around the Go binary that bundler adapters (unplugin, vite, diff --git a/toolchain/ttsc/bin/ttsc-dev.js b/toolchain/ttsc/src/launcher/ttsc-dev.js similarity index 78% rename from toolchain/ttsc/bin/ttsc-dev.js rename to toolchain/ttsc/src/launcher/ttsc-dev.js index 49f57f2865a..acef14f5da1 100755 --- a/toolchain/ttsc/bin/ttsc-dev.js +++ b/toolchain/ttsc/src/launcher/ttsc-dev.js @@ -6,16 +6,12 @@ const path = require("node:path"); const { spawnSync } = require("node:child_process"); const root = fs.realpathSync.native?.(__dirname) ?? fs.realpathSync(__dirname); -const packageRoot = path.resolve(root, ".."); +const packageRoot = path.resolve(root, "..", ".."); const command = process.platform === "win32" ? "go.exe" : "go"; const invocationCwd = process.cwd(); const argv = [...process.argv.slice(2)]; -if ( - argv.length > 0 && - needsCwd(argv[0]) && - !argv.some((value) => value === "--cwd" || value.startsWith("--cwd=")) -) { +if (needsInvocationCwd(argv) && !argv.some((value) => value === "--cwd" || value.startsWith("--cwd="))) { argv.push(`--cwd=${invocationCwd}`); } @@ -33,7 +29,9 @@ if (result.error) { process.exitCode = result.status ?? 1; } -function needsCwd(commandName) { +function needsInvocationCwd(args) { + if (args.length === 0) return true; + const commandName = args[0]; return ( commandName === "build" || commandName === "check" || diff --git a/toolchain/ttsc/bin/ttsc.js b/toolchain/ttsc/src/launcher/ttsc.js similarity index 86% rename from toolchain/ttsc/bin/ttsc.js rename to toolchain/ttsc/src/launcher/ttsc.js index 42f9ba827cc..bc49c029b6b 100755 --- a/toolchain/ttsc/bin/ttsc.js +++ b/toolchain/ttsc/src/launcher/ttsc.js @@ -3,7 +3,8 @@ const fs = require("node:fs"); const path = require("node:path"); -const builtCli = path.resolve(__dirname, "..", "lib", "cli.js"); + +const builtCli = path.resolve(__dirname, "..", "..", "lib", "cli.js"); if (fs.existsSync(builtCli)) { const { main } = require(builtCli); const code = main(process.argv.slice(2)); diff --git a/toolchain/ttsc/src/native.ts b/toolchain/ttsc/src/native.ts new file mode 100644 index 00000000000..94f88343420 --- /dev/null +++ b/toolchain/ttsc/src/native.ts @@ -0,0 +1,60 @@ +export type NativeRewriteMode = string; +export type NativePluginContractVersion = 1; + +export interface TtscNativeBackend { + mode: NativeRewriteMode; + binary?: string; + contractVersion?: NativePluginContractVersion; + capabilities?: readonly string[]; +} + +export interface TtscNativePluginShape { + name: string; + native?: TtscNativeBackend; + nativeMode?: NativeRewriteMode; + nativeBinary?: string; +} + +export function resolveNativeBackend( + plugin: TtscNativePluginShape, +): TtscNativeBackend | null { + if (plugin.native && (plugin.nativeMode || plugin.nativeBinary)) { + throw new Error( + `ttsc: plugin "${plugin.name}" must use either native or nativeMode/nativeBinary, not both`, + ); + } + if (!plugin.native) { + if ( + plugin.nativeBinary && + (!plugin.nativeMode || plugin.nativeMode === "none") + ) { + throw new Error( + `ttsc: plugin "${plugin.name}" declared nativeBinary without a non-empty nativeMode`, + ); + } + if (!plugin.nativeMode || plugin.nativeMode === "none") { + return null; + } + return { + binary: plugin.nativeBinary, + contractVersion: 1, + mode: plugin.nativeMode, + }; + } + + const backend = plugin.native; + if (!backend.mode || backend.mode === "none") { + throw new Error( + `ttsc: plugin "${plugin.name}" declared native without a non-empty mode`, + ); + } + if (backend.contractVersion !== undefined && backend.contractVersion !== 1) { + throw new Error( + `ttsc: plugin "${plugin.name}" requested unsupported native contract version ${backend.contractVersion}`, + ); + } + return { + ...backend, + contractVersion: backend.contractVersion ?? 1, + }; +} diff --git a/toolchain/ttsc/src/platform.ts b/toolchain/ttsc/src/platform.ts index f4e46d7d823..74c1ceead20 100644 --- a/toolchain/ttsc/src/platform.ts +++ b/toolchain/ttsc/src/platform.ts @@ -1,7 +1,7 @@ /** * Platform detection and binary resolution for @typia/ttsc. * - * This is the tested implementation used by bin/ttsc.js (after build). + * This is the tested implementation used by the ttsc launcher after build. * Keep it pure and dependency-free so it can be unit-tested without spawning * any child process. * @@ -12,7 +12,7 @@ * node_modules. * 2. `@typia/ttsc-{platform}-{arch}/bin/ttsc{.exe}` optional dependency. * Standard distribution path. - * 3. A sibling `bin/ttsc-native` binary next to ttsc.js. Used by this + * 3. A package-local `native/ttsc-native` binary. Used by this * repository's local `pnpm run build:toolchain` output before the * platform packages exist on npm. * @@ -36,11 +36,11 @@ export interface ResolveOptions { /** Override `process.env` for testing. */ env?: NodeJS.ProcessEnv; /** - * Override the sibling-directory scan used for the local `bin/ttsc-native` + * Override the package-local scan used for the local `native/ttsc-native` * fallback. Returns an absolute path if present, else null. */ localBinaryLookup?: () => string | null; - /** Override the module directory used to resolve sibling `bin/` entries. */ + /** Override the module directory used to resolve the package-local binary. */ moduleDir?: string; } @@ -105,7 +105,7 @@ export function resolveBinary(opts: ResolveOptions = {}): string | null { /* fall through */ } - // 3. Local workspace fallback: /bin/ttsc-native. + // 3. Package-local fallback: /native/ttsc-native. if (opts.localBinaryLookup) { const local = opts.localBinaryLookup(); if (local) return local; @@ -132,7 +132,7 @@ export function installHint(opts: ResolveOptions = {}): string { `Resolution order:`, ` 1. TTSC_BINARY env var (absolute path)`, ` 2. ${pkg} optional dependency`, - ` 3. ./bin/ttsc-native (local workspace build)`, + ` 3. ./native/ttsc-native (local workspace build)`, ``, `Try:`, ` npm install --include=optional ${pkg}`, @@ -149,7 +149,7 @@ function defaultLocalBinaryPath(opts: ResolveOptions): string | null { const candidate = path.resolve( root, "..", - "bin", + "native", (opts.platform ?? process.platform) === "win32" ? "ttsc-native.exe" : "ttsc-native", ); return fs.existsSync(candidate) ? candidate : null; diff --git a/toolchain/ttsc/src/plugin.ts b/toolchain/ttsc/src/plugin.ts index ca280849b47..a18f9168cc0 100644 --- a/toolchain/ttsc/src/plugin.ts +++ b/toolchain/ttsc/src/plugin.ts @@ -1,13 +1,22 @@ -import * as path from "node:path"; -import * as fs from "node:fs"; +import fs from "node:fs"; +import path from "node:path"; import { - readProjectConfig, + type NativeRewriteMode, + type TtscNativeBackend, + resolveNativeBackend, +} from "./native"; +import { type ParsedProjectConfig, type ProjectPluginConfig, + readProjectConfig, } from "./project"; -export type NativeRewriteMode = string; +export type { + NativePluginContractVersion, + NativeRewriteMode, + TtscNativeBackend, +} from "./native"; export interface TtscPluginFactoryContext { binary: string; @@ -28,7 +37,10 @@ export interface TtscTransformContext { export interface TtscPlugin { name: string; + native?: TtscNativeBackend; + /** @deprecated Use `native.mode` instead. */ nativeMode?: NativeRewriteMode; + /** @deprecated Use `native.binary` instead. */ nativeBinary?: string; transformOutput?(context: TtscTransformContext): string; } @@ -42,6 +54,7 @@ export type TtscPluginModule = TtscPlugin | TtscPluginFactory; export interface LoadedPlugins { compatibilityFallback: boolean; + nativeBackend: TtscNativeBackend | null; nativeBinary: string | null; nativeMode: NativeRewriteMode; plugins: TtscPlugin[]; @@ -75,6 +88,7 @@ export function loadProjectPlugins(options: LoadPluginsOptions): LoadedPlugins { if (entries.length === 0) { return { compatibilityFallback: false, + nativeBackend: null, nativeBinary: null, nativeMode: "none", plugins: [], @@ -91,33 +105,32 @@ export function loadProjectPlugins(options: LoadPluginsOptions): LoadedPlugins { const plugins = entries.map((entry) => loadPluginEntry(entry, context)); let nativeBinary: string | null = null; + let nativeBackend: TtscNativeBackend | null = null; let nativeMode: NativeRewriteMode = "none"; for (const plugin of plugins) { - if (plugin.nativeBinary && (!plugin.nativeMode || plugin.nativeMode === "none")) { - throw new Error( - `ttsc: plugin "${plugin.name}" declared nativeBinary without a non-empty nativeMode`, - ); - } - if (!plugin.nativeMode || plugin.nativeMode === "none") { + const backend = resolveNativeBackend(plugin); + if (!backend) { continue; } - if (nativeMode !== "none" && nativeMode !== plugin.nativeMode) { + if (nativeMode !== "none" && nativeMode !== backend.mode) { throw new Error( - `ttsc: multiple native plugin modes requested (${nativeMode}, ${plugin.nativeMode})`, + `ttsc: multiple native plugin modes requested (${nativeMode}, ${backend.mode})`, ); } - nativeMode = plugin.nativeMode; - if (plugin.nativeBinary) { - if (nativeBinary !== null && nativeBinary !== plugin.nativeBinary) { + nativeMode = backend.mode; + nativeBackend = backend; + if (backend.binary) { + if (nativeBinary !== null && nativeBinary !== backend.binary) { throw new Error( - `ttsc: multiple native plugin binaries requested (${nativeBinary}, ${plugin.nativeBinary})`, + `ttsc: multiple native plugin binaries requested (${nativeBinary}, ${backend.binary})`, ); } - nativeBinary = plugin.nativeBinary; + nativeBinary = backend.binary; } } return { compatibilityFallback: false, + nativeBackend, nativeBinary, nativeMode, plugins, @@ -154,14 +167,23 @@ function loadPluginEntry( default?: TtscPluginModule; } & Partial>; const candidate = - mod.createTtscPlugin ?? mod.default ?? mod.plugin ?? (mod as unknown as TtscPluginModule); + mod.createTtscPlugin ?? + mod.default ?? + mod.plugin ?? + (mod as unknown as TtscPluginModule); if (typeof candidate === "function") { return candidate(entry, context); } - if (candidate && typeof candidate === "object" && typeof candidate.name === "string") { + if ( + candidate && + typeof candidate === "object" && + typeof candidate.name === "string" + ) { return candidate; } - throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`); + throw new Error( + `ttsc: plugin "${specifier}" does not export a valid ttsc plugin`, + ); } function resolvePluginRequest(specifier: string, projectRoot: string): string { @@ -198,7 +220,7 @@ function resolveSourceCheckoutPlugin( projectRoot: string, ): string | null { const normalized = specifier.replace(/\\/g, "/"); - const match = normalized.match(/^(.*)\/lib\/ttsc\/plugin$/); + const match = normalized.match(/^(.*)\/lib\/transform$/); if (!match) { return null; } @@ -206,8 +228,13 @@ function resolveSourceCheckoutPlugin( const packageJson = require.resolve(`${match[1]}/package.json`, { paths: [projectRoot], }); - const candidate = path.join(path.dirname(packageJson), "bin", "ttsc-plugin.cjs"); - return fs.existsSync(candidate) ? candidate : null; + const packageRoot = path.dirname(packageJson); + const candidates = [ + path.join(packageRoot, "lib", "transform.js"), + path.join(packageRoot, "src", "transform.ts"), + path.join(packageRoot, "bin", "ttsc-plugin.cjs"), + ]; + return candidates.find((candidate) => fs.existsSync(candidate)) ?? null; } catch { return null; } diff --git a/toolchain/ttsc/src/project.ts b/toolchain/ttsc/src/project.ts index 0cf0ab71ab0..34384c99ba1 100644 --- a/toolchain/ttsc/src/project.ts +++ b/toolchain/ttsc/src/project.ts @@ -41,7 +41,9 @@ export function resolveProjectConfig(opts: ProjectLocatorOptions = {}): string { return resolveRealPath(resolved); } - const start = opts.file ? resolveAbsolutePath(cwd, opts.file) : cwd; + const start = opts.file + ? resolveRealPath(resolveAbsolutePath(cwd, opts.file)) + : cwd; const from = isDirectory(start) ? start : path.dirname(start); const found = findUp(from, ["tsconfig.json", "jsconfig.json"]); if (!found) { diff --git a/toolchain/ttsc/test/native.test.ts b/toolchain/ttsc/test/native.test.ts new file mode 100644 index 00000000000..a286439644e --- /dev/null +++ b/toolchain/ttsc/test/native.test.ts @@ -0,0 +1,60 @@ +const assert = require("node:assert/strict"); +const test = require("node:test"); + +const { resolveNativeBackend } = require("../src/native.ts"); + +test("resolveNativeBackend accepts versioned native backend descriptors", () => { + const backend = resolveNativeBackend({ + name: "native-test", + native: { + mode: "native-test", + binary: "/tmp/ttsc-native-test", + contractVersion: 1, + capabilities: ["rewrite", "diagnostics"], + }, + }); + + assert.deepEqual(backend, { + mode: "native-test", + binary: "/tmp/ttsc-native-test", + contractVersion: 1, + capabilities: ["rewrite", "diagnostics"], + }); +}); + +test("resolveNativeBackend keeps nativeMode/nativeBinary as a legacy alias", () => { + const backend = resolveNativeBackend({ + name: "legacy-native-test", + nativeMode: "legacy-native-test", + nativeBinary: "/tmp/ttsc-legacy-native-test", + }); + + assert.deepEqual(backend, { + mode: "legacy-native-test", + binary: "/tmp/ttsc-legacy-native-test", + contractVersion: 1, + }); +}); + +test("resolveNativeBackend rejects ambiguous native declarations", () => { + assert.throws( + () => + resolveNativeBackend({ + name: "bad-native-test", + native: { mode: "native-test" }, + nativeMode: "native-test", + }), + /must use either native or nativeMode\/nativeBinary/, + ); +}); + +test("resolveNativeBackend rejects unsupported native contract versions", () => { + assert.throws( + () => + resolveNativeBackend({ + name: "future-native-test", + native: { mode: "native-test", contractVersion: 2 }, + }), + /unsupported native contract version 2/, + ); +}); diff --git a/toolchain/ttsc/test/project.test.ts b/toolchain/ttsc/test/project.test.ts index 28e0150852a..81c06ae5c39 100644 --- a/toolchain/ttsc/test/project.test.ts +++ b/toolchain/ttsc/test/project.test.ts @@ -4,7 +4,10 @@ const os = require("node:os"); const path = require("node:path"); const test = require("node:test"); -const { readProjectConfig, resolveProjectConfig } = require("../src/project.ts"); +const { + readProjectConfig, + resolveProjectConfig, +} = require("../src/project.ts"); test("resolveProjectConfig canonicalizes symlinked tsconfig paths", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-project-")); @@ -32,7 +35,7 @@ test("readProjectConfig inherits plugins and outDir through tsconfig extends", ( { compilerOptions: { outDir: "../dist/shared", - plugins: [{ transform: "typia/lib/ttsc/plugin" }], + plugins: [{ transform: "typia/lib/transform" }], }, }, null, @@ -57,7 +60,7 @@ test("readProjectConfig inherits plugins and outDir through tsconfig extends", ( tsconfig: path.join(project, "tsconfig.json"), }); assert.deepEqual(parsed.compilerOptions.plugins, [ - { transform: "typia/lib/ttsc/plugin" }, + { transform: "typia/lib/transform" }, ]); assert.equal(parsed.compilerOptions.outDir, path.join(root, "dist/shared")); }); @@ -73,7 +76,7 @@ test("readProjectConfig lets child tsconfig override inherited plugins", () => { JSON.stringify( { compilerOptions: { - plugins: [{ transform: "typia/lib/ttsc/plugin" }], + plugins: [{ transform: "typia/lib/transform" }], }, }, null, diff --git a/toolchain/ttsc/tsconfig.json b/toolchain/ttsc/tsconfig.json index e590a5b5406..43c8fa59bb4 100644 --- a/toolchain/ttsc/tsconfig.json +++ b/toolchain/ttsc/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../../config/tsconfig.json", "compilerOptions": { + "ignoreDeprecations": "6.0", + "moduleResolution": "bundler", "outDir": "lib", "rootDir": "src" }, diff --git a/toolchain/ttsx/README.md b/toolchain/ttsx/README.md index c56efa8c67b..517565868ff 100644 --- a/toolchain/ttsx/README.md +++ b/toolchain/ttsx/README.md @@ -1,15 +1,281 @@ # @typia/ttsx -`@typia/ttsx` is the `ts-node` / `tsx`-style runner for `ttsc` projects. +`@typia/ttsx` is a `ts-node` / `tsx`-style runner built on top of `@typia/ttsc`. -- `@typia/ttsc` owns build/check/transform and plugin loading -- `@typia/ttsx` owns `ttsx src/index.ts` -- both sit next to the official compiler package (`@typescript/native-preview` today, `typescript@7` later) +It exists so a project that already depends on the `ttsc` host can also run: -Current implementation status: +```bash +ttsx src/index.ts +``` -- CommonJS entrypoint runner: available -- ESM entrypoint runner: available -- plugin-hosted rewrite via `@typia/ttsc`: available -- argument pass-through and cache directory: available -- `register()` remains the in-process CommonJS hook; ESM execution runs the cached emitted entry in a child Node process +without introducing a second compiler path. + +## Installation + +```bash +npm install -D @typescript/native-preview @typia/ttsx +pnpm add -D @typescript/native-preview @typia/ttsx +``` + +`@typia/ttsx` depends on `@typia/ttsc`, and uses its project resolution, transform pipeline, and cache directory conventions. Install `@typia/ttsc` explicitly as well when the project also calls the `ttsc` CLI directly from its own scripts. + +## What `ttsx` owns + +- CLI runner: `ttsx src/index.ts` +- in-process CommonJS require hook +- cached ESM execution fallback +- project-aware cache directories + +`@typia/ttsc` still owns: + +- build / check / transform +- tsconfig plugin loading +- native rewrite backend selection + +## Quick Start + +### CommonJS-style execution + +```bash +ttsx src/index.ts +ttsx --project tsconfig.json src/index.ts +ttsx --cache-dir .cache/ttsx src/index.ts +ttsx src/index.ts -- --port 3000 +``` + +### Preload modules + +```bash +ttsx -r dotenv/config src/index.ts +ttsx -r ./register-env.js src/index.ts -- --mode local +``` + +### Programmatic registration + +```ts +import { register } from "@typia/ttsx"; + +const unregister = register({ + project: "tsconfig.json", +}); + +try { + require("./src/index.ts"); +} finally { + unregister(); +} +``` + +## CLI + +```bash +ttsx [options] [-- ] +``` + +Supported options: + +- `-P, --project `: use an explicit `tsconfig.json` +- `--cwd `: resolve project and entry relative to a different directory +- `--cache-dir `: override the compiled output cache +- `--binary `: force a particular `ttsc` native binary +- `-r, --require `: preload a module before the entrypoint +- `-h, --help` +- `-v, --version` + +`--` splits runner options from entrypoint argv. + +Example: + +```bash +ttsx --project tsconfig.app.json src/index.ts -- --port 8080 --watch +``` + +Inside the executed program: + +```ts +process.argv +// [node, /abs/path/src/index.ts, "--port", "8080", "--watch"] +``` + +## Runtime Model + +`ttsx` has two execution paths. + +### 1. CommonJS path + +For a CommonJS project: + +- `ttsx` installs a require hook +- each `.ts/.tsx/.cts/.mts` file is transformed on demand through `@typia/ttsc.transform()` +- the resulting JS is cached under `node_modules/.cache/ttsc/ttsx/...` +- Node executes the transformed text in-process + +This is the lightweight hot path. + +### 2. ESM path + +For an ESM project: + +- `ttsx` first probes the entry file with `transform()` +- if the output still looks like ESM, `ttsx` falls back to a project build +- it runs `@typia/ttsc.build({ emit: true, outDir: cacheDir })` +- it rewrites relative import specifiers to include `.js` +- it spawns a child Node process to execute the cached emitted entry + +This is heavier, but keeps the ESM lane working without pretending Node can run the transformed ESM in-process through the CJS require hook. + +## JS API + +```ts +import { prepareExecution, register } from "@typia/ttsx"; +``` + +### `register(options)` + +Install the in-process require hook and return an unregister function. + +```ts +import { register } from "@typia/ttsx"; + +const unregister = register({ + cwd: process.cwd(), + project: "tsconfig.json", +}); + +require("./src/index.ts"); +unregister(); +``` + +Supported options come from `RegisterOptions`, which extends `@typia/ttsc` common options: + +- `binary` +- `cwd` +- `env` +- `plugins` +- `rewriteMode` +- `cacheDir` +- `project` +- `extensions` + +Important note: + +- the CLI exposes only a subset of these options +- the JS API is currently broader than the CLI surface + +### `prepareExecution(entryFile, options)` + +Resolve whether the entry will run through the CJS path or the ESM fallback path. + +```ts +import { prepareExecution } from "@typia/ttsx"; + +const prepared = prepareExecution("src/index.ts", { + project: "tsconfig.json", +}); + +console.log(prepared.moduleKind); +console.log(prepared.entryFile); +console.log(prepared.emitDir); +``` + +This is useful if you want to embed `ttsx` in another runner or wrapper. + +## Cache Layout + +By default the runner uses: + +```text +/node_modules/.cache/ttsc/ttsx +``` + +Inside that cache: + +- `single/` stores per-file transform results for the CJS path +- `project//` stores emitted project output for the ESM path + +The cache salt includes signatures from: + +- the selected native binary +- relevant workspace compiler source trees + +so local compiler changes invalidate cached output during development. + +## How `ttsx` sees plugins + +`ttsx` does not load compiler plugins on its own. + +Instead it delegates to `@typia/ttsc`, which means: + +- tsconfig plugin resolution works the same way as `ttsc` +- consumer packages reuse the same `transform` entry +- the same native backend is selected for both build-time and runner-time execution + +That shared host surface is the main reason `ttsx` exists as a sibling package instead of reimplementing a second compiler path. + +## Current Constraints + +These are real current constraints. + +### 1. CJS and ESM paths are different + +The CommonJS lane is per-file and in-process. The ESM lane is project-wide and child-process based. + +That split is intentional for now, but it means: + +- startup cost differs by module kind +- CJS debugging and ESM debugging do not go through the exact same runtime path +- ESM execution depends on a cached emitted project tree + +### 2. The CLI does not expose every `RegisterOptions` field + +The JS API supports: + +- `plugins` +- `rewriteMode` +- `extensions` +- `env` + +The CLI does not currently expose flags for those. + +If you need them today, use the JS API. + +### 3. In-process execution is CJS only + +The require hook throws if a transformed file still looks like ESM. + +That is why the ESM lane falls back to `build()` + child Node execution. + +## When to use `ttsx` + +Use `ttsx` when: + +- you already use `@typia/ttsc` +- you want one consistent compiler / plugin host for build and run +- you need `ts-node` / `tsx`-style execution for scripts, tests, or local tools + +Do not use `ttsx` when: + +- the project does not need the `ttsc` plugin host at all +- another runtime is intentionally the source of truth + +## Development + +```bash +pnpm build +``` + +The package is intentionally small. Most of the heavy lifting lives in `@typia/ttsc`. + +## Layout + +``` +toolchain/ttsx/ +├── src/launcher/ttsx.js +├── src/cli.ts +├── src/register.ts +└── src/index.ts +``` + +## License + +MIT. See [../../LICENSE](../../LICENSE). diff --git a/toolchain/ttsx/package.json b/toolchain/ttsx/package.json index 4c30d0d915a..32a9966156c 100644 --- a/toolchain/ttsx/package.json +++ b/toolchain/ttsx/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "bin": { - "ttsx": "bin/ttsx.js" + "ttsx": "src/launcher/ttsx.js" }, "exports": { ".": { @@ -16,7 +16,7 @@ "./package.json": "./package.json" }, "scripts": { - "build": "rimraf lib && ttsc build", + "build": "rimraf lib && ttsc", "prepack": "pnpm run build" }, "repository": { @@ -41,7 +41,6 @@ "files": [ "README.md", "package.json", - "bin", "lib", "src" ], diff --git a/toolchain/ttsx/src/cli.ts b/toolchain/ttsx/src/cli.ts index 9fd77429675..68cc59d986d 100644 --- a/toolchain/ttsx/src/cli.ts +++ b/toolchain/ttsx/src/cli.ts @@ -19,6 +19,15 @@ interface ParsedCLI extends RegisterOptions { } export function main(argv: readonly string[] = process.argv.slice(2)): number { + try { + return run(argv); + } catch (error) { + process.stderr.write(`${formatError(error)}\n`); + return 2; + } +} + +function run(argv: readonly string[]): number { const parsed = parseCLI(argv); if (parsed === "help") { printHelp(); @@ -65,6 +74,13 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number { return 0; } +function formatError(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + return String(error); +} + function parseCLI(argv: readonly string[]): ParsedCLI | "help" | "version" { const preload: string[] = []; const passthroughIndex = argv.indexOf("--"); diff --git a/toolchain/ttsx/bin/ttsx.js b/toolchain/ttsx/src/launcher/ttsx.js similarity index 86% rename from toolchain/ttsx/bin/ttsx.js rename to toolchain/ttsx/src/launcher/ttsx.js index 15b8f193585..99a098068e1 100755 --- a/toolchain/ttsx/bin/ttsx.js +++ b/toolchain/ttsx/src/launcher/ttsx.js @@ -3,7 +3,8 @@ const fs = require("node:fs"); const path = require("node:path"); -const builtCli = path.resolve(__dirname, "..", "lib", "cli.js"); + +const builtCli = path.resolve(__dirname, "..", "..", "lib", "cli.js"); if (fs.existsSync(builtCli)) { const { main } = require(builtCli); const code = main(process.argv.slice(2)); diff --git a/toolchain/ttsx/src/register.ts b/toolchain/ttsx/src/register.ts index db857c88975..5318d375618 100644 --- a/toolchain/ttsx/src/register.ts +++ b/toolchain/ttsx/src/register.ts @@ -4,6 +4,7 @@ import * as path from "node:path"; import { build, + check, defaultCacheDirectory, resolveProjectConfig, resolveProjectRoot, @@ -35,6 +36,7 @@ interface ProjectContext { compiledText: Map; emitDir: string; emittedFiles: string[] | null; + diagnosticsChecked: boolean; entryMap: Map; root: string; tsconfig: string; @@ -70,6 +72,7 @@ export function register(options: RegisterOptions = {}): () => void { cacheDir, cacheSalt: createCompilerCacheSalt(options), compiledText: new Map(), + diagnosticsChecked: false, emitDir: path.join(cacheDir, "project", PROCESS_CACHE_KEY), emittedFiles: null, entryMap: new Map(), @@ -89,6 +92,7 @@ export function register(options: RegisterOptions = {}): () => void { cacheDir, cacheSalt: createCompilerCacheSalt(options), compiledText: new Map(), + diagnosticsChecked: false, emitDir: path.join(cacheDir, "project", PROCESS_CACHE_KEY), emittedFiles: null, entryMap: new Map(), @@ -99,6 +103,7 @@ export function register(options: RegisterOptions = {}): () => void { const compile = (filename: string): string => { const context = getContext(filename); + ensureProjectDiagnostics(context, options); const output = readCompiledOutput(context, filename, options); if (looksLikeESM(output)) { throw new Error( @@ -134,6 +139,7 @@ export function prepareExecution( ): PreparedExecution { const cwd = path.resolve(options.cwd ?? process.cwd()); const context = resolveProjectContext(cwd, entryFile, options); + ensureProjectDiagnostics(context, options); const entryOutput = transformSingleFile(context, entryFile, options); if (!looksLikeESM(entryOutput)) { return { @@ -144,6 +150,9 @@ export function prepareExecution( } ensureProjectBuild(context, options); const resolvedEntry = resolveEmittedFile(context, entryFile); + if (resolvedEntry === null) { + throw new Error(`ttsx: emitted entry not found for ${entryFile}`); + } const output = fs.readFileSync(resolvedEntry, "utf8"); return { emitDir: context.emitDir, @@ -218,12 +227,41 @@ function createProjectContext( cacheDir, cacheSalt: createCompilerCacheSalt(options), compiledText: new Map(), + diagnosticsChecked: false, emitDir: path.join(cacheDir, "project", PROCESS_CACHE_KEY), emittedFiles: null, entryMap: new Map(), }; } +function ensureProjectDiagnostics( + context: ProjectContext, + options: RegisterOptions, +): void { + if (context.diagnosticsChecked) { + return; + } + const result = check({ + binary: options.binary, + cwd: context.root, + env: options.env, + plugins: options.plugins, + quiet: true, + rewriteMode: options.rewriteMode, + tsconfig: context.tsconfig, + }); + if (result.status !== 0) { + const detail = [result.stderr.trim(), result.stdout.trim()] + .filter(Boolean) + .join("\n"); + throw new Error( + `ttsx: project check failed for ${context.tsconfig}` + + (detail ? `\n${detail}` : ""), + ); + } + context.diagnosticsChecked = true; +} + function transformSingleFile( context: ProjectContext, filename: string, diff --git a/toolchain/ttsx/tsconfig.json b/toolchain/ttsx/tsconfig.json index 74cb778ecee..43c8fa59bb4 100644 --- a/toolchain/ttsx/tsconfig.json +++ b/toolchain/ttsx/tsconfig.json @@ -1,13 +1,9 @@ { "extends": "../../config/tsconfig.json", "compilerOptions": { - "baseUrl": ".", + "ignoreDeprecations": "6.0", + "moduleResolution": "bundler", "outDir": "lib", - "paths": { - "@typia/ttsc": [ - "../ttsc/lib/index.d.ts" - ] - }, "rootDir": "src" }, "include": ["src"] diff --git a/website/package.json b/website/package.json index eea855ed109..25795fd3499 100644 --- a/website/package.json +++ b/website/package.json @@ -5,16 +5,17 @@ "description": "Typia Guide Documents", "scripts": { "build": "npm run prebuild && next build && npm run postbuild", - "prebuild": "rimraf .next && rimraf out && npm run build:compiler && npm run build:blog:rss", + "prebuild": "rimraf .next && rimraf out && npm run build:compiler && npm run build:blog:rss && npm run build:examples", "postbuild": "npm run build:pagefind && npm run build:sitemap", "build:blog:rss": "node build/blog", "build:compiler": "node build/compiler", + "build:examples": "cd ../examples && pnpm run build", "build:pagefind": "pagefind --site .next/server/app --output-path out/_pagefind", "build:sitemap": "node build/sitemap", "build:tgz": "node build/tgz", "build:typedoc": "node build/typedoc", "deploy": "node build/deploy", - "dev": "npm run build:compiler && next dev" + "dev": "npm run build:compiler && npm run build:examples && next dev" }, "repository": { "type": "git", diff --git a/website/src/components/internal/getLocalSourceFile.ts b/website/src/components/internal/getLocalSourceFile.ts index 9509ec231b3..e41f98e2263 100644 --- a/website/src/components/internal/getLocalSourceFile.ts +++ b/website/src/components/internal/getLocalSourceFile.ts @@ -1,4 +1,3 @@ -import cp from "child_process"; import fs from "fs"; import path from "path"; import { Singleton, VariadicSingleton } from "tstl"; @@ -8,7 +7,6 @@ export function getLocalSourceFile(location: string): Promise { } const loader = new VariadicSingleton(async (location: string) => { - await examples.get(); const absolute: string = `${await root.get()}/${location}`; if (fs.existsSync(absolute) === false) { @@ -34,10 +32,3 @@ const root = new Singleton(async () => { } return path.resolve(cwd); }); - -const examples = new Singleton(async () => { - cp.execSync("pnpm run build", { - stdio: "inherit", - cwd: path.resolve(`${await root.get()}/examples`), - }); -}); diff --git a/website/src/content/docs/setup.mdx b/website/src/content/docs/setup.mdx index 95310b2823e..5f02ef6f7f3 100644 --- a/website/src/content/docs/setup.mdx +++ b/website/src/content/docs/setup.mdx @@ -148,7 +148,7 @@ Firstly install `typia` as a dependency. And then, install `@typescript/native-p "strictNullChecks": true, "skipLibCheck": true, "plugins": [ - { "transform": "typia/lib/ttsc/plugin" } + { "transform": "typia/lib/transform" } ] } } @@ -487,7 +487,7 @@ After installing `typia` like above, and ensuring `@typescript/native-preview` a "outDir": "../../dist/out-tsc", "declaration": true, "types": [], - "plugins": [{ "transform": "typia/lib/ttsc/plugin" }] + "plugins": [{ "transform": "typia/lib/transform" }] }, "include": ["**/*.ts"], "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] @@ -518,7 +518,7 @@ To debug whether this is an issue with your setup or simply NX just silently swa Running this task will show you the errors from Typia, and allow you to correct them, meaning that using the standard `nx :build` task should now work the way you expect. -Note: While Nx has a `transformers` feature on the `@nx/js` plugin, that won't work with Typia. Nx expects a transformer to export a `before` hook, which it then plugs directly into the compiler API. Typia's default path is the `ttsc` host plus `typia/lib/ttsc/plugin`, so the Nx transformer slot is not the primary integration point. +Note: While Nx has a `transformers` feature on the `@nx/js` plugin, that won't work with Typia. Nx expects a transformer to export a `before` hook, which it then plugs directly into the compiler API. Typia's default path is the `ttsc` host plus `typia/lib/transform`, so the Nx transformer slot is not the primary integration point. ## Generation diff --git a/website/src/content/docs/validators/assert.mdx b/website/src/content/docs/validators/assert.mdx index 758aab6ba99..b64e77b20f5 100644 --- a/website/src/content/docs/validators/assert.mdx +++ b/website/src/content/docs/validators/assert.mdx @@ -230,7 +230,7 @@ It validates only the primitive properties. Therefore, `typia.assert()` funct "compilerOptions": { "plugins": [ { - "transform": "typia/lib/ttsc/plugin", + "transform": "typia/lib/transform", "functional": true } ] diff --git a/website/src/content/docs/validators/is.mdx b/website/src/content/docs/validators/is.mdx index 3c47a27cb41..9c71b14ac21 100644 --- a/website/src/content/docs/validators/is.mdx +++ b/website/src/content/docs/validators/is.mdx @@ -135,7 +135,7 @@ It validates only the primitive properties. Therefore, `typia.is()` function "compilerOptions": { "plugins": [ { - "transform": "typia/lib/ttsc/plugin", + "transform": "typia/lib/transform", "functional": true } ] diff --git a/website/src/content/docs/validators/validate.mdx b/website/src/content/docs/validators/validate.mdx index 089eb643c99..9c76a7c869d 100644 --- a/website/src/content/docs/validators/validate.mdx +++ b/website/src/content/docs/validators/validate.mdx @@ -202,7 +202,7 @@ It validates only the primitive properties. Therefore, `typia.validate()` fun "compilerOptions": { "plugins": [ { - "transform": "typia/lib/ttsc/plugin", + "transform": "typia/lib/transform", "functional": true } ] diff --git a/wiki/00-INDEX.md b/wiki/00-INDEX.md index 0926235a728..86f0a6cee50 100644 --- a/wiki/00-INDEX.md +++ b/wiki/00-INDEX.md @@ -1,35 +1,36 @@ # typia wiki +## 현재 기준 + +- 기본 컴파일 경로는 `@typescript/native-preview` + `@typia/ttsc` + `typia/lib/transform` 이다. +- `typia setup` 은 `@typescript/native-preview`, `@typia/ttsc`, `@typia/ttsx` 를 설치하고, legacy `ts-patch` 설정을 제거한다. +- `ttsc` 는 standalone compiler adapter / plugin host 다. +- `ttsx` 는 `ttsc` host 를 재사용하는 standalone runner 다. +- typia는 `ttsc` / `ttsx` 의 첫 consumer 다. +- typia 변환은 Go native backend 가 수행한다. +- `@typia/core` / `@typia/transform` TypeScript transformer 패키지는 현재 코드베이스에 없다. +- `typia/lib/transform` 은 legacy transformer 가 아니라 native plugin entry 다. + ## 먼저 볼 문서 1. [08-tsgo-master-plan/00-README.md](08-tsgo-master-plan/00-README.md) -2. [08-tsgo-master-plan/02-products.md](08-tsgo-master-plan/02-products.md) -3. [08-tsgo-master-plan/03-plugin-contract.md](08-tsgo-master-plan/03-plugin-contract.md) -4. [08-tsgo-master-plan/04-typia-consumer.md](08-tsgo-master-plan/04-typia-consumer.md) -5. [08-tsgo-master-plan/08-current-spike.md](08-tsgo-master-plan/08-current-spike.md) +2. [02-architecture/00-README.md](02-architecture/00-README.md) +3. [03-packages/00-README.md](03-packages/00-README.md) +4. [06-feedback/05-ttsc-ttsx-follow-ups.md](06-feedback/05-ttsc-ttsx-follow-ups.md) +5. [08-tsgo-master-plan/09-references.md](08-tsgo-master-plan/09-references.md) 6. [10-ecosystem/00-README.md](10-ecosystem/00-README.md) -## 현재 기준 - -- `ttsc` 는 standalone compiler adapter / plugin host 다. -- `ttsx` 는 standalone runner 다. -- typia는 `ttsc` / `ttsx` 위에 올라가는 첫 consumer 다. -- preview 기본 설치 계약은 `@typescript/native-preview` + `@typia/ttsc` 다. -- stable 기본 설치 계약은 `typescript@7` + `@typia/ttsc` 다. -- `@typia/ttsx` 는 optional runner 다. -- `@typia/core`, `@typia/transform` 는 Go 이전 대상이다. - ## 폴더 | 폴더 | 역할 | -|---|---| +| --- | --- | | `01-philosophy/` | typia의 핵심 명제 | | `02-architecture/` | 현재 구조 | | `03-packages/` | 패키지별 책임 | | `04-features/` | 기능별 정리 | | `05-research/` | 외부 사실 자료 | -| `06-feedback/` | 개선 과제 | -| `08-tsgo-master-plan/` | 현재 전환 계획 | -| `09-audit/` | 측정과 감수 | -| `10-ecosystem/` | nestia · agentica · autobe 연결 | -| `07-strategy/` | 보존용 참고 문서 | +| `06-feedback/` | 현재 제약과 후속 과제 | +| `08-tsgo-master-plan/` | 현재 `ttsc` / `ttsx` 계약 | +| `09-audit/` | 측정과 감수 기록 | +| `10-ecosystem/` | downstream 영향 | +| `07-strategy/` | 보존용 과거 전략 문서 | diff --git a/wiki/01-philosophy/00-README.md b/wiki/01-philosophy/00-README.md index e1a122a77ba..10ce0a8161d 100644 --- a/wiki/01-philosophy/00-README.md +++ b/wiki/01-philosophy/00-README.md @@ -1,15 +1,21 @@ -# 01. 철학 (Philosophy) +# 01. Philosophy -typia의 핵심 명제를 읽는 섹션이다. 기준 문장은 `website/src/content/docs/pure.mdx` 다. +typia의 핵심 명제만 적는다. -## 읽기 순서 +## 핵심 문장 -1. [01-introduction.md](01-introduction.md) — 한 줄 정의 -2. [02-pure-typescript.md](02-pure-typescript.md) — "Pure TypeScript"의 4차원 해석 (AI 재구성) -3. [03-design-principles.md](03-design-principles.md) — 8 코드 패턴 (AI 귀납, 공식 원칙 아님) -4. [04-positioning.md](04-positioning.md) — 좌표축 위 typia 위치 -5. [10-ecosystem/04-philosophy-pyramid.md](../10-ecosystem/04-philosophy-pyramid.md) — 생태계 확장 +> TypeScript 타입을 단일 진실원으로 삼고, 필요한 산출물을 거기서 만든다. -## 핵심 문장 +## 현재 구현과의 연결 + +- 사용자는 `typia.is()`, `typia.json.stringify()`, `typia.llm.application()` 같은 public API 를 호출한다. +- `typia/lib/transform` 은 native plugin entry 다. +- `packages/core/native` 가 타입을 분석해 metadata 를 만든다. +- 같은 metadata 에서 validator, JSON, LLM, protobuf, random 계열 산출물이 나온다. + +## 읽기 순서 -> TypeScript 타입을 단일 진실원으로 삼고, 필요한 산출물을 거기서 직접 만든다. +1. [01-introduction.md](01-introduction.md) +2. [02-pure-typescript.md](02-pure-typescript.md) +3. [03-design-principles.md](03-design-principles.md) +4. [04-positioning.md](04-positioning.md) diff --git a/wiki/01-philosophy/01-introduction.md b/wiki/01-philosophy/01-introduction.md index 3e1a4911cba..10a265e39c5 100644 --- a/wiki/01-philosophy/01-introduction.md +++ b/wiki/01-philosophy/01-introduction.md @@ -1,68 +1,50 @@ -# 01. typia란 무엇인가 — 한 줄 정의에서 시작 +# 01. Introduction -## 한 줄 정의 +typia는 TypeScript 타입을 단일 진실원으로 삼는다. -> **typia는 "TypeScript 타입 그 자체를 런타임 코드의 단일 진실원(Single Source of Truth)으로 삼는다"는 단 하나의 명제로 모든 결정을 정렬한 라이브러리다.** +## 무엇이 나오나 -마케팅 카피가 아니다. typia의 모든 기술적 선택 — 트랜스포머, 메타데이터 IR, JSDoc/Brand 태그, JSON·Protobuf·LLM 스키마 일원화, `IRandomGenerator` 형태까지 — 이 명제 하나에서 연역된다. 사상이 코드를 결정하고, 코드가 사상을 증명하는 보기 드문 라이브러리다. +한 타입에서 다음 산출물이 나온다. -## 다른 라이브러리와 무엇이 다른가 +- runtime validator +- assertion +- JSON stringify / parse helper +- JSON Schema / OpenAPI / LLM schema +- Protocol Buffer codec +- random data generator -런타임 검증 라이브러리는 본질적으로 **두 가지 표현 사이의 다리**다. +## 현재 구현 -| 표현 A (개발 시) | 표현 B (런타임) | 다리 | -|---|---|---| -| TypeScript 타입 | JavaScript 값 | 검증/직렬화 | +현재 기본 경로: -이 다리를 어떻게 짓느냐로 라이브러리의 정체성이 결정된다. +```bash +npx typia setup +ttsc +``` -| 접근 | 대표 | 다리 짓는 방식 | -|---|---|---| -| **스키마 빌더** | Zod, Valibot, ArkType, TypeBox | 별도 스키마 객체 작성 → `Static` 같은 도구로 TS 타입 추출 | -| **데코레이터** | class-validator, io-ts | 클래스/인스턴스에 메타데이터 주입 → 런타임에 reflection | -| **반사(reflection)** | Deepkit | TS 타입을 bytecode로 보존 → 런타임에 typeOf()로 조회 | -| **AST 변환 (typia)** | typia, ts-runtime-checks | TS 타입 자체를 컴파일러가 읽고, 컴파일 타임에 전용 함수를 emit | +setup 은 다음을 수행한다. -처음 셋은 모두 **타입 표현이 둘 이상 존재**한다. 사용자(또는 도구)가 둘을 동기화해야 한다. typia는 이 동기화 비용 자체를 0으로 만든다 — **타입 한 번 쓰면 끝**. +- `@typescript/native-preview` 설치 +- `@typia/ttsc` 설치 +- `@typia/ttsx` 설치 +- `tsconfig.json` 에 `typia/lib/transform` 추가 +- legacy `ts-patch` 설정 제거 -## "Pure TypeScript" 라는 슬로건의 진짜 무게 +변환은 Go native backend 가 수행한다. -`docs/pure.mdx:12-24`의 본문: +``` +TypeScript type + -> packages/core/native metadata + -> JavaScript expression + -> emitted JS rewrite +``` -> typia needs only one line with pure TypeScript type. You know what? Every other validator libraries need extra schema definition, that is different with pure TypeScript type. -> -> Those duplicated schema definitions are not only annoying, but also error-prone. If you take any mistake on the extra schema definition, such mistake can't be detected by TypeScript compiler. +## 비용 -"Pure"는 "추가 코드가 없다"가 아니라 "**TS 컴파일러가 검증할 수 있는 모든 것을 검증한다**"이다. 별도 스키마 객체는 컴파일러 시야 밖이라 동기화가 깨져도 컴파일은 통과한다. typia는 사용자 타입을 그대로 받기 때문에, **타입 자체가 옳으면 검증 코드가 틀릴 수 없다**. +typia는 런타임-only 라이브러리가 아니다. compiler host 가 필요하다. -이는 DX 개선이 아니라 **정확성의 정의를 바꾸는 주장**이다. 다른 라이브러리에선 "스키마≡타입"이 사용자 책임이지만, typia에선 **그 불일치 자체가 존재할 수 없다**. +- 일반 빌드: `ttsc` +- 번들러: `@typia/unplugin` +- TS runner: `ttsx` -## 한 명제의 5가지 따름정리 - -이 명제 하나에서 typia의 모든 특징이 자연스럽게 따라 나온다: - -1. **속도** — 컴파일 타임에 타입을 알면 검증 함수는 해당 타입에 맞춘 specialized code. Zod·AJV 같은 인터프리터와 달리 분기 트리가 inline되어 V8 hidden class까지 최적화 → **20,000× class-validator, 200× class-transformer**. - -2. **0 외부 런타임** — 검증기는 사용자 코드 안에 emit. 빌드 후 산출물에 typia 의존이 거의 없다(TypeGuardError·RandomGenerator 등 최소 헬퍼만). Edge runtime/RSC에서 사이즈 우위. - -3. **JSON/Protobuf/LLM 스키마 일원화** — 모두 같은 메타데이터 IR에서 도출. 한 타입에서 OpenAPI v3.0/3.1, Protobuf .proto, OpenAI·Anthropic·Gemini 함수 호출 스키마가 동시에 나온다. - -4. **에러 경로 정확성** — 실패 시 `$input.user.address[0].zipCode` 처럼 **TS 식별자 그대로**. 런타임 reflection 기반 라이브러리가 흉내 내기 어려운 영역. - -5. **AI 에이전트 친화** — interface 한 번이면 `typia.llm.application()`이 함수 호출 스키마를 즉시 생성. 별도 스키마 객체 불필요. AutoBE/Agentica가 이 위에서 동작. - -## 이 명제의 비용 - -공짜가 아니다. **TS 컴파일러를 빌드 파이프라인에 끼워야** 한다 — 현재 기본 경로는 `npx typia setup` → `@typescript/native-preview` + `@typia/ttsc` + `typia/lib/ttsc/plugin`, 번들러 중심 대체 경로는 `@typia/unplugin` 이다. 이는: - -- legacy `ts-patch`, current `ttsc`, bundler-native `unplugin` 이 공존하는 전환기라 setup 마찰이 끊임없는 약점 -- TypeScript Compiler API (특히 향후 tsgo)에 강하게 종속 -- `ts-node` / `tsx` 류 실행은 별도 runner `@typia/ttsx` 같은 보조 경로가 필요 - -이 비용을 어떻게 다룰지가 [08-tsgo-master-plan/](../08-tsgo-master-plan/)의 핵심 주제. - -## 다음 읽기 순서 - -- [02. Pure TypeScript 사상](02-pure-typescript.md) — 위 슬로건의 기술적 의미를 더 깊이 -- [03. 설계 원칙](03-design-principles.md) — 사상에서 따라 나온 코드 차원의 원칙들 -- [04. 포지셔닝과 가치관](04-positioning.md) — 다른 라이브러리들 사이에서 typia의 좌표 +이 비용을 받는 대신 타입/스키마 중복을 없앤다. diff --git a/wiki/01-philosophy/02-pure-typescript.md b/wiki/01-philosophy/02-pure-typescript.md index 75ca2e0139c..fccb421dd6e 100644 --- a/wiki/01-philosophy/02-pure-typescript.md +++ b/wiki/01-philosophy/02-pure-typescript.md @@ -1,118 +1,42 @@ -# 02. Pure TypeScript — 사상의 기술적 의미 +# 02. Pure TypeScript -> ⚠️ **이 문서의 성격**: typia 공식 문서(`website/src/content/docs/pure.mdx`)의 원문은 "Only one line required, with pure TypeScript type" 한 문장을 강조한다. **아래 "4가지 차원" 분해는 이 wiki 저자(AI)가 코드·문서를 관찰해 재구성한 해석**이지 samchon의 공식 분류는 아니다. 원문 인용은 § "Pure TypeScript 4가지 차원" 끝에 있는 pure.mdx 인용문만 공식이다. - -## "Pure TypeScript"의 4가지 차원 (이 wiki의 해석) - -이 슬로건은 한 단어처럼 들리지만, 이 wiki는 **4개의 독립적 주장이 겹쳐 있다**고 해석한다. 각각이 별개의 결정을 강제한다. - -### 1. 표현의 단일성 — "한 번만 쓴다" +여기서 "Pure TypeScript" 는 별도 schema object 를 작성하지 않는다는 뜻이다. ```ts interface Member { id: string & tags.Format<"uuid">; email: string & tags.Format<"email">; - age: number & tags.Type<"uint32"> & tags.Maximum<150>; } -// 이게 끝. 검증, 직렬화, 스키마, 랜덤 생성이 다 여기서 나온다. typia.is(input); typia.json.stringify(member); -typia.json.schema(); -typia.random(); -typia.llm.application(); -``` - -대조: -```ts -// Zod -const Member = z.object({ - id: z.string().uuid(), - email: z.string().email(), - age: z.number().int().min(0).max(150), -}); -type Member = z.infer; -// → Member 사용처: 타입 X, 스키마 Y. 둘이 가장 가까운 형태로 묶여있지만 여전히 둘. -``` - -차이의 핵심: **`z.infer`는 한 방향 도출**이다. 스키마에서 타입이 나온다. 그래서 "Pure TypeScript 우선"이 아니라 "스키마 우선 + 타입 보너스"다. - -typia는 정확히 그 반대다. **타입에서 모든 것이 나온다**. JSDoc 코멘트와 `tags.*` 브랜드 둘 다 가능한 이유도 여기 있다 — 둘 다 TS 타입 시스템 안에 있는 표현이기 때문이다. - -### 2. 컴파일러가 곧 진실 검사관 - -별도 스키마 정의는 TS 컴파일러가 검증할 수 없다. `z.string().email()`이 진짜 email 검증인지는 컴파일러가 모른다 — 라이브러리 내부 약속일 뿐. 다른 사용자가 `z.string()`으로 "email임을 잊고" 정의해도 컴파일러는 침묵한다. - -typia에서는 그런 일이 불가능하다. `string & tags.Format<"email">` 자체가 타입이고, 사용자가 잘못 쓰면 다른 타입이 되어 컴파일러가 잡는다. **"타입에 거짓말을 할 수 없다"** 가 핵심. - -이 사고는 typia에 한정되지 않는다 — TypeScript 자체의 사상("한 곳에서 진실을 말한다")의 자연스러운 확장이다. typia는 "TypeScript의 타입 시스템을 끝까지 믿어라" 라는 입장의 가장 극단적 표명이다. - -### 3. 컴파일 시점 = 검증 코드 생성 시점 - -이는 표현 측면이 아니라 **시간(time) 측면의 단일성**이다. - -런타임 라이브러리는 다음과 같이 동작한다: -``` -[빌드 시] 사용자 스키마 코드 → tsc → JS -[런타임] 스키마 객체 + 입력 값 → 인터프리터 → 결과 +typia.llm.application(); ``` -typia는: -``` -[빌드 시] 사용자 타입 → typia transformer → 전용 검증 함수 (JS 코드 자체) -[런타임] 검증 함수(input) → 결과 -``` +## 현재 해석 -런타임에서 "스키마"라는 데이터 구조가 **존재하지 않는다**. 함수 자체가 검증이다. 이 차이가 200~20,000× 속도 차이를 만든다 — 인터프리터 오버헤드가 0이고, V8이 monomorphic call site로 인라인할 수 있다. +| 축 | 의미 | +| --- | --- | +| 표현 | TypeScript 타입이 출발점이다. | +| 시간 | compile time 에 전용 JS 코드를 만든다. | +| IR | typia metadata 를 거쳐 여러 산출물을 만든다. | +| 실행 | runtime 에 schema interpreter 를 돌리지 않는다. | -### 4. 메타데이터 IR을 가진 컴파일러 +## 현재 구현 -typia는 단순히 "타입에서 코드를 emit"하는 게 아니라, 그 사이에 **자체 IR** 을 가진다 — `MetadataSchema` (`packages/core/src/schemas/metadata/MetadataSchema.ts:49-135`). +`packages/core/native/metadata` 가 IR 이다. ``` -TypeScript Type - ↓ (MetadataFactory) -MetadataSchema ← typia의 자체 IR - ↓ (Programmers) -{ Is, Assert, Validate, JsonStringify, JsonParse, Random, ProtobufEncode, ProtobufDecode, LlmSchema, ... } +TypeScript-Go type/checker information + -> metadata.Schema + -> emitter + -> JS expression ``` -이 IR이 typia의 진짜 사상적 핵심이다. 한 타입을 한 번 분석하면 모든 산출물이 거기서 나온다. 새 기능 추가는 새 Programmer 작성 = IR을 소비하는 새 코드 생성기 추가다. **타입 분석 비용이 N개 산출물에 분산**된다. - -이는 단순한 DRY가 아니라 **컴파일러 설계 사상**이다. typia는 작은 컴파일러 — 자체 IR과 백엔드를 가진 — 라고 말해도 틀리지 않는다. - -## "Pure"의 반증 — typia가 포기하지 않은 것들 - -"Pure"가 어떤 경계에서 멈추는지를 보면 그 경계가 사상의 진짜 모양을 보여준다. - -- **Brand 태그(`tags.Format<"email">`)는 비순수 아닌가?** — 아니다. brand는 TS 타입 시스템 내 표현이고, 컴파일 후 사라진다. `string & tags.Format<"email">`은 TS 타입 검사 시 `string`과 같이 취급된다. - -- **JSDoc 주석(`@minimum 0`)도 받지 않나?** — 받는다. JSDoc은 TS의 1급 시민이고 typia는 둘 다 지원한다. 어느 쪽을 택할지는 사용자 취향. - -- **정말 어려운 타입 (high-order generic, conditional type with infer)도 다 되나?** — 대부분 된다. 단 `T extends Foo ? A : B`처럼 추론 시점에 결정되는 타입은 미지정 generic으로 들어오면 에러 (`iterate_metadata.ts:22-31`). 이는 "Pure TS"가 아니라 "TS의 한계 안에서 Pure". - -- **런타임에 emit되는 헬퍼들(`_isFormatEmail`, `TypeGuardError`)은 비순수 아닌가?** — 이들은 emit된 코드의 일부일 뿐이지 사용자가 import하는 코드가 아니다. 사용자 입장에서는 보이지 않는다. - -## 사상의 한계 — 정직한 인정 - -"Pure TypeScript"는 강력하지만 모든 문제를 풀지 않는다. - -1. **외부 데이터 모델 표현**: OpenAPI YAML이 이미 있는 경우 "타입에서 시작"의 출발점이 없다. `@typia/utils`의 `OpenApiConverter`로 역방향(OpenAPI → 타입)은 되나 사상의 보조 회로. - -2. **공유 스키마 마이그레이션**: 여러 서비스·언어가 한 스키마를 공유할 때, "TS 타입이 진실"은 타 언어엔 진실이 아니다. Protobuf .proto 생성으로 일부 해결하지만, BAML·Pydantic 같은 다언어 환경엔 약하다. - -3. **런타임 동적 스키마**: 동적 타입(폼 빌더 등)에는 적용 불가. **컴파일 타임에 타입이 알려져 있어야** 한다는 전제가 깨진다. - -이 세 가지가 zod/valibot이 typia를 완전히 대체할 수 없는 이유이고, 동시에 두 라이브러리가 공존할 수 있는 이유다. - -## 정리 - -이 wiki는 "Pure TypeScript"를 4가지 차원으로 해석한다 (**재확인**: AI 재구성, 공식 아님): -1. 표현 단일성 (스키마 객체 없음) -2. TS 컴파일러가 진실 검사관 -3. 컴파일 시점 = 검증 코드 생성 시점 -4. 자체 IR (MetadataSchema)을 가진 컴파일러 +legacy TypeScript `MetadataFactory` / `Programmer` 구현은 현재 코드베이스의 기본 변환 경로가 아니다. -이 4가지가 모두 합쳐져야 typia다. 하나라도 빠지면 "그냥 빠른 검증기". typia는 이 4를 동시에 지키는 거의 유일한 라이브러리 (ts-runtime-checks는 1·2·3뿐 4가 없어 확장 약함; Deepkit은 4를 가졌지만 1·3을 약화해 reflection으로). +## 한계 -→ [03. 설계 원칙](03-design-principles.md) 으로 이어진다. +- compile time 에 타입이 알아야 한다. +- runtime dynamic schema 에는 맞지 않는다. +- 다른 언어가 원천 schema 인 시스템에서는 별도 변환 경계가 필요하다. diff --git a/wiki/01-philosophy/03-design-principles.md b/wiki/01-philosophy/03-design-principles.md index 7744de3fe6b..aaa8ee4a471 100644 --- a/wiki/01-philosophy/03-design-principles.md +++ b/wiki/01-philosophy/03-design-principles.md @@ -1,110 +1,29 @@ -# 03. 설계 원칙 — 8가지 코드 패턴 (wiki의 귀납) +# 03. Design Principles -> ⚠️ **성격**: 아래 P1~P8은 **AI가 typia 코드를 관찰해 귀납한 패턴**이다. samchon의 공식 원칙이 아니며 "개발 규칙서"도 아니다. 외부 관점의 post-hoc 서술이고 저자 의도와 다를 수 있다. -> -> **쓰임**: 새 기능 추가 시 기존 스타일과 충돌을 피하는 체크리스트, 또는 기여자가 설계 철학을 빠르게 파악하는 도구. **공식 문서 대체 아님**. +현재 코드에서 보이는 원칙만 적는다. -[02-pure-typescript.md](02-pure-typescript.md)의 4중 해석은 코드 차원에서 다음 8가지 패턴으로 귀납된다고 이 wiki는 본다. +## P1. public TypeScript API 유지 -## P1. 메타데이터 IR을 모든 기능의 입력으로 삼는다 +사용자는 계속 `typia.is()` 같은 API 를 호출한다. compiler host 와 native backend 는 내부 구현이다. -`packages/core/src/schemas/metadata/MetadataSchema.ts` (IR 프로퍼티 `:50-69`)가 typia의 진짜 표준이다. 모든 Programmer는 `MetadataSchema`를 입력으로 받는다. +## P2. metadata IR 중심 -``` -Type → MetadataFactory → MetadataSchema → { Is, Assert, Validate, JsonStringify, ..., LlmSchema } -``` +`packages/core/native/metadata` 가 현재 IR 이다. -- 새 기능 = 새 Programmer = MetadataSchema의 새 사용자. -- IR이 곧 **확장점**. +한 번 분석한 타입에서 validator, JSON, LLM, protobuf, random 산출물이 나온다. -**위반 시 비용**: 새 기능이 직접 TypeScript Type을 보면, MetadataFactory가 처리한 union/intersection/recursive 처리, 캐싱, 알리아스 추적을 다시 구현해야 한다. typia 안에서 이 함정에 빠진 코드는 거의 없다. +## P3. adapter 는 얇게 -## P2. Programmer 패턴 — 모든 기능을 같은 모양으로 정렬 +`packages/transform/native` 는 typia call site 를 찾고 rewrite 를 준비한다. 타입 분석과 JS expression 생성은 `packages/core/native` 가 맡는다. -모든 Programmer는 다음 시그니처로 통일된다: +## P4. host 와 consumer 분리 -```ts -namespace XxxProgrammer { - export const write = (props: IProgrammerProps): ts.ArrowFunction; - export const decompose = (...): { functions, statements, arrow }; -} -``` +`@typia/ttsc` 는 generic host 다. typia는 `typia/lib/transform` 으로 consumer native backend 를 선언한다. -이는 단순한 코드 컨벤션이 아니라 **확장 가능성의 약속**이다. 새 기능을 만들 때 어떤 인터페이스를 따르면 되는지가 명확하다. +## P5. diagnostics 는 compile surface 로 올린다 -`@typia/transform`의 `GenericTransformer.scalar/factory`가 이 인터페이스 위에서 동작 — `props.write()`를 부르면 끝이다. +잘못된 project config, plugin config, native backend 실패는 stderr / exit code / JS API error 로 드러나야 한다. -## P3. AST 생성은 ts.factory만 사용 +## P6. legacy transformer 를 현재 경로로 섞지 않는다 -`packages/core` 전체에서 `ts.factory.createIdentifier`·`createCallExpression`·`createIfStatement` 등 public factory만 사용. 직접 `{ kind: ts.SyntaxKind.Identifier, ... }` 로 노드를 만들지 않는다. - -`ts-expose-internals`를 devDependency로 선언해 두지만 실사용은 거의 없다 (대부분 type 용도). 이는 **TS 호환성 표면 최소화**의 가장 중요한 자산 — tsgo 대응 시 결정적. - -## P4. 모듈 식별은 import 경로로 - -`CallExpressionTransformer.ts:145-151`은 호출이 typia.is인지 판별할 때 **타입 모양이 아니라 선언이 위치한 파일 경로**(`typia/lib/*.d.ts`, `typia/src/*.ts`)를 본다. - -→ TypeScript의 internal type representation이 바뀌어도 typia는 멈추지 않는다. - -## P5. 어댑터는 얇게 — 결합 비용을 의존성 경계로 흡수 - -`packages/transform`은 `@typia/core` 위에 얹은 얇은 어댑터다. 라우팅·import 관리·diagnostic만 담당하고 코드 생성은 모두 core가 한다. - -LLM 통합(mcp/langchain/vercel)도 같은 패턴: ILlmController/ILlmFunction라는 단일 IR을 각 SDK의 Tool 타입으로 매핑하는 ~150 LOC 함수가 전부. - -→ **외부 API 변동의 영향이 한 파일에 갇힌다**. - -## P6. 타입 정보는 한 번만 분석 - -`MetadataCollection`이 `ts.Type → MetadataObjectType` 1:1 캐시를 한다. 같은 타입을 두 번째 만나면 캐시 히트. - -→ 재귀 타입, 공유 타입의 무한 루프와 메모리 폭발 모두 한 메커니즘으로 해결. - -## P7. 에러 경로는 컴파일 타임에 결정 - -런타임 reflection 라이브러리는 검증 실패 시 **객체를 거꾸로 추적**해 path를 만든다. typia는 컴파일 타임에 emit하는 코드 자체에 path 문자열을 인라인한다: - -```js -if ("string" !== typeof input.user.address[0].zipCode) - errors.push({ path: "$input.user.address[0].zipCode", expected: "string", value: input.user.address[0].zipCode }); -``` - -`AssertProgrammer.ts:56-84`의 `create_guard_call`이 이 경로 문자열을 컴파일 타임에 합성한다. - -→ 에러 메시지가 정확하면서 런타임 비용 0. - -## P8. 한 타입에서 N개 표준이 동시에 도출 - -`packages/interface`가 OpenAPI v3.0 / v3.1 / SwaggerV2와 **Emended OpenApi(정규화 버전)** 를 모두 들고 있는 이유다. 사용자가 어떤 표준 버전을 원하든 같은 메타데이터에서 변환만 하면 된다. - -LLM 스키마(`ILlmSchema`)도 같은 사상 — 공통 모델 + `IConfig.strict`로 OpenAI/Anthropic/Google 호환 모드 분기. - -## 위 8가지를 깨면 무엇이 깨지는가 - -| 원칙 위반 | 발생할 위험 | -|---|---| -| P1 직접 Type 분석 | union/intersection/recursive 처리 중복 → 미묘한 버그 | -| P2 Programmer 시그니처 일탈 | 새 기능을 transformer가 호출 못 함 | -| P3 ts.* internal 사용 | TS 마이너 업그레이드마다 깨짐, **tsgo에서 0% 가능성** | -| P4 타입 모양으로 식별 | TS의 type narrowing 변경 시 잘못된 식별 | -| P5 어댑터에 비즈니스 로직 | 외부 SDK 의존이 도메인까지 침투 | -| P6 캐시 우회 | recursive 타입 무한 루프 | -| P7 런타임 path 합성 | 메시지 정확도 ↓ + 런타임 비용 ↑ | -| P8 표준별 별도 IR | OpenAPI 3.2가 나오면 기능마다 따로 패치 | - -→ 이 8가지는 **typia가 외부 변동(TS, LLM SDK, OpenAPI 표준)에 대해 가진 면역계**다. 사상 자체가 면역계의 설계 원리다. - -## 사상이 잘 지켜진 부분 / 미흡한 부분 (간단한 자기진단) - -| 영역 | 사상 일치도 | 비고 | -|---|---|---| -| MetadataSchema와 Programmer 패턴 | ★★★★★ | 사상의 가장 깨끗한 구현 | -| transformer 어댑터 분리 | ★★★★★ | core/transform 책임 분리 모범 | -| LLM 통합 어댑터들 | ★★★★ | 패턴 일관, 추후 base class 추출 여지 | -| public TS API만 사용 | ★★★★★ | tsgo 대응의 핵심 자산 | -| 캐싱·재귀 처리 | ★★★★ | 견고하나 algorithm 주석 부족 | -| 에러 메시지 path 합성 | ★★★★★ | 다른 라이브러리가 흉내 못 함 | -| 표준 일원화 (OpenAPI/LLM) | ★★★★ | Emended 모델 좋음. 신규 표준 대응은 매번 수동 | -| 거대 파일들 (CheckerProgrammer 1614 LOC) | ★★★ | 사상에는 맞지만 가독성 약함 — 분리 여지 | - -→ 다음: [04. 포지셔닝과 가치관](04-positioning.md) +`@typia/core` / `@typia/transform` TypeScript transformer package 는 현재 코드베이스에 없다. 현재 경로는 Go native backend 다. diff --git a/wiki/01-philosophy/04-positioning.md b/wiki/01-philosophy/04-positioning.md index b93bfc27edd..91806fc79ec 100644 --- a/wiki/01-philosophy/04-positioning.md +++ b/wiki/01-philosophy/04-positioning.md @@ -1,97 +1,26 @@ -# 04. 포지셔닝과 가치관 — 좌표축 위의 typia +# 04. Positioning -## typia를 측정하는 4개 좌표축 +typia의 위치는 다음 네 축으로 설명된다. -typia를 다른 라이브러리들과 비교하려면 다음 4축이 필요하다. +| 축 | typia | +| --- | --- | +| 표현 | TypeScript type first | +| 실행 | compile-time code generation | +| 영역 | validator, JSON, LLM, protobuf, random | +| 비용 | compiler host 필요 | -``` -A. 표현축 : 스키마 객체 ←→ 순수 타입 -B. 시점축 : 런타임 인터프리터 ←→ 컴파일 타임 코드 생성 -C. 영역축 : 단일 기능(검증만) ←→ 다영역(검증+직렬화+스키마+LLM+protobuf) -D. 통합 마찰축 : 런타임 import만 ←→ 빌드 파이프라인 변경 필요 -``` +## 다른 접근과의 차이 -이 4축에 주요 라이브러리를 배치하면: +- Zod / Valibot / TypeBox: schema object first +- class-validator: decorator / runtime metadata +- Deepkit: runtime reflection +- typia: TypeScript type first + compile-time emit -| 라이브러리 | A 표현 | B 시점 | C 영역 | D 마찰 | -|---|---|---|---|---| -| Zod | 스키마 | 런타임 + JIT(v4) | 단일+생태계 | 0 | -| Valibot | 스키마(함수) | 런타임 | 단일 | 0 | -| ArkType | TS-syntax DSL | 런타임 | 단일 | 0 | -| TypeBox | JSON Schema | 런타임 | 단일+OpenAPI | 0 | -| io-ts | 스키마 | 런타임 | 단일 | 0 | -| Deepkit | 타입 | 컴파일+런타임 reflection | 다영역(프레임워크) | 중(transformer) | -| ts-runtime-checks | 타입 | 컴파일 | 단일 | 중 | -| **typia** | **순수 타입** | **컴파일** | **다영역(올인원)** | **중** | -| BAML | DSL | 컴파일+codegen | 단일(LLM) | 높(DSL) | +## 현재 toolchain -typia는 **A=가장 순수, B=가장 컴파일, C=가장 다영역, D=중간**의 조합이다. 이 좌표가 비어 있는 자리였고, typia가 그 자리를 채웠다. +- `ttsc`: compiler adapter / plugin host +- `ttsx`: runner +- `@typia/unplugin`: bundler adapter +- `typia/lib/transform`: typia native plugin entry -## 좌표가 의미하는 가치관 - -| 축 | typia의 가치판단 | -|---|---| -| A | "표현이 둘이면 진실도 둘. 진실은 하나여야 한다." | -| B | "런타임 비용이 0에 가까워야 검증이 어디서나 켜진다." | -| C | "한 타입의 모든 변환은 같은 IR에서 나오는 것이 자연스럽다." | -| D | "마찰은 일회성이지만 사상의 비순수는 영구적이다." | - -D축은 약점이다. 그러나 typia는 "마찰 0"을 위해 사상 1·2·3을 양보하지 않는다. 이게 가치관 — 어디서 양보하지 않을지의 선택. - -## "가장 빠르다"는 진짜 무엇인가 - -벤치마크 숫자(20,000×, 200×, 287K MB/s, ...)보다 더 중요한 것은 **속도가 사상의 따름정리**라는 점. - -순서가 거꾸로다: -- 보통: "빠르려면 컴파일 시점에 emit한다" -- typia: "Pure TypeScript의 자연스러운 결과가 컴파일 emit이고, 그 부산물이 속도" - -누가 "런타임에 같은 속도"를 내놓아도 typia의 가치 명제는 무너지지 않는다 — 핵심은 **타입 단일성**이지 속도 자체가 아니다. 속도는 결과. - -이 정렬 덕에 typia 마케팅에는 일관성이 있다. "빠르다"만 강조하지 않고 "Pure TypeScript"를 함께 강조하는 이유. - -## LLM 시대의 새로운 좌표 — type-first vs schema-first - -2024~2026 동안 좌표축이 하나 더 생겼다. - -``` -type-first schema-first - typia Zod, BAML - ArkType(부분) Pydantic -``` - -LLM/agentic 시대의 implication: - -| 차원 | type-first | schema-first | -|---|---|---| -| AI 코드 생성 | LLM이 interface만 생성하면 끝 | 스키마 객체까지 생성·동기화 필요 | -| 프롬프트 버저닝 | 자동 (코드 git diff) | 별도 도구 필요(BAML이 강점) | -| 멀티 언어 | TS만 가능 | Pydantic·BAML로 다언어 | -| 런타임 동적 스키마 | 불가 | 가능 | - -typia가 type-first 진영의 가장 우수한 표현이고, AutoBE/Agentica가 그 위에 만든 사례다. 이 이야기는 typia가 더 강하게 외부에 알려야 할 부분이다 — "vibe coding 시대의 typia"라는 한 줄로 압축된다. - -## typia의 딜레마 — Standard Schema 표준화 - -2025~2026의 가장 큰 외부 변동은 **Standard Schema 1.0** 표면화. Zod·Valibot·ArkType·Effect Schema·TypeBox가 모두 `~standard` 인터페이스를 구현하고, MCP TS SDK·Next.js Server Actions·Hono·Drizzle이 수용. - -이는 typia에게 두 가지를 동시에 의미한다: - -**기회**: typia가 Standard Schema 어댑터를 노출하면 Zod 자리에 끼워 넣을 수 있다 → Zod 종속 생태계로 즉시 진입. - -**위협**: typia가 어댑터를 노출하지 않으면 "Zod와 호환 안 되는 라이브러리" 가 된다. AI SDK / MCP / LangChain 통합에서 자기 어댑터 패키지(@typia/vercel, @typia/mcp, @typia/langchain)를 계속 만들어야 하는 부담. - -→ Standard Schema 어댑터 출시는 사상이 양보하는 게 아니라 **사상을 더 멀리 퍼뜨리는 통로**다. 이게 [07-strategy/](../07-strategy/)에서 가장 우선순위 높은 항목이 되는 이유. - -## "사상이 코드를, 코드가 사상을 증명한다" — 한 줄 요약 - -typia는 그냥 "빠른 검증 라이브러리"가 아니다. **TypeScript 타입 시스템에 대한 한 가지 입장의 가장 정직한 코드적 구현**이다. "타입이면 충분하다. 타입을 끝까지 믿어라." - -이 입장을 가진 라이브러리는 거의 typia와 ArkType 둘뿐이고, ArkType은 transformer 없는 비용을 DSL 표면에서 갚는다. typia는 transformer 비용을 받아들이고 그 대가로 사상의 가장 순수한 형태를 얻었다. - -다음을 결정해야 하는 시점이다: -1. 사상을 더 강하게 옹호하는 마케팅 (vibe coding / type-first / Standard Schema) -2. 사상을 위협하는 외부 변동 (tsgo) 에 대한 대응 -3. 사상을 양보하지 않으면서 통합 마찰을 낮추는 기술 (unplugin 강화, Standard Schema 어댑터) - -→ [06-feedback/](../06-feedback/) 와 [07-strategy/](../07-strategy/) 로 이어진다. +속도는 결과다. 핵심은 타입과 런타임 산출물 사이의 중복 표현을 없애는 것이다. diff --git a/wiki/02-architecture/00-README.md b/wiki/02-architecture/00-README.md index 62651bdb75d..fdbde165cb2 100644 --- a/wiki/02-architecture/00-README.md +++ b/wiki/02-architecture/00-README.md @@ -1,24 +1,26 @@ -# 02. 아키텍처 (Architecture) +# 02. Architecture -현재 typia 구현 구조를 설명한다. 전환 계획은 [08-tsgo-master-plan/](../08-tsgo-master-plan/)에서 다룬다. +현재 typia 변환 구조만 설명한다. -## 읽기 순서 +## 한 줄 -1. [01-overview.md](01-overview.md) — 한 장의 그림 (typia 내부) -2. [02-data-flow.md](02-data-flow.md) — 한 호출이 코드가 되기까지 (9단계) -3. [03-package-graph.md](03-package-graph.md) — 현행 9개 패키지 의존 그래프 (`@typia/ttsc` 제외) -4. [04-transformation-pipeline.md](04-transformation-pipeline.md) — `ttsc` 기본 경로와 `@typia/unplugin` 대체 경로 (현재) +`typia/lib/transform` 이 `@typia/ttsc` plugin 으로 로드되고, Go native backend 가 `typia.*` 호출을 emitted JS 로 rewrite 한다. -## 핵심 한 줄 +## 현재 구조 -> 4개 박스(빌드 통합 / AST 어댑터 / 메타데이터 분석 / 코드 생성)로 깨끗히 분리. 가운데에 자체 IR(MetadataSchema). TypeScript 호환성 표면이 한 박스에 격리. +| 영역 | 현재 | +| --- | --- | +| compiler host | `@typia/ttsc` | +| runner | `@typia/ttsx` | +| TypeScript compiler | `@typescript/native-preview` | +| typia plugin entry | `typia/lib/transform` | +| typia native adapter | `packages/transform/native` | +| analyzer / emitter | `packages/core/native` | +| bundler adapter | `@typia/unplugin` | -## 현재 vs 미래 (요약) +## 읽기 순서 -| | 현재 | 전환 후 목표 | -|---|---|---| -| 빌드 도구 | `typia setup` → `@typescript/native-preview` + `@typia/ttsc`, 또는 `@typia/unplugin` | `typescript@7` + `@typia/ttsc` | -| 엔진 | @typia/core 30,307 + transform 4,306 = 34,613 LOC (9 패키지 합계 64,678 LOC) | Go 100~150K LOC (ttsc 내장) | -| 사용자 API | `typia.is(input)` | **동일** | -| tsconfig plugins | `typia/lib/ttsc/plugin` 기본, `typia/lib/transform` 는 compatibility alias | 같은 host plugin 계약 유지 | -| 설치 | `npm i typia` + `npx typia setup` | preview compiler 제거 후 같은 setup 흐름 | +1. [01-overview.md](01-overview.md) +2. [02-data-flow.md](02-data-flow.md) +3. [03-package-graph.md](03-package-graph.md) +4. [04-transformation-pipeline.md](04-transformation-pipeline.md) diff --git a/wiki/02-architecture/01-overview.md b/wiki/02-architecture/01-overview.md index 97045eb04f5..cfe97ca0358 100644 --- a/wiki/02-architecture/01-overview.md +++ b/wiki/02-architecture/01-overview.md @@ -1,95 +1,55 @@ -# 01. 아키텍처 개요 — 한 장의 그림 - -## 한 장의 그림 +# 01. Overview ``` -┌────────────────────────────────────────────────────────────────────┐ -│ 사용자 코드 (TS) │ -│ │ -│ import typia, { tags } from "typia"; │ -│ typia.is(input); │ -│ typia.json.stringify(member); │ -│ typia.llm.application(); │ -└────────────────────────┬───────────────────────────────────────────┘ - │ ttsc 또는 unplugin - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ @typia/ttsc | unplugin-typia │ -│ │ -│ - 기본 경로: `@typia/ttsc` host가 `typia/lib/ttsc/plugin` 로드 │ -│ - unplugin: vite/webpack/rspack/esbuild/rolldown/bun/farm/next │ -└────────────────────────┬───────────────────────────────────────────┘ - │ TransformerFactory(program, options, extras) - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ @typia/transform ─ FileTransformer / CallExpressionTransformer │ -│ │ -│ - 각 SourceFile 깊이 우선 순회 │ -│ - typia.* 호출 식별 (선언 파일 경로 기반) │ -│ - FUNCTORS[module][methodName]로 라우팅 │ -│ - 결과 AST 삽입 + import 자동 주입 │ -└────────────────────────┬───────────────────────────────────────────┘ - │ IProgrammerProps - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ @typia/core ─ MetadataFactory │ -│ │ -│ ts.Type ──→ MetadataSchema (typia 자체 IR) │ -│ - union/intersection/recursive/generic 처리 │ -│ - MetadataCollection 캐시 │ -│ - ts.TypeChecker public API만 사용 │ -└────────────────────────┬───────────────────────────────────────────┘ - │ MetadataSchema - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ @typia/core ─ Programmers (코드 생성기) │ -│ │ -│ IsProgrammer / AssertProgrammer / ValidateProgrammer │ -│ JsonStringifyProgrammer / JsonParseProgrammer / JsonSchema... │ -│ ProtobufEncodeProgrammer / ProtobufDecodeProgrammer / Message... │ -│ LlmSchemaProgrammer / LlmApplicationProgrammer / Parameters... │ -│ RandomProgrammer / FunctionalProgrammer / HttpProgrammer ... │ -└────────────────────────┬───────────────────────────────────────────┘ - │ ts.Expression / ts.ArrowFunction - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ 사용자 코드의 그 위치에 emit된 결과 │ -│ │ -│ const validate = (input) => { │ -│ const errors = []; │ -│ if ("string" !== typeof input.id || !_isFormatUuid(input.id)) │ -│ errors.push({ path: "$input.id", expected: ..., value: ... });│ -│ // ...수십~수백 줄 inline 검증 코드 │ -│ return { success: errors.length === 0, errors, data: input }; │ -│ }; │ -└────────────────────────────────────────────────────────────────────┘ +user TypeScript + typia.is(input) + | + v +tsconfig.json + compilerOptions.plugins = [{ transform: "typia/lib/transform" }] + | + v +@typia/ttsc + load project + load plugin + run TypeScript-Go + | + v +typia/lib/transform + declares native.mode = "typia" + declares native.binary = typia ttsc launcher + | + v +packages/transform/native + collect typia.* call sites + build rewrite set + | + v +packages/core/native + analyze TypeScript type + emit JavaScript expression + | + v +toolchain/ttsc/driver + rewrite emitted JS ``` -## 4개의 큰 박스 - -| 박스 | 책임 | 패키지 | -|---|---|---| -| **빌드 통합** | 표준 compiler/번들러에 transformer 주입 | `@typia/ttsc`, `@typia/unplugin` | -| **AST 어댑터** | typia.* 식별 + 라우팅 | `@typia/transform` | -| **메타데이터 분석** | ts.Type → MetadataSchema | `@typia/core` (factories) | -| **코드 생성** | MetadataSchema → ts.Expression | `@typia/core` (programmers) | - -## 주변부 - -- `@typia/interface` — 모든 패키지가 의존하는 0-dep 타입 정의 (IValidation, ILlmApplication, OpenApi, tags, ...) -- `@typia/utils` — 런타임에 emit되는 헬퍼들 + OpenAPI/LLM 변환 유틸 -- `typia` — 사용자가 import하는 메인 모듈 (CLI 포함) -- `@typia/mcp / langchain / vercel` — LLM 프레임워크 어댑터 3종 - -## 이 그림이 말해주는 4가지 강점 +## 책임 -1. **분리가 깨끗하다** — 각 박스가 자기 책임만 한다. 새 기능 추가 시 어느 박스를 건드릴지 명확. -2. **TypeScript Compiler API는 한 박스(transform)가 격리** — tsgo 대응 시 이 박스만 다시 만들면 된다. -3. **MetadataSchema가 자체 IR** — TS를 바꿔도, 다음 프로그래밍 언어로 가도 IR 위 코드는 살아남는다. -4. **번들러 통합이 어댑터로 분리됨** — vite든 webpack이든 rspack이든 같은 core가 돌아간다. +| 영역 | 위치 | 책임 | +| --- | --- | --- | +| public API | `packages/typia` | 사용자가 import 하는 runtime API | +| plugin entry | `packages/typia/src/transform.ts` | native backend 선언 | +| compiler host | `toolchain/ttsc` | build/check/transform, plugin load, rewrite orchestration | +| runner | `toolchain/ttsx` | TypeScript 실행 | +| call-site adapter | `packages/transform/native` | typia 호출 수집 | +| analyzer/emitter | `packages/core/native` | type -> metadata -> JS expression | +| bundler adapter | `packages/unplugin` | `@typia/ttsc.transform()` 호출 | -## 이 그림이 보여주는 1가지 위험 +## 제거된 표면 -빌드 통합 박스(맨 위)가 tsc API에 강하게 묶여 있다. tsgo 7.0이 in-process API를 포기하면 이 박스 모양이 근본적으로 바뀐다(IPC·async). 그러나 **나머지 3개 박스는 그대로** — IR과 코드 생성기는 ts.Type 모양과 무관하게 돈다. 이게 typia가 tsgo를 견디는 구조적 이유. +- `@typia/core` TypeScript package +- `@typia/transform` TypeScript package +- TypeScript AST factory 기반 legacy transformer implementation -→ 다음 [02. 데이터 플로우](02-data-flow.md) +`typia/lib/transform` 은 남아 있다. 다만 의미가 legacy transformer 에서 native plugin entry 로 바뀌었다. diff --git a/wiki/02-architecture/02-data-flow.md b/wiki/02-architecture/02-data-flow.md index 20750ff9331..0d62d41e4dc 100644 --- a/wiki/02-architecture/02-data-flow.md +++ b/wiki/02-architecture/02-data-flow.md @@ -1,175 +1,65 @@ -# 02. 데이터 플로우 — 한 호출이 코드가 되기까지 +# 02. Data Flow -`typia.is(input)` 호출이 emit된 검증 코드가 되기까지의 전 과정을 단계별로 따라간다. - -## 단계 0. 사용자 입력 +예시: ```ts -interface Member { - id: string & tags.Format<"uuid">; - email: string & tags.Format<"email">; - age: number & tags.Type<"uint32"> & tags.Maximum<150>; -} - const ok = typia.is(input); ``` -## 단계 1. ttsc host / unplugin이 typia transformer를 호출 - -`packages/transform/src/transform.ts:41-68` — `transform(program, options, extras)` 는 현재 `typia/lib/ttsc/plugin` 과 `@typia/unplugin` 이 공통으로 소비하는 TransformerFactory 진입점이다. 호출되면 strict 옵션 검증 후 TransformerFactory를 반환한다. legacy `ts-patch` 경로도 이 시그니처를 reuse할 수 있지만, 현행 기본 계약은 아니다. - -## 단계 2. FileTransformer가 SourceFile 순회 +## 1. plugin load -`packages/transform/src/FileTransformer.ts:18-58`: +`@typia/ttsc` 는 `tsconfig.json` 의 plugin entry 를 읽는다. -- 각 SourceFile에 대해 `ts.visitEachChild` 깊이 우선 탐색 -- CallExpression을 만나면 `iterate_node` → `NodeTransformer` 위임 -- 변환에 실패하면 try/catch로 `addDiagnostic` - -## 단계 3. CallExpressionTransformer가 typia.* 식별 - -`packages/transform/src/CallExpressionTransformer.ts:145-162`: - -``` -expression = typia.is(input) - ↓ -checker.getResolvedSignature(expression)?.declaration - ↓ -declaration의 파일 경로 = "typia/lib/module.d.ts" - ↓ -isTarget("typia/lib/module.d.ts") → true - ↓ -methodName = "is", module = "module" - ↓ -FUNCTORS["module"]["is"]() → IsTransformer.transform({ equals: false }) -``` - -여기서 중요한 점 두 가지: -- **타입 모양이 아니라 선언 파일 경로**로 식별 (P4 원칙) -- FUNCTORS는 2단계 객체 맵 (`module`/`functional`/`http`/`llm`/`json`/`protobuf`/`reflect`/`misc`/`notations`) × 메소드명 - -## 단계 4. GenericTransformer가 type argument 추출 - -`packages/transform/src/internal/GenericTransformer.ts:13-61` (scalar 패턴): - -```ts -GenericTransformer.scalar({ - context, modulo, expression, - method: "is", - write: ({ context, modulo, type, name }) => IsProgrammer.write({...}), -}) +```json +{ + "compilerOptions": { + "plugins": [{ "transform": "typia/lib/transform" }] + } +} ``` -- `expression.typeArguments[0]` 또는 `expression.arguments[0]`에서 type 추출 -- `props.write()` 호출 — core의 IsProgrammer로 위임 - -## 단계 5. MetadataFactory가 ts.Type → MetadataSchema +`typia/lib/transform` 은 `native.mode = "typia"` 와 native backend launcher 를 반환한다. -`packages/core/src/factories/MetadataFactory.ts:120-166`: +## 2. program load -``` -analyze(type) - ├─ explore_metadata() # 진입 - │ └─ iterate_metadata() - │ ├─ iterate_metadata_alias # Member 알리아스 탐지 - │ ├─ iterate_metadata_intersection # string & Format<"uuid"> 처리 - │ │ → atomic{string} + tag{Format:"uuid"} - │ ├─ iterate_metadata_object # Member 객체 분석 - │ │ └─ emplace_metadata_object # properties = [id, email, age] - │ └─ ... - ├─ iterate_metadata_collection # recursive 표시 - └─ iterate_metadata_sort # 의존도 정렬 -``` +`ttsc` 는 TypeScript-Go 로 project 를 읽는다. -결과 (의사 표현): -```js -MetadataSchema { - objects: [{ - name: "Member", - properties: [ - { key: "id", value: { atomics: [string], tags: [Format:"uuid"] } }, - { key: "email", value: { atomics: [string], tags: [Format:"email"] } }, - { key: "age", value: { atomics: [number], tags: [Type:"uint32", Maximum:150] } }, - ], - }], -} -``` +- `tsconfig.json` 파싱 +- `extends` 처리 +- source file load +- type checker 생성 +- emit 준비 -이 IR이 typia의 진짜 표준이다. `MetadataCollection` 캐시가 같은 Type 재방문 시 캐시 히트 (P6). +잘못된 tsconfig 는 stderr diagnostic 과 non-zero exit 로 드러난다. -## 단계 6. IsProgrammer가 검증 코드 생성 +## 3. call-site collection -`packages/core/src/programmers/IsProgrammer.ts:31-94`: +`packages/transform/native` 는 source file 을 순회하며 typia 호출을 찾는다. -``` -IsProgrammer.write(props) - ↓ -CheckerProgrammer.write(props, config) # 공통 검증 골조 - ├─ FeatureProgrammer.decompose # functions/statements/arrow 분해 - ├─ UnionExplorer (필요 시) # discriminator 분기 - ├─ AtomicPredicator → atomist # atomic 단위 검증 - ├─ combiner (AND/OR) # 결합기 - └─ ts.factory.createArrowFunction -``` +- `typia.is()` +- `typia.assert()` +- `typia.json.stringify()` +- `typia.llm.application()` +- 기타 typia public API marker -생성되는 AST의 의사 표현: -```ts -(input) => { - return "object" === typeof input && null !== input - && "string" === typeof input.id && _isFormatUuid(input.id) - && "string" === typeof input.email && _isFormatEmail(input.email) - && "number" === typeof input.age && Number.isInteger(input.age) - && input.age >= 0 && input.age <= 150 - ; -} -``` +## 4. analysis / emit -## 단계 7. ImportProgrammer가 헬퍼 import 수집 +`packages/core/native` 가 TypeScript-Go checker/type 정보를 받아 typia metadata 를 만든다. -`packages/core/src/programmers/ImportProgrammer.ts:14-45`: - -검증 코드가 부르는 `_isFormatUuid`, `_isFormatEmail`은 typia 내부 헬퍼다. ImportProgrammer가: ``` -context.importer.internal("isFormatUuid") -context.importer.internal("isFormatEmail") +type information + -> metadata.Schema + -> JavaScript expression string ``` -호출을 누적해 두고, 마지막에 한꺼번에 emit. - -## 단계 8. FileTransformer가 import + 변환된 AST 합성 -`packages/transform/src/FileTransformer.ts:44-49`: +## 5. rewrite -- 모든 노드 변환 후 `importer.toStatements()`로 import 문 합성 -- "use strict" 다음 위치에 import 주입 -- 원본 typia.is(input) 자리에 단계 6의 ArrowFunction을 즉시호출(IIFE) 형태로 주입 +`toolchain/ttsc/driver` 는 TypeScript-Go 가 emit 한 JS 에 rewrite set 을 적용한다. -## 단계 9. 최종 emit +결과: -```js -"use strict"; -import { isFormatUuid as typia_transform_isFormatUuid } from "typia/lib/internal/_isFormatUuid.js"; -import { isFormatEmail as typia_transform_isFormatEmail } from "typia/lib/internal/_isFormatEmail.js"; -// ... - -const ok = ((input) => { - return "object" === typeof input && null !== input - && "string" === typeof input.id && typia_transform_isFormatUuid(input.id) - // ... -})(input); +```ts +typia.is(input); ``` -런타임에 typia 라이브러리가 import되지 않는다 — 이 함수와 헬퍼들만 남는다. - -## 데이터 플로우의 핵심 통찰 - -| 단계 | 데이터 형태 | 사상적 위치 | -|---|---|---| -| 0 | TypeScript 타입 | "Pure TypeScript" 출발 | -| 1~4 | TS AST + ts.Type | TS Compiler API 의존 표면 | -| **5** | **MetadataSchema (typia IR)** | **타입 시스템 무관한 자체 IR** | -| 6~7 | TS AST (생성된) | TS Compiler API 의존 표면 | -| 8~9 | 사용자 코드 | "0 외부 런타임 의존" 도착 | - -**TS Compiler API에 묶여 있는 것은 1~4와 6~7뿐**. 5번이 자체 IR이라는 건 IR 위쪽을 다른 언어/컴파일러로 옮길 수 있다는 뜻 — tsgo 대응 시 이 분리가 결정적. - -→ 다음 [03. 패키지 의존 그래프](03-package-graph.md) +가 런타임 validator expression 으로 교체된다. diff --git a/wiki/02-architecture/03-package-graph.md b/wiki/02-architecture/03-package-graph.md index 2f277b21a07..e52143b935b 100644 --- a/wiki/02-architecture/03-package-graph.md +++ b/wiki/02-architecture/03-package-graph.md @@ -1,120 +1,46 @@ -# 03. 패키지 의존 그래프 +# 03. Package Graph -> 범위: typia v12 기준 **현행 9개 패키지**의 의존 구조를 설명한다. 신규 전환 드라이버 `@typia/ttsc`는 현재 구현 그래프가 아니라 [08-tsgo-master-plan/](../08-tsgo-master-plan/)에서 별도로 다룬다. - -## 그림 +## public packages ``` - ┌──────────────────────┐ - │ @typia/interface │ ← 0-dep 순수 타입 - │ (IValidation, │ - │ ILlmApplication, │ - │ OpenApi, tags, ...) │ - └──┬─────────┬─────────┘ - │ │ - ┌────────────┘ └──────────────┐ - │ │ - ▼ ▼ - ┌────────────────┐ ┌──────────────────────┐ - │ @typia/utils │ ◀────────────── │ @typia/core │ - │ (LlmJson, │ │ (MetadataFactory, │ - │ HttpLlm, │ ────────────────▶│ Programmers, IR) │ - │ Converters) │ └──┬───────────────────┘ - └──┬─────────────┘ │ - │ │ - │ ┌────────────────────────────────┘ - ▼ ▼ - ┌─────────────────────┐ ┌──────────────────────┐ - │ @typia/transform │ │ typia (메인) │ - │ (TransformerFactory│ ◀───────│ - CLI │ - │ FileTransformer, │ │ - re-export │ - │ CallExpression... │ │ - 사용자 facing API │ - └──┬──────────────────┘ └──┬───────────────────┘ - │ │ - │ ┌─────────────────────────────┘ - ▼ ▼ - ┌─────────────────────┐ - │ @typia/unplugin │ ← vite/webpack/rspack/esbuild/rolldown/bun/farm/next - └─────────────────────┘ - - ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ - │ @typia/mcp │ │ @typia/langchain │ │ @typia/vercel │ - │ (MCP SDK 어댑터) │ │ (LangChain 어댑터) │ │ (Vercel AI SDK) │ - └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ - depends on: depends on: depends on: - interface, utils interface, utils interface, utils - + @modelcontextprotocol + @langchain/core + ai - (peerDependency) (peerDependency) (peerDependency) +@typia/interface + -> @typia/utils + -> typia + -> @typia/unplugin + -> @typia/mcp + -> @typia/langchain + -> @typia/vercel ``` -## 의존 layer 정리 - -| Layer | 패키지 | 역할 | -|---|---|---| -| 0 | `interface` | 순수 타입 (의존 0) | -| 1 | `utils` | 런타임 헬퍼 + 변환 유틸 | -| 2 | `core` | IR + 코드 생성기 | -| 3 | `transform` | TS transformer 어댑터 | -| 4a | `typia` | 사용자 진입 | -| 4b | `unplugin` | 번들러 통합 | -| 4c | `mcp` / `langchain` / `vercel` | LLM SDK 어댑터 | - -→ 위 → 아래로의 의존만 존재. 순환 없음. 이 깨끗함이 최대 자산. - -## 현행 9개 패키지 책임 한 줄 - -| 패키지 | 한 줄 | -|---|---| -| `@typia/interface` | IValidation, ILlmApplication, OpenApi, tags 등 모든 공개 타입 | -| `@typia/utils` | 런타임 헬퍼 + LLM/OpenAPI 변환 유틸 + 검증기 헬퍼 | -| `@typia/core` | MetadataFactory + 모든 Programmer (코드 생성 본체) | -| `@typia/transform` | `typia/lib/ttsc/plugin` 과 `@typia/unplugin` 이 부르는 TransformerFactory (어댑터) | -| `typia` | 사용자가 import하는 메인 + CLI (setup/patch/generate) | -| `@typia/unplugin` | vite/webpack/rspack/...에 transformer 주입 | -| `@typia/mcp` | MCP SDK Tool로 ILlmController 변환 | -| `@typia/langchain` | LangChain DynamicStructuredTool로 변환 | -| `@typia/vercel` | Vercel AI SDK tool()로 변환 | - -## 외부 의존성 표 +## toolchain -| 패키지 | 주요 외부 의존 | -|---|---| -| `interface` | (없음) | -| `utils` | (없음) | -| `core` | `typescript` (peer), `comment-parser` | -| `transform` | `typescript` (peer), `ts-expose-internals` (type only) | -| `typia` | `commander`, `inquirer`, `randexp`, `@standard-schema/spec` | -| `unplugin` | `unplugin`, `diff-match-patch-es` | -| `mcp` | `@modelcontextprotocol/sdk` (peer) | -| `langchain` | `@langchain/core` (peer) | -| `vercel` | `ai` (peer) | - -→ 모든 SDK가 peerDependency다. 사용자가 버전을 통제한다 = typia가 버전을 강제하지 않는다. 깨지면 사용자 탓이 아니라 SDK breaking change 탓이라는 책임 분배가 명확. - -## 어디가 깨지면 어디가 영향을 받는가 (영향도 매트릭스) - -| 깨지는 곳 | 영향 받는 패키지 | 사용자 영향 | -|---|---|---| -| `interface` | 전 패키지 | 매우 큼 (semver-major) | -| `core` IR | transform, typia | 큼 | -| `core` Programmer 추가 | (없음, 추가만) | 없음 (기능 추가) | -| `transform` | typia, unplugin | 중간 (재빌드 필요) | -| `utils` 런타임 헬퍼 | 사용자 emit 코드 | 중간 (업그레이드 시 재빌드) | -| `typia` CLI | 새 사용자 setup | 작음 | -| LLM 어댑터 (mcp/langchain/vercel) | 해당 SDK 사용자만 | 격리됨 | +``` +@typescript/native-preview + -> @typia/ttsc + -> @typia/ttsx +``` -→ **interface와 core IR이 typia의 진짜 ABI**. 이 둘의 변경이 가장 신중해야 한다. +`typia` depends on `@typia/ttsc`. `@typia/ttsx` depends on `@typia/ttsc`. -## 패키지 분리의 5가지 이점 +## native backend -1. 0-dep `interface`로 순환 차단 -2. LLM SDK 어댑터 격리 → 한 SDK 깨져도 다른 패키지 무사 -3. unplugin이 transform과 분리 → 번들러 사용자가 별도 `ttsc` host 없이도 같은 core를 사용 -4. utils가 별도 → 런타임 헬퍼 업데이트가 core 빌드와 무관 -5. typia CLI가 별도 모듈 → CLI 의존(commander/inquirer)이 라이브러리 사용자에게 전파 안 됨 +``` +packages/transform/native + -> packages/core/native + -> toolchain/ttsc/driver +``` -## 1가지 결합 위험 +## current package roles -`mcp`의 Preserve 모드(`McpControllerRegistrar.ts:101-190`)가 MCP SDK의 `private _registeredTools`에 접근한다. SDK 업데이트로 private 명명이 바뀌면 즉시 깨진다. → CI에서 매주 latest SDK로 smoke test 권장. +| package / directory | role | +| --- | --- | +| `typia` | public runtime API, CLI, native plugin entry | +| `@typia/interface` | public type contracts | +| `@typia/utils` | runtime helpers and schema utilities | +| `@typia/unplugin` | bundler adapter over `@typia/ttsc.transform()` | +| `@typia/ttsc` | compiler adapter / plugin host | +| `@typia/ttsx` | runner over `@typia/ttsc` | +| `packages/core/native` | type analysis and JS expression emit | +| `packages/transform/native` | typia call-site collection and rewrite planning | -→ 다음 [04. 변환 파이프라인](04-transformation-pipeline.md) +`@typia/core` and `@typia/transform` TypeScript packages are not part of the current package graph. diff --git a/wiki/02-architecture/04-transformation-pipeline.md b/wiki/02-architecture/04-transformation-pipeline.md index 1bf12263dbf..ff0d4be06ca 100644 --- a/wiki/02-architecture/04-transformation-pipeline.md +++ b/wiki/02-architecture/04-transformation-pipeline.md @@ -1,121 +1,55 @@ -# 04. 변환 파이프라인 — 두 가지 빌드 경로 +# 04. Transformation Pipeline -typia transformer는 현재 두 가지 진입 경로가 있다. 하나는 기본 `ttsc` 경로, 다른 하나는 번들러용 `@typia/unplugin` 경로다. 두 경로가 같은 typia core에 도달한다는 점은 유지되지만, 기본 설치 계약은 더 이상 `ts-patch` 가 아니다. +## `ttsc` -## 경로 A. `ttsc` (기본 / 표준) +기본 빌드 경로다. +```bash +ttsc +ttsc -p tsconfig.json +ttsc --noEmit +ttsc --watch ``` -[사용자] typia setup / ttsc build - ↓ -[ttsc] tsconfig.json의 plugins[].transform 읽음 - ↓ "typia/lib/ttsc/plugin" resolve -[ttsc host] built-in typia plugin 로드 - ↓ native rewrite + JS plugin host 조합 -[ttsc] 각 SourceFile에 rewrite / transform 적용 - ↓ -[ttsc] 변환된 코드를 emit -``` - -### 설치 절차 (typia setup이 자동화) - -1. `npm i typia` -2. `npm i -D @typescript/native-preview @typia/ttsc` -3. `tsconfig.json.compilerOptions.plugins += [{ transform: "typia/lib/ttsc/plugin" }]` -4. `strict` / `strictNullChecks` / `skipLibCheck` 정리 -5. `ttsc build --emit --tsconfig tsconfig.json` - -참고: -- `typia/lib/transform` 는 아직 compatibility alias 로 남아 있어 기존 경로를 즉시 깨뜨리지는 않는다. -- 하지만 현재 문서와 setup wizard 기준의 **기본 경로**는 `typia/lib/ttsc/plugin` 이다. -### 장점 -- `ts-patch install` / `prepare` 없이 기본 경로가 동작한다 -- `build` / `transform` / `check` 를 같은 host 표면에서 검증할 수 있다 -- typia의 Go-backed rewrite path 를 직접 태운다 - -### 단점 -- 오늘 시점에는 preview compiler 패키지에 의존한다 -- `ttsc` 를 직접 호출하지 않는 기존 도구는 별도 적응이 필요할 수 있다 -- `ts-node` / `tsx` 류 실행은 별도 sibling package `@typia/ttsx` 를 추가해야 한다 - -## 경로 B. unplugin (번들러) +흐름: ``` -[사용자] vite/webpack/rspack/esbuild build - ↓ -[unplugin-typia] 각 .ts 파일을 transform 훅에서 가로챔 - ↓ -[unplugin core/typia.ts:31-62] - - ts.createProgram() 또는 캐시된 program - - ts.transform()으로 typia transformer 적용 - - ts.createPrinter()로 변환된 코드 print - - diff-match-patch-es로 sourcemap 생성 - ↓ -[번들러] 변환된 코드를 받아 다음 단계 진행 +ttsc + -> resolve tsconfig + -> load plugins + -> run TypeScript-Go + -> run typia native backend + -> rewrite emitted JS ``` -### 사용 예 (vite) -```ts -// vite.config.ts -import typia from "@typia/unplugin/vite"; -export default { - plugins: [typia({ tsconfig: "tsconfig.json" })], -}; -``` +## `@typia/unplugin` -### 옵션 (`packages/unplugin/src/core/options.ts:7-78`) -- `include` / `exclude` — 파일 필터 -- `tsconfig` — auto/path -- `typia` — ITransformOptions 패스스루 -- `cache` — 빌드 캐시 -- `enforce` — 플러그인 순서 (pre/post) - -### 장점 -- 기본 `ttsc` 경로와 독립적으로 번들러에 통합 가능 -- 빌드 캐시로 재빌드 빠름 -- vite/webpack/rspack/esbuild/rolldown/bun/farm/next 다 지원 - -### 단점 -- `ttsc` 직접 빌드는 변환 안 함 (번들러 통과해야만) -- 캐시 무효화 버그 가능성 -- esbuild의 빠른 트랜스파일러를 그대로 쓰지 못함 (typia는 type info 필요 → tsc 의존) - -## 두 경로의 비교 결정 트리 +번들러용 per-file 경로다. ``` -프로젝트가 표준 compiler 경로로 직접 빌드? -├─ Yes → ttsc (경로 A) -└─ No (vite/webpack/...) - ├─ 라이브러리 빌드도 별도로 필요한가? - │ ├─ Yes → ttsc + unplugin 둘 다 - │ └─ No → unplugin만 (경로 B) - └─ 타입 체크는 하고 emit은 번들러에 맡기는가? - ├─ Yes → unplugin - └─ No → ttsc +bundler transform hook + -> @typia/ttsc.transform({ file, tsconfig, plugins }) + -> ttsc-typia transform + -> JS string + -> bundler result ``` -대부분의 프로젝트: -- 라이브러리 (NestJS 백엔드, npm 패키지) → ttsc -- 앱 (Next.js, Vite SPA) → unplugin -- 라이브러리 + 앱 모노레포 → ttsc + unplugin 둘 다 +`@typia/unplugin` 은 별도 TypeScript transformer 를 들고 있지 않다. `@typia/ttsc.transform()` 을 호출하는 adapter 다. -## 양쪽 다 거치는 공통 핵심: typia transformer factory +## `ttsx` -두 경로 모두 결국 `@typia/transform.transform(program, options, extras)`를 부른다. 그 후 흐름은 같다 → [02. 데이터 플로우](02-data-flow.md) 참조. +runtime 실행 경로다. -## tsgo 시대의 이 그림 (현재 방향) - -[05-research/03-tsgo-status.md](../05-research/03-tsgo-status.md)에 따르면 tsgo는 in-process plugin을 지원하지 않는다 (IPC만). 이 변화가 두 경로에 미치는 영향: - -| 경로 | tsgo 영향 | -|---|---| -| ttsc | **현재 기본 경로** — preview compiler + typia plugin host 를 직접 검증 | -| unplugin | **상대적으로 안전** — 번들러 훅에서 자체적으로 typescript 모듈을 부르므로, TS 7과 TS 6를 별도로 부를 수 있음 | +```bash +ttsx src/index.ts +ttsx --project tsconfig.json src/index.ts +``` -→ 전략적 함의: **`ttsc` 를 기본 경로로, `unplugin` 을 bundler-native 대체 경로로** 운영한다. `typia/lib/transform` + ts-patch 조합은 compatibility layer 로만 남기고, 새 계약의 중심에는 두지 않는다. +현재 실행 방식: -## 한 줄 요약 +- CJS: require hook + per-file transform +- ESM: project build + cache emit + child Node -> 두 진입 경로가 같은 core를 부른다. `ttsc` 는 현재 기본 host 경로이고, `unplugin` 은 번들러 유연성에 충실한 대체 경로다. legacy `ts-patch` 경로는 호환성 표면으로만 남는다. +## 제거된 경로 -→ 패키지별 깊은 구조는 [03-packages/](../03-packages/) 참조. +`@typia/core` / `@typia/transform` 기반 TypeScript legacy transformer 는 현재 코드베이스에 없다. diff --git a/wiki/03-packages/00-README.md b/wiki/03-packages/00-README.md index 93dc640125c..6ec1ad835ee 100644 --- a/wiki/03-packages/00-README.md +++ b/wiki/03-packages/00-README.md @@ -1,12 +1,12 @@ # 03. 패키지별 깊은 분석 -이 섹션은 현재 운영 패키지를 다룬다. `@typia/ttsc`, `@typia/ttsx` 계획은 [08-tsgo-master-plan/](../08-tsgo-master-plan/)에서 다룬다. +이 섹션은 현재 typia 본체 패키지군을 다룬다. TypeScript legacy transformer 패키지였던 `@typia/core` / `@typia/transform` 은 제거되었고, native backend 문서만 남긴다. -## 읽기 순서 (의존도 낮은 것 → 높은 것) +## 읽기 순서 1. [01-interface.md](01-interface.md) — 0-dep 순수 타입 (모든 패키지의 ABI) -2. [02-core.md](02-core.md) — 메타데이터 + 코드 생성 본체 (★ 가장 큼) -3. [03-transform.md](03-transform.md) — transformer host 어댑터 +2. [02-core.md](02-core.md) — `packages/core/native` analyzer/emitter +3. [03-transform.md](03-transform.md) — `packages/transform/native` ttsc adapter 4. [04-typia-utils-unplugin.md](04-typia-utils-unplugin.md) — 사용자 진입 + 런타임 헬퍼 + 번들러 통합 5. [05-llm-integrations.md](05-llm-integrations.md) — mcp/langchain/vercel 어댑터 3종 6. [06-website.md](06-website.md) — 문서/플레이그라운드/블로그 diff --git a/wiki/03-packages/01-interface.md b/wiki/03-packages/01-interface.md index 5d7da4fb470..7501f74140e 100644 --- a/wiki/03-packages/01-interface.md +++ b/wiki/03-packages/01-interface.md @@ -44,11 +44,11 @@ JSDoc 태그와의 차이: JSDoc은 문서용이고, Brand 태그는 검증 코 ## 5. 핵심 인터페이스 -| 인터페이스 | 책임 | 위치 | -|---|---|---| -| `IValidation` | discriminated union (success+data / errors) | `IValidation.ts:30` | -| `IResult` | 일반 성공/실패 결과 | `IResult.ts:31` | -| `IRandomGenerator` | OpenApi.IJsonSchema 기반 랜덤값 생성 | `IRandomGenerator.ts:11` | +| 인터페이스 | 책임 | 위치 | +| --------------------- | --------------------------------------------- | ------------------------ | +| `IValidation` | discriminated union (success+data / errors) | `IValidation.ts:30` | +| `IResult` | 일반 성공/실패 결과 | `IResult.ts:31` | +| `IRandomGenerator` | OpenApi.IJsonSchema 기반 랜덤값 생성 | `IRandomGenerator.ts:11` | | `IJsonParseResult` | lenient JSON 파싱 결과 (부분 데이터 + errors) | `IJsonParseResult.ts:24` | ## 6. 의존 그래프 @@ -56,8 +56,8 @@ JSDoc 태그와의 차이: JSDoc은 문서용이고, Brand 태그는 검증 코 ``` @typia/interface (0-dep, 순수 타입) ↑ - ├─ @typia/core - ├─ @typia/transform + ├─ typia + ├─ @typia/unplugin ├─ @typia/mcp ├─ @typia/langchain ├─ @typia/vercel diff --git a/wiki/03-packages/02-core.md b/wiki/03-packages/02-core.md index b711eaad451..303465742fc 100644 --- a/wiki/03-packages/02-core.md +++ b/wiki/03-packages/02-core.md @@ -1,139 +1,33 @@ -# packages/core — 컴파일러 엔진 (메타데이터 + 코드 생성) +# packages/core/native — Go analyzer/emitter -> 위치: `packages/core/src` -> 책임: TypeScript 타입 → 메타데이터 → 검증/직렬화/스키마/랜덤/Protobuf/LLM 코드. typia의 두뇌. +> 위치: `packages/core/native` -## 1. 책임 +`@typia/core` TypeScript 패키지는 제거되었다. 현재 남은 `packages/core`의 실질 구현은 Go native backend이며, `packages/transform/native`가 typia call site를 넘기면 여기서 타입 메타데이터 분석과 JavaScript 코드 생성을 수행한다. -- **타입 메타데이터 추출**: TypeScript Type을 `MetadataSchema`로 변환 -- **코드 생성**: Is/Assert/Validate, Json {Stringify, Parse, Schema, Application}, Random, Protobuf {Encode, Decode, Message}, LLM {Schema, Application, Parameters} -- **Union/Intersection 최적화**: 합성 타입 분기/병합 -- **재귀 타입 처리**: 순환 참조 감지 + 캐싱 -- **AST 인젝션**: `typia/lib/ttsc/plugin` 또는 `@typia/unplugin` 이 결과 AST를 호출 위치에 삽입 +## 책임 -## 2. 핵심 데이터 모델 +- `analyzer/` — tsgo checker/type/type node를 `metadata.Schema` 로 변환 +- `metadata/` — typia native IR +- `emitter/` — `metadata.Schema` 를 validator/stringifier/schema/random/protobuf/llm JavaScript expression 문자열로 변환 -### MetadataSchema (`schemas/metadata/MetadataSchema.ts:49-135`) -모든 타입을 단일 sum-type으로 표현: -- `atomics[]` — boolean/number/bigint/string -- `constants[]` — 리터럴 -- `templates[]` — template literal types -- `arrays[]`, `tuples[]`, `objects[]`, `aliases[]` -- `natives[]` — Date / Uint8Array / 빌트인 -- `sets[]`, `maps[]`, `functions[]` -- `escaped` — Date→number 같은 변환 표현 -- modifiers: `required / optional / nullable / any` - -### MetadataObjectType -이름 + `properties: MetadataProperty[]` + `recursive?: boolean`. - -### MetadataCollection (`schemas/metadata/MetadataCollection.ts:22-115`) -분석 중 발견된 타입의 캐시/레지스트리. `ts.Type → MetadataObjectType/ArrayType/TupleType/AliasType` 1:1 매핑. 동일 타입 재방문 시 캐시 반환 → 재귀/공유 타입 정확하게 처리. - -## 3. MetadataFactory 동작 +## 주요 흐름 ``` -MetadataFactory.analyze() - ├─ explore_metadata() # 타입 트리 재귀 순회 - │ └─ iterate_metadata() - │ ├─ alias # 타입 별칭 추적 - │ ├─ union # 각 요소 개별 분석 - │ ├─ intersection # 메타데이터 태그 병합 - │ ├─ escape # 변환 타입 (Date → string) - │ ├─ constant # literal value - │ ├─ atomic # primitive flag - │ ├─ native # Date/Uint8Array/... - │ └─ object → emplace_metadata_object() - ├─ iterate_metadata_collection() # recursive 표시 - └─ iterate_metadata_sort() # 의존도 정렬 -``` - -- **재귀**: `MetadataCollection.emplace()` 캐시 — 같은 type 재방문 = 캐시 hit -- **제너릭**: `TypeChecker.getTypeOfPropertyOfType()` 등으로 instantiation. 미지정 generic은 에러 (`iterate_metadata.ts:22-31`) -- **Intersection 병합**: `iterate_metadata_intersection.ts:12-110` — 단일 객체에 다중 tag set 지원 - -## 4. Programmer 패턴 - -공통 인터페이스: 모두 `decompose() → FeatureProgrammer.IDecomposed` - -```ts -{ functions: ts.VariableStatement[], statements: ts.Statement[], arrow: ts.ArrowFunction } +shimchecker.Type + ast.Node + ↓ analyzer.New(...).WalkWithTypeNode(...) +metadata.Schema + ↓ emitter +JavaScript expression string ``` -| Programmer | 동작 | 반환 | 핵심 | -|---|---|---|---| -| **IsProgrammer** | boolean 검증 | true/false | 빠른 경로, AND/OR combiner | -| **AssertProgrammer** | 검증+예외 | T | path 추적, TypeGuardError | -| **ValidateProgrammer** | 모든 오류 수집 | IValidation | 누적, full path | -| **JsonStringifyProgrammer** | 직렬화 | JSON 문자열 | 인라인 최적화, .join() | -| **RandomProgrammer** | 랜덤 인스턴스 | T | 제약 조건 활용 | - -Is vs Assert (`IsProgrammer.ts:31-94`, `AssertProgrammer.ts:56-84`): combiner는 success path만, atomist는 실패 시 `create_guard_call()` 호출. - -## 5. 흥미로운 최적화 - -- **Union 객체 discriminator** (`UnionExplorer.ts:30-139`): 특정 필드 값으로 사전 필터링 → if-else 트리. O(n) 체크 회피. -- **StringifyJoiner 인라인** (`StringifyJoinder.ts:10-113`): 상수 부분은 컴파일 타임에 `"[...]"`로 고정, 동적 속성만 런타임. 마지막 콤마 제거 분기. -- **Recursive 감지** (`iterate_metadata_collection.ts:58-84`): 방문 set으로 cycle, `setArrayRecursive()` 캐싱. -- **Native 인식**: `ts.Symbol`의 fully qualified name + 멤버 시그니처로 Date/Uint8Array 등 식별. - -## 6. TypeScript Compiler API 의존도 - -- `ts.TypeChecker`: 매우 많이 사용 — `typeToString`, `getTypeAtLocation`, `getApparentProperties`, `getIndexInfosOfType`, `getReturnTypeOfClassMethod` -- `ts.factory.*`: AST 생성 (전부) -- `ts.SyntaxKind / TypeFlags`: 타입 플래그 비교 -- `ts.getModifiers()`: readonly 등 -- **ts-expose-internals 사용 없음**: 모두 public API - -## 7. 약점/개선 여지 - -- **거대 파일**: `CheckerProgrammer.ts` 1614 LOC, `RandomProgrammer.ts` ~1200, `JsonStringifyProgrammer.ts` 1129 → 책임 분리 가능성 -- **emplace_metadata_object.ts** 225 LOC: 3중 루프, 동적 키 처리 복잡 -- **iterate_metadata_intersection.ts** 211 LOC: 유효성 검증 로직 인지부담 큼 -- **OptionPredicator 중복 인스턴스화**: Is/Assert/Validate 각각 → 한 군데로 통합 가능 -- **주석 부족**: 함수 헤더는 있으나 알고리즘 핵심부 주석 거의 없음 (특히 union/intersection 처리) -- **테스트 가능성**: TypeChecker, TransformationContext 의존 → unit test 어려움. 통합 테스트로 우회 중 - -## 8. 주요 파일 (50+) - -### 메타데이터 스키마 (`schemas/metadata/`) -- MetadataSchema, MetadataObject(Type), MetadataProperty, MetadataCollection -- MetadataAtomic, MetadataConstant, MetadataArray(Type), MetadataTuple(Type) -- MetadataAlias, MetadataNative, MetadataTemplate, MetadataMap, MetadataSet - -### 메타데이터 추출 (`factories/internal/metadata/`) -- explore_metadata, iterate_metadata -- iterate_metadata_{union, intersection, object, atomic, constant, native, array, tuple, alias, escape, function, sort, collection} -- emplace_metadata_object, MetadataHelper - -### Programmer (`programmers/`) -- IsProgrammer, AssertProgrammer, ValidateProgrammer -- RandomProgrammer, ImportProgrammer - -### 내부 코드 생성 (`programmers/internal/`) -- FeatureProgrammer (602), CheckerProgrammer (1614) - -### 헬퍼 (`programmers/helpers/`) -- FunctionProgrammer, UnionExplorer, UnionPredicator -- AtomicPredicator, OptionPredicator -- StringifyJoinder, StringifyPredicator - -### JSON (`programmers/json/`) -- JsonStringifyProgrammer (1129), JsonApplicationProgrammer, JsonSchemaProgrammer - -### Protobuf / LLM (`programmers/protobuf|llm/`) -- ProtobufEncodeProgrammer, ProtobufDecodeProgrammer -- LlmSchemaProgrammer - -### 팩토리 (`factories/`) -- ExpressionFactory, MetadataFactory, JsonMetadataFactory -- TypeFactory, StatementFactory +## 유지되는 계약 -### 컨텍스트 (`context/`) -- ITypiaContext, ITransformOptions +- 사용자는 계속 `typia.is()`, `typia.json.stringify()` 같은 public API를 호출한다. +- TypeScript transformer AST factory는 더 이상 사용하지 않는다. +- rewrite 결과는 `toolchain/ttsc/driver`가 emitted JS 텍스트에 적용한다. -## 핵심 통찰 +## 남은 참고성 주석 -core는 **모든 타입 정보를 한 자료구조(MetadataSchema)에 모은 뒤, 그 위에서 여러 코드 생성기가 변주**하는 구조다 — typia 내부의 작은 IR. 신규 기능 추가 비용은 낮지만, IR 변경은 모든 Programmer에 파급된다는 대가로 안정성이 강제된다. tsgo 대응 시 'TypeChecker만 다시 구현하면 나머지는 그대로' 라는 분리가 잘 된 자산. +Go 파일 일부에는 과거 TypeScript 구현 파일명이 주석으로 남아 있다. 이는 포팅 출처를 설명하는 참고 주석이며, 현재 빌드 의존성은 아니다. → 다음 [03-transform.md](03-transform.md) diff --git a/wiki/03-packages/03-transform.md b/wiki/03-packages/03-transform.md index 6a1995f9028..cc267449e09 100644 --- a/wiki/03-packages/03-transform.md +++ b/wiki/03-packages/03-transform.md @@ -1,80 +1,56 @@ -# packages/transform — TypeScript Transformer 어댑터 +# packages/transform/native — ttsc typia adapter -> 위치: `packages/transform/src` -> 책임: `typia/lib/ttsc/plugin` / `@typia/unplugin` 이 호출하는 TransformerFactory 진입점. typia.* 호출을 감지하고 `@typia/core`의 Programmer로 디스패치한다. +> 위치: `packages/transform/native` -## 1. 진입점 구조 +`@typia/transform` TypeScript 패키지는 제거되었다. 현재 transform 계층은 Go native ttsc adapter이며, `typia/lib/transform`이 지정하는 native binary가 이 코드를 실행한다. -- `transform.ts:41-68` — 현재 `typia/lib/ttsc/plugin` 과 `@typia/unplugin` 이 공통으로 소비하는 `transform(program, options, extras)`. strict 모드 검증 후 TransformerFactory를 반환. -- `FileTransformer.ts:18-58` — 각 SourceFile을 받아 `ts.visitEachChild`로 깊이 우선 탐색. CallExpression을 만나면 `iterate_node`로 처리. -- `FileTransformer.ts:35-43` — JSDoc 파싱 모드 체크 (TS 5.3 변경 대응). +## 구성 -## 2. 디스패치 — FUNCTORS 2단계 맵 +| 위치 | 책임 | +| ----------------------------- | ---------------------------------------------- | +| `cmd/ttsc-typia/main.go` | `build`, `check`, `transform` command dispatch | +| `cmd/ttsc-typia/build.go` | project emit 경로 | +| `cmd/ttsc-typia/transform.go` | 단일 파일 JS string 반환 경로 | +| `ttsc/visit.go` | typia.\* call site 수집 | +| `ttsc/adapter.go` | core/native analyzer/emitter 연결 | +| `ttsc/cleanup.go` | emitted text 후처리 | -`CallExpressionTransformer.ts:186-581`의 `FUNCTORS[module][methodName]` 객체 룩업. - -- 1단계 module: `module / functional / http / llm / json / protobuf / reflect / misc / notations` -- 2단계 method: `is, assert, validate, createIs, application, structuredOutput, ...` -- 각 entry는 lazy `() => Task` — switch가 아닌 dictionary 룩업이므로 동적 확장 용이. +## build 경로 ``` -FUNCTORS[module][name]() → Task → Task(ITransformProps) → ts.Expression +driver.LoadProgram(...) + ↓ +CollectCallSites(...) + ↓ +AnalyzeTypeWithOptions(...) + ↓ +EmitCall(...) + ↓ +driver.RewriteSet.Add(...) + ↓ +prog.EmitAll(rewrites, writeFile) ``` -식별: `props.context.checker.getResolvedSignature(...).declaration` → 파일 경로가 `typia/lib/*.d.ts | typia/src/*.ts`인지 검증 (`isTarget`). - -## 3. Feature Transformer 패턴 - -세 가지 메인 패턴: - -1. **Scalar** (Is/Assert/Validate) — 입력값+타입 → 검증 함수 호출. `GenericTransformer.scalar` 사용. -2. **Factory** (CreateIs/CreateAssert) — 타입만으로 함수 노드 생성. `GenericTransformer.factory`. -3. **Special** (Random / FunctionalGeneric) — 커스텀. RandomTransformer는 타입만, FunctionalGenericTransformer는 함수 선언을 분석해 파라미터/리턴 검증을 모두 emit. +## transform 경로 -`IsTransformer.ts:7-17`처럼 `transform(config) => (props) => GenericTransformer.scalar(...)` 구조의 부분 적용 패턴이 일관되게 쓰인다. - -## 4. Core와의 결합 +`@typia/unplugin`과 `typia generate` CLI가 쓰는 per-file API다. ``` -features/IsTransformer - → internal/GenericTransformer.scalar - → props.write({ context, modulo, type, name }) - → @typia/core IsProgrammer.write(props) +@typia/ttsc.transform() + ↓ ttsc-typia transform --file=... +collectTypiaRewrites(... onlyFile ...) + ↓ +prog.EmitAll(...) + ↓ +captured JS stdout ``` -`ITypiaContext` 객체로 `checker / transformer(=AST factory) / importer / program / compilerOptions / printer`를 한 번에 전달. 모든 AST 생성은 `ts.factory.*`를 사용 — public API만. - -## 5. Import 관리 - -`ImportProgrammer` (core 측)가 default/named/namespace/internal import를 추적 → `FileTransformer.ts:49`에서 `importer.toStatements()`로 일괄 주입. 이름 충돌은 `internalPrefix: "typia_transform_"`로 회피. `ImportTransformer.ts:99-262`의 두 번째 패스가 사용되지 않은 typia default import를 제거한다 (`isLikelyTransformableCall`). - -## 6. 에러 처리 - -`TransformerError.ts:22-79` — code+message를 가진 커스텀 에러. `MetadataFactory.IError[]`를 받아 포맷팅. -`FileTransformer.ts:74-93` — 노드별 try/catch → `ts.createDiagnosticForNode` → `props.context.extras.addDiagnostic` (현재 compiler host / plugin bridge가 제공). - -## 7. TS 내부 API 의존도 - -- `package.json`이 `@types/ts-expose-internals`를 declare하지만 실제 transform 패키지는 **public API만 사용**. -- 사용하는 외부 API: `TypeChecker.getResolvedSignature`, `Type.isTypeParameter`, `ts.factory.*`, `ts.visitEachChild`, `ts.createDiagnosticForNode`. -- TS 5.3 `jsDocParsingMode` 같은 호환성 분기 한 군데. -- 핵심 안전장치: 모듈 식별을 **import 경로 기반**으로 한다 → 타입 모양 변동에 강함. - -## 8. 주요 파일 한 줄 요약 - -| 파일 | LOC | 책임 | -|---|---|---| -| `transform.ts` | 68 | current host/plugin entry, strict 검증 | -| `FileTransformer.ts` | 143 | AST 순회, error 처리, import 주입 위치 | -| `NodeTransformer.ts` | 25 | CallExpression 필터링 | -| `CallExpressionTransformer.ts` | 581 | FUNCTORS 맵, 라우팅, target 파일 검증 | -| `ITransformProps.ts` | 20 | context/modulo/expression 전달 인터페이스 | -| `TransformerError.ts` | 85 | 컴파일 타임 에러 + diagnostic 포맷팅 | -| `ImportTransformer.ts` | 262 | import path 리라이팅, unused 제거 | -| `TypiaGenerator.ts` | 172 | 프로그래밍 API: tsconfig 파싱→transform→print 오케스트레이션 | -| `internal/GenericTransformer.ts` | 102 | scalar/factory 공통 패턴 | -| `features/*.ts` | 18~50 | 각 typia API의 thin wrapper | +## 제거된 경로 -## 핵심 통찰 +- `transform.ts` +- `FileTransformer.ts` +- `CallExpressionTransformer.ts` +- `TypiaGenerator.ts` +- `features/*.ts` -`packages/transform`은 **얇은 어댑터**다. 비즈니스 로직은 모두 `@typia/core`에 위임. 책임이 깨끗하게 분리되어 있고 (parse 라우팅 vs 코드 생성), TypeScript 호환성 위험이 가장 큰 표면이지만 **public API만 쓰고 import 경로 기반 식별을 채택**해 변동에 강하다 — tsgo 대응의 핵심 자산. +위 TypeScript transformer 코드는 현재 코드베이스에서 제거되었다. legacy transformer가 필요한 사용자는 해당 기능을 포함한 구버전 typia를 사용한다. diff --git a/wiki/03-packages/04-typia-utils-unplugin.md b/wiki/03-packages/04-typia-utils-unplugin.md index c9284202eb1..b7625fdd08b 100644 --- a/wiki/03-packages/04-typia-utils-unplugin.md +++ b/wiki/03-packages/04-typia-utils-unplugin.md @@ -2,121 +2,55 @@ ## A. packages/typia — 사용자 진입 모듈 -### 공개 API 표면 (요약) - -기본 검증 (`module.ts:172-252, 515-779`): -- `is`, `assert`, `assertGuard`, `validate` -- `equals`, `assertEquals`, `validateEquals` — extra property 거부 -- `random`, `createRandom` -- `createIs`, `createAssert`, `createValidate`, `createEquals`, ... - -서브 namespace: -- `typia.json.{schema, schemas, application, parse, stringify, assertParse, assertStringify}` (`json.ts:41-74`) -- `typia.llm.{controller, application, schema, parameters, structuredOutput}` (`llm.ts:54-74`) -- `typia.protobuf.{message, encode, decode, assertEncode, assertDecode}` (`protobuf.ts:35-72`) -- `typia.misc.{clone, prune, literals}` (`misc.ts:30-56`) -- `typia.notations.{camel, pascal, snake}` -- `typia.reflect.{schema, name}` -- `typia.functional.{assertFunction, assertParameters, assertReturn}` -- `typia.http.{formData, queryObject, headers, parameter}` (`http.ts:46-90`) - -### Wrapping 구조 - -- `packages/typia/src/transform.ts` — 단순 re-export of `@typia/transform`. -- `package.json` exports에서 `./lib/transform` 별도 진입점 유지 → legacy `typia/lib/transform` compatibility alias 및 일부 내부/브라우저 경로가 참조. -- 메인 패키지는 **사용자 facing API + CLI**만 들고, 실제 transformer는 의존성으로 가져온다. +### 공개 API 표면 -### 빌드 - -- Rollup 번들 (`config/rollup.config.mjs` 공유) -- `ttsc → rollup` 순으로 lib/ 생성 -- Dual CJS+ESM -- `@typia/core | @typia/transform | @typia/utils | @typia/interface`는 workspace dependency -- `@standard-schema/spec`, `commander`, `inquirer`, `randexp`는 외부 dep -- `@typescript/native-preview` 는 peerDependency -- `@typia/ttsc` 는 현재 build/dev dependency +- 기본 검증: `is`, `assert`, `assertGuard`, `validate`, `equals`, `random`, factory 계열 +- namespace: `json`, `llm`, `protobuf`, `misc`, `notations`, `reflect`, `functional`, `http` +- CLI: `typia setup`, `typia generate` +- plugin entry: `typia/lib/transform` -### CLI (`packages/typia/src/executable/typia.ts`) +### 현재 wrapping 구조 -``` -npx typia setup [--manager npm|pnpm|yarn|bun] [--project tsconfig.json] -npx typia generate --input --output -``` +- `packages/typia/src/transform.ts` 가 `@typia/ttsc.definePlugin()`으로 native plugin을 선언한다. +- `typia/lib/transform` export는 native plugin entry로 유지된다. +- `TypiaProgrammer` legacy alias도 제거되었다. +- 메인 패키지는 사용자 facing API, 런타임 헬퍼, CLI, native plugin entry만 들고 간다. -#### `typia setup` 5단계 -1. PackageManager 감지 (`detect`) -2. Inquirer 인터랙티브 입력 -3. `@typescript/native-preview` + `@typia/ttsc` 설치 -4. 기존 `ts-patch install` / `typia patch` 성격의 prepare/postinstall hook 제거 -5. `tsconfig.json.compilerOptions.plugins += [{ transform: "typia/lib/ttsc/plugin" }]` + strict 옵션 활성화 - -지원 빌더: -- **ttsc** (`@typescript/native-preview` + `@typia/ttsc`) — 기본 경로 -- **unplugin** (Vite/Webpack/Rspack/esbuild/Rolldown/Bun) — 별도 패키지 +### 빌드 ---- +- `ttsc → rollup` 순으로 `lib/` 생성 +- Dual CJS+ESM +- workspace dependency: `@typia/interface`, `@typia/ttsc`, `@typia/utils` +- external dependency: `@standard-schema/spec`, `commander`, `inquirer`, `randexp` +- peer dependency: `@typescript/native-preview` ## B. packages/utils — 런타임 헬퍼 + 변환 유틸 -### 모듈 구조 - `converters/` — `LlmSchemaConverter`, `OpenApiConverter` - `http/` — `HttpError`, `HttpLlm`, `HttpMigration` -- `validators/` — `LlmTypeChecker`, `OpenApiTypeChecker` (V3, V3_1, SwaggerV2) -- `utils/` — `LlmJson`, `StringUtil`, `NamingConvention`, `ArrayUtil`, `MapUtil`, `Singleton` +- `validators/` — OpenAPI/LLM schema validators +- `utils/` — `LlmJson`, `StringUtil`, `NamingConvention`, `Singleton` -### 런타임에 emit되는 헬퍼 -- `TypeGuardError` (path/expected/value 포함) -- `_isFormatEmail`, `_isFormatUuid`, `_isFormatIpv4`, ... -- `_httpFormDataRead*`, `_httpQueryRead*`, `_httpParameterRead*` -- `_ProtobufReader`, `_ProtobufWriter`, `_ProtobufSizer` +## C. packages/unplugin — 번들러 통합 어댑터 -→ 사용자가 `typia.is()`를 부르면 위 헬퍼들이 자동 import 된다. +### 진입점 ---- +- `core/index.ts` — `UnpluginFactory` 본체 +- `core/typia.ts` — `@typia/ttsc.transform()` 호출 +- `vite.ts | webpack.ts | rspack.ts | rollup.ts | esbuild.ts | farm.ts | rolldown.ts | bun.ts | next.ts` — 각 번들러 wrapper -## C. packages/unplugin — 빌더 통합 어댑터 +### 메커니즘 -### 진입점 -- `index.ts` — 옵션 타입 export -- `core/index.ts` — `UnpluginFactory` 본체 (`core/index.ts:39-150`) -- `vite.ts | webpack.ts | rspack.ts | rollup.ts | esbuild.ts | farm.ts | rolldown.ts | bun.ts | next.ts` — 각 번들러 wrapper +- transform hook에서 typia가 포함된 TS 파일만 처리한다. +- `plugins: [{ transform: "typia/lib/transform" }]` 를 명시 주입한다. +- `@typia/ttsc.transform()` 이 반환한 JS string을 diff-match-patch-es + magic-string으로 sourcemap 처리한다. -### ts-patch 우회 메커니즘 (`core/typia.ts:31-62`) -- 현재 기본 설치 계약(`typia/lib/ttsc/plugin`)과 독립적으로, -- TypeScript Compiler API + `ts.transform()` + `ts.createPrinter()`로 **개별 파일 단위 변환** -- 파일별 변환 결과를 캐싱 (`Cache` 클래스) -- diff-match-patch-es로 sourcemap 생성 +### 옵션 -### 옵션 (`core/options.ts:7-78`) - `include`, `exclude` — 파일 필터 - `tsconfig` — 자동/수동 -- `typia` — `ITransformOptions` 패스스루 +- `typia` — native transform option bag - `cache` — 빌드 캐시 ON/OFF -- `enforce` — 플러그인 순서 (pre/post) - ---- - -## 주요 파일 한 줄 설명 - -### packages/typia -- `index.ts` — 모듈 re-export -- `module.ts` — assert/is/validate/random/factory 함수 -- `json.ts | llm.ts | protobuf.ts | misc.ts | notations.ts | reflect.ts | functional.ts | http.ts` — 각 namespace -- `TypeGuardError.ts` — path/expected/value 에러 -- `transform.ts` — `@typia/transform` re-export -- `executable/typia.ts` — CLI dispatcher -- `executable/TypiaSetupWizard.ts` — setup 본체 -- `executable/TypiaGenerateWizard.ts` — generate 명령 -- `executable/setup/PluginConfigurator.ts` — tsconfig 수정 - -### packages/utils -- `index.ts` — 컨버터/HTTP/검증기 re-export -- `utils/LlmJson.ts` — LLM 결과 lenient 파싱 - -### packages/unplugin -- `core/index.ts` — UnpluginFactory 본체 -- `core/typia.ts` — TS Compiler API로 변환 -- `core/options.ts` — 옵션 정의/해석 -- `vite.ts, webpack.ts, ...` — 번들러별 thin wrapper +- `enforce` — 플러그인 순서 → 다음 [05-llm-integrations.md](05-llm-integrations.md) diff --git a/wiki/03-packages/05-llm-integrations.md b/wiki/03-packages/05-llm-integrations.md index 029fe9dad97..d9e3c884109 100644 --- a/wiki/03-packages/05-llm-integrations.md +++ b/wiki/03-packages/05-llm-integrations.md @@ -1,94 +1,34 @@ -# packages/mcp, langchain, vercel — LLM 프레임워크 어댑터 +# LLM integrations -세 패키지 모두 **`ILlmController` / `ILlmFunction`** 을 각 LLM 프레임워크의 네이티브 Tool 타입으로 변환하는 얇은 어댑터다. +Packages: -## 1. 분리 이유 +- `@typia/mcp` +- `@typia/langchain` +- `@typia/vercel` -- **의존성 격리**: `@modelcontextprotocol/sdk`, `@langchain/core`, `ai`를 각각 peerDependency -- **프레임워크 중립**: typia core는 어떤 LLM 프레임워크도 모름 -- **개별 SDK 추적**: 각 SDK의 breaking change를 독립적으로 대응 +## 역할 -## 2. MCP (`packages/mcp`) +`ILlmController` / `ILlmFunction` 을 각 SDK의 tool 형식으로 변환한다. -`registerMcpControllers()` (`internal/McpControllerRegistrar.ts:192-217`): - -1. `controller.application.functions[]`에서 `name/description/parameters` 추출 -2. `ListToolsRequestSchema` 응답에 JSON Schema 그대로 노출 -3. `CallToolRequestSchema` 핸들러: `func.validate()` → 실패 시 `LlmJson.stringify()` 결과 - -**Preserve 모드** (`McpControllerRegistrar.ts:101-190`): MCP SDK의 private `_registeredTools / _toolHandlersInitialized`에 접근해 `McpServer.registerTool()`로 등록된 도구와 공존. **위험 주의** — SDK upgrade 시 깨짐 가능성 가장 큼. - -## 3. LangChain (`packages/langchain`) - -`toLangChainTools()` (`internal/LangChainToolsRegistrar.ts:122-148`): - -- `DynamicStructuredTool` 생성자에 `parameters`(`ILlmFunction`)를 그대로 `schema`에 전달 — LangChain이 JSON Schema 직접 수용 -- `func()` 실행 전 `func.validate(coerced)` → 실패 시 `ToolInputParsingException` - -## 4. Vercel (`packages/vercel`) - -`toVercelTools()`: - -- Vercel AI SDK `tool()` 함수에 `jsonSchema(parameters as JSONSchema7)` 전달 (`internal/VercelParameterConverter.ts:6-7`) -- 검증 패턴 동일 -- 다중 공급자 호환 — Vercel SDK가 OpenAI/Anthropic/Google 모두 지원 - -## 5. 의존 그래프 +## 공통 흐름 ``` -@typia/mcp | @typia/langchain | @typia/vercel -├─ @typia/interface (workspace) -├─ @typia/utils (workspace) -└─ {SDK} (peerDependency) +typia.llm.application() + -> ILlmApplication / ILlmFunction + -> framework tool wrapper + -> func.validate() before execution ``` -`sideEffects: false`, rollup 외부 의존성 자동 externalize. - -## 6. 공통 패턴 (추상화 가능) - -``` -for controller in controllers: - for func in controller.application.functions: - Tool = makeTool({ # 프레임워크별 차이는 여기뿐 - name: func.name, - description: func.description, - schema: func.parameters, - execute: async (args) => { - if (!func.validate(args).success) throw ...; - return await func(); - } - }) -``` - -→ 향후 `@typia/llm-adapter` 추상 베이스로 통합 가능. 현재는 각 SDK의 타입 차이로 별도 구현이 명확함. - -## 7. 외부 SDK 호환성 위험 - -| SDK | 현재 ver | 최소 | 위험도 | -|---|---|---|---| -| `@modelcontextprotocol/sdk` | 1.26.0 | 1.0.0 | **높음** (private API 사용) | -| `@langchain/core` | 1.1.31 | 1.0.0 | 중 | -| `ai` (Vercel) | 6.0.116 | 6.0.0 | 중 | - -완화 전략: -- `func.validate / coerce / parse`가 typia 내부 로직이라 외부 SDK 변동과 격리됨 -- JSON Schema 부분집합(ILlmSchema.IParameters)으로 통일 → SDK 업그레이드 영향 적음 -- **Preserve 모드는 정기적 호환성 테스트가 필수** (CI에서 SDK latest 매주 검증 권장) - -## 8. 주요 파일 +## package boundary -| 파일 | 책임 | -|---|---| -| `mcp/src/index.ts` | `registerMcpControllers()` 공개 API | -| `mcp/src/internal/McpControllerRegistrar.ts` | Tool 레지스트리, Preserve 모드 | -| `langchain/src/index.ts` | `toLangChainTools()` | -| `langchain/src/internal/LangChainToolsRegistrar.ts` | DynamicStructuredTool 생성 | -| `vercel/src/index.ts` | `toVercelTools()`, `toVercelSchema()` | -| `vercel/src/internal/VercelToolsRegistrar.ts` | Vercel tool() 생성 | -| `vercel/src/internal/VercelParameterConverter.ts` | JSON Schema 변환 | +| package | external SDK | +| --- | --- | +| `@typia/mcp` | `@modelcontextprotocol/sdk` | +| `@typia/langchain` | `@langchain/core` | +| `@typia/vercel` | `ai` | -## 핵심 통찰 +SDK 는 peer dependency 로 둔다. -세 어댑터의 패턴이 거의 동일하므로 **커뮤니티 contribution 받기 가장 쉬운 영역**(BAML / Mastra / Genkit / Anthropic Agent SDK / OpenAI Agents SDK 등). 단, MCP Preserve 모드는 SDK private API 의존이라 typia가 떠안은 가장 큰 호환성 리스크 — `private API not available` 시 fallback이 명확해야 한다. +## 현재 주의점 -→ 다음 [06-website.md](06-website.md) +`@typia/mcp` 의 preserve mode 는 MCP SDK private field 에 닿는다. SDK upgrade smoke test 가 필요하다. diff --git a/wiki/03-packages/06-website.md b/wiki/03-packages/06-website.md index f87ed41fbd5..bdaae559af3 100644 --- a/wiki/03-packages/06-website.md +++ b/wiki/03-packages/06-website.md @@ -1,129 +1,40 @@ -# website — 문서/플레이그라운드/블로그 +# website -> 위치: `website/` -> 책임: typia.io 사용자 대면 문서 사이트, 인-브라우저 playground, 블로그. +위치: `website/` -## 1. 기술 스택 +## 역할 -- **Next.js 15.3.4** + `output: "export"` (정적 사이트) -- **Nextra 4.6.0** — `nextra-theme-docs` + `nextra-theme-blog` -- **MDX** — 마크다운 + React 컴포넌트 -- **Rspack 1.0.0** — 브라우저용 TypeScript 컴파일러 번들 (`rspack.config.js`) -- **Monaco Editor** + `@rollup/browser` — 인-브라우저 편집/번들 -- **Pagefind** — 정적 검색 -- **TypeDoc** — API 문서 자동 생성 (`build:typedoc`) +- typia.io 문서 +- playground +- blog +- static export -## 2. IA / 메뉴 (`src/content/docs/_meta.ts`) +## 현재 toolchain 문서 기준 -``` -🙋🏻‍♂️ Introduction -📦 Setup -⛲ Pure TypeScript ← 사상의 핵심 -───────── -📖 Features - ├─ Runtime Validators (validators/{is, assert, validate, functional, tags}) - ├─ Enhanced JSON (json/{schema, stringify, parse}) - ├─ LLM Function Calling (llm/{application, schema, http, json, structuredOutput}) - ├─ Protocol Buffer (protobuf/{message, decode, encode}) - ├─ Random Generator - └─ Miscellaneous -───────── -🔗 Appendix - ├─ Utilization (NestJS, tRPC, MCP, Vercel, LangChain, Hono) - ├─ API Documents (TypeDoc) - ├─ Benchmark (외부 GitHub) - └─ dev.to Articles -``` - -각 기능별 3~5 페이지로 세분화. 코드 예제는 거의 모든 페이지에 TypeScript ↔ Compiled JS 비교 탭으로 제공. - -## 3. 마케팅 메시지 - -홈페이지 (`docs/index.mdx:99-110`): -``` -Only one line required, with pure TypeScript type -Runtime validator: 20,000x faster than class-validator -JSON serialization: 200x faster than class-transformer -LLM function calling harness: 6.75% → 100% accuracy -``` - -비교 대상: class-validator + @nestjs/swagger (Triple Duplicated DTO 비판), ajv, TypeBox, class-transformer. - -## 4. "Pure TypeScript" 사상 (인용) - -`docs/pure.mdx:12-24`: - -> typia needs only one line with pure TypeScript type. You know what? Every other validator libraries need extra schema definition, that is different with pure TypeScript type. -> -> Those duplicated schema definitions are not only annoying, but also error-prone. If you take any mistake on the extra schema definition, such mistake can't be detected by TypeScript compiler. - -핵심 차이: AOT(Ahead of Time) 컴파일 — 타입을 분석해 컴파일 타임에 검증/직렬화 코드 emit. 현재 공개 setup 문서의 기본 경로는 `typia setup` → `@typescript/native-preview` + `@typia/ttsc` + `typia/lib/ttsc/plugin` 이다. 다만 브라우저 playground/compiler 쪽에는 아직 `typia/lib/transform` compatibility entry가 남아 있다. - -## 5. 기능 문서 품질 - -| 기능 | 코드 예제 | 깊이 | -|---|---|---| -| Validators | 매우 풍부 (TS+JS) | ★★★★ | -| JSON | 풍부 | ★★★★ | -| LLM | 풍부 | ★★★★ | -| Protobuf | 있음 | ★★★ | -| Random | 있음 | ★★★ | -| Tags | 매우 풍부 (30+ 태그 카탈로그) | ★★★★★ | - -특별히 우수: `pure.mdx` 3-탭 비교(class-validator vs ajv vs typia) + 컴파일된 JS 800줄, `setup.mdx` 8가지 환경별 가이드. - -## 6. 블로그 (11개) - -성능: "15,000x faster TypeScript Validator", "10x faster JSON stringify", "Express faster than Fastify", "V8 hidden class", ... -기술 심화: "Protocol Buffer of TypeScript", "Bun is up to 20x slower than Node.js" -생태계: "Good-bye and thanks to typescript-is", **"Function Calling Harness: 6.75% → 100%" (2026-03-26, AutoBe + Qwen)** - -## 7. Playground 구현 - -`website/src/compiler/index.ts` — Web Worker: -1. `EmbedTypeScript` (브라우저용 TS 컴파일러) -2. `typia/lib/transform` compatibility entry (browser worker) -3. `tgrid` WorkerServer (RPC) -4. `RollupBundler` (esm.sh CDN) - -3 모드: `compile / transform / bundle`. +setup 문서는 다음 current lane 을 설명해야 한다. -기본 스크립트: -```ts -const member: IMember = typia.random(); -const check = typia.is(member); -const json = typia.json.stringify(member); -const binary = typia.protobuf.encode(member); +```bash +npx typia setup +# installs @typescript/native-preview, @typia/ttsc, @typia/ttsx +# writes compilerOptions.plugins += [{ transform: "typia/lib/transform" }] +# removes legacy ts-patch settings ``` -## 8. 약점 / 개선 여지 +## playground boundary -| 항목 | 현황 | 개선안 | -|---|---|---| -| 벤치마크 | 외부 GitHub | 사이트 내 인터랙티브 차트 | -| 마이그레이션 가이드 | 없음 (class-validator → typia 등) | 단계별 변환 가이드 | -| 고급 패턴 | 태그 사용법 위주 | 커스텀 포매터/검증기 확장 예제 | -| TypeDoc | 외부 링크 | 사이트 내장 | -| 에러 메시지 카탈로그 | 일반론 | 실제 메시지 + 해결법 | -| Playground 공유 | URL 공유 미지원 | gist/pastebin 통합 | -| LLM 다중 공급자 | OpenAI 위주 | Anthropic/Google/Groq/Ollama 예제 | +website playground 는 browser/static-hosting 제약이 있다. 현재 native host lane 과 완전히 같은 실행 환경으로 쓰지 않는다. -## 9. 주요 페이지 +문서에서는 다음을 분리한다. -| 파일 | 책임 | -|---|---| -| `src/content/index.mdx` | 홈, HomeHeroMovie / HomeCompilationMovie | -| `src/content/docs/pure.mdx` | 핵심 철학, 3-라이브러리 비교, 컴파일 시각화 | -| `src/content/docs/setup.mdx` | 현재 toolchain 설정 (`typia setup`, `@typia/ttsc`, bundler guide, NX caveat) | -| `src/content/docs/validators/{is,assert,validate,tags}.mdx` | 검증 핵심 | -| `src/content/docs/llm/application.mdx` | `typia.llm.application()`, ILlmApplication 구조 | -| `src/content/docs/random.mdx` | 타입 분석 기반 랜덤, IRandomGenerator 커스텀 | -| `src/content/docs/utilization/nestjs.mdx` | Nestia 통합, @TypedBody/@TypedRoute | -| `src/app/(docs)/playground/page.tsx` | Playground 진입 | -| `src/compiler/index.ts` | Worker 진입점 | -| `src/components/playground/SourceEditor.tsx` | Monaco 래퍼 | -| `rspack.config.js` | 브라우저용 TS 컴파일러 번들 | +| lane | 의미 | +| --- | --- | +| native/default | `@typescript/native-preview` + `@typia/ttsc` + Go native backend | +| runner | `@typia/ttsx` | +| bundler | `@typia/unplugin` | +| browser playground | website compatibility lane | -## 핵심 통찰 +## 유지할 것 -웹사이트는 **이론(사상) + 실용(playground) 균형**이 매우 우수. "Pure TypeScript" 메시지가 일관되게 강조됨. 다음 단계로 (a) 사이트 내 벤치마크 인터랙티브, (b) class-validator/zod 마이그레이션 가이드, (c) Anthropic/Gemini 등 LLM 공급자별 워크플로 문서, (d) playground 공유 URL이 효과 큼. +- setup 문서와 실제 setup wizard 동작 동기화 +- playground 를 native host parity 증거처럼 과장하지 않기 +- browser lane 과 native lane 의 차이 명시 diff --git a/wiki/05-research/02-competitors.md b/wiki/05-research/02-competitors.md index 8cd9fe5c40b..d078032879f 100644 --- a/wiki/05-research/02-competitors.md +++ b/wiki/05-research/02-competitors.md @@ -103,6 +103,6 @@ typia의 **객관적 속도 우위**는 여전히 견고하다. 그러나 2025~2026 생태계의 진짜 변동축은 (a) Standard Schema 표준화, (b) MCP/AI SDK가 Zod를 사실상 표준으로 굳힌 점, (c) tsgo 전환의 그림자다. typia가 다음 1~2년에 잃지 않으려면: 1. **Standard Schema 어댑터를 first-class로 출시** (1주일 작업, 영향 큼) -2. **Setup 마찰 제거** — `typia init` 한 줄로 ts-patch+plugin+tsconfig 자동, unplugin이 기본 권장 +2. **Setup 마찰 제거** — `typia setup` 한 줄로 preview/stable lane, plugin 주입, bundler 대체 경로, optional runner 안내까지 정리 3. **AutoBE/Agentica 같은 type-first AI 워크플로 사례를 표면에 노출** — "vibe coding 시대의 typia" 메시지 4. **벤치 시계열 사이트** (matrix.typia.io 식) — 신뢰의 정량 표시 diff --git a/wiki/05-research/03-tsgo-status.md b/wiki/05-research/03-tsgo-status.md index a3e14448f9e..bdc738d56c6 100644 --- a/wiki/05-research/03-tsgo-status.md +++ b/wiki/05-research/03-tsgo-status.md @@ -5,6 +5,7 @@ ## 1. 프로젝트 현황 [사실] + - 2025-03 Anders Hejlsberg가 "Corsa" 발표 (TypeScript의 Go 포팅). 빌드 7~10×, 메모리 -50%, 에디터 시작 8× 목표. - 2026-03-23 **TypeScript 6.0** 정식 — "마지막 JS 기반 버전". - **TypeScript 7.0 (Corsa) GA: 공식 미정** — Microsoft DevBlog (2025-12 Progress report)는 날짜 명시 없음. InfoWorld 등 3rd-party 추정 "2026 mid/late", 일부는 "2026-01-15 GA" 주장하나 **공식 확인 불가**. 안전 범위 **2026 H2 ~ 2027 H1** (v2 재조사). @@ -29,6 +30,7 @@ ## 2. 호환성 정책 — 중대 비호환 선언 [사실] + - "**TypeScript 7.0은 기존 Strada API를 지원하지 않음**" (DevBlog 공식). - Discussion #455 (Daniel Rosenwasser, 2025-03-11): "API 소비자는 동일 프로세스 내에서 통신하지 않을 것. **IPC 레이어를 거치는 메시지 패싱**." - Issue #2824 (andrewbranch, 2026-02-18): TS 6.x 의 server plugin 직접 로드는 Go 구현에서 불가능. 대체는 **IPC 기반 API + TypeScript client library**. @@ -41,6 +43,7 @@ ## 3. Transformer Plugin 지원 — typia에 가장 결정적 [사실] + - Issue #516 "Transformer Plugin or Compiler API" (2025-03-12, M-jerez 개설) — **milestone "Post-7.0"**, 여전히 Open. Microsoft 공식 답변 없음. - Discussion #455 jakebailey: "We love ts-morph; it is explicitly an anti-goal to prevent ts-morph from working altogether" — 타입 정보는 IPC로 노출. - 그러나 **custom transformer / ts-patch / typia 같은 internal patching 기반 도구는 "redesigned approaches" 필요** — 공식 인정. @@ -53,6 +56,7 @@ ## 4. ts.TypeChecker / Internal API 호환성 [사실] + - 현재 IPC API (PR #711)는 `getSymbolAtLocation`, `getTypeOfSymbol` 정도만 노출. typia가 쓰는 `getTypeAtLocation`, `getPropertiesOfType`, `getIndexInfoOfType`, `getApparentType`, `getAugmentedPropertiesOfType` 등 **대부분 미노출**. - tsgo 패키지 대부분이 `internal/`로 선언 → JS 레이어에서 ts-expose-internals에 해당하는 것 자체가 **존재하지 않음**. - Discussion #455의 IPC 설계는 "curated API" 명시 → 전체 checker API 노출 계획 없음. @@ -62,6 +66,7 @@ ## 5. AST 호환성 [사실] + - AST는 `internal/ast/ast.go`에 Go struct. **SyntaxKind enum 값은 유지 방향**이지만 Node는 Go struct → JS 측은 IPC 직렬화 형태로만 접근. - PR #711에서 "binary encoded AST" 포맷 언급 — JS 쪽 AST 생성 → tsgo 주입 실험 중 (`DocumentStore`). - CHANGES.md에 TS 6.0 대비 의도적 비호환 리스트 존재. @@ -71,6 +76,7 @@ ## 6. Microsoft 공식 마이그레이션 경로 [사실] + - TS 6.x 병행 유지. Strada(JS) 코드베이스는 7.x 성숙까지 LTS. - 7.0은 에디터 기본값 아닌 **opt-in VS Code 확장**으로 먼저. - `@typescript/native-preview` → 7.0 GA → 점진적 기본 전환. @@ -80,34 +86,38 @@ ## 6.5. typia/ttsc 에 대한 직접 함의 [사실] + - `typescript-go` 를 외부 패키지가 **Go module import** 형태로 재사용하는 것은 공식 방향이 아니다. - Microsoft가 열고 있는 경계는 **CLI / VS Code extension / IPC API / TypeScript client** 쪽이다. - public Go API 는 2025-03 Discussion #481에서 **"unlikely side"** 로 답변되었다. +- `@typia/ttsc` 의 현재 plugin contract 에도 JS-side AST hook 은 없다. JS plugin 은 text-output post-processing 까지만 stack 가능하고, type analysis / AST mutation / native rewrite 는 native backend responsibility 로 둔다. [결론] + - typia 계열 도구의 **최종 설치 계약**은 `@typescript/native-preview`/향후 `typescript@7` + `@typia/ttsc` 의 **side-by-side** 가 자연스럽다. - 다만 2026-04 현재 공식 API가 아직 불안정하므로, 내부 shim·bridge 같은 **한 차례 우회 구현** 은 현실적으로 필요하다. +- 기존 TypeScript transformer API 를 그대로 지원하는 범용 `ttsc` 는 현실 목표에서 제외한다. TypeScript v7 lane 은 native plugin 또는 IR bridge 로 설계하고, legacy transformer 는 TS v5/v6 또는 구버전 typia lane 에 남긴다. ## 7. 커뮤니티 라이브러리 대응 -| 프로젝트 | 2026-04 현황 | -|---|---| -| ts-patch (nonara) | Issue #181 (2025-03-11) 개설만, **maintainer 응답 없음**, 로드맵 미공개 | -| **typia (samchon)** | Issue #1534 (2025-03-13) 개설, **공식 플랜 없음** | -| ts-morph (dsherret) | Microsoft "anti-goal to break ts-morph" — IPC 타입 정보 보장 | -| Effect-TS | `Effect-TS/tsgo` 포크 (LSP 커스터마이징 실험) | -| ttypescript | 사실상 유지보수 중단, ts-patch가 후계 | -| Vue/Vite/Angular | Issue #516에서 우려 표명, 개별 대응 미공개 | +| 프로젝트 | 2026-04 현황 | +| ------------------- | ----------------------------------------------------------------------- | +| ts-patch (nonara) | Issue #181 (2025-03-11) 개설만, **maintainer 응답 없음**, 로드맵 미공개 | +| **typia (samchon)** | Issue #1534 (2025-03-13) 개설, **공식 플랜 없음** | +| ts-morph (dsherret) | Microsoft "anti-goal to break ts-morph" — IPC 타입 정보 보장 | +| Effect-TS | `Effect-TS/tsgo` 포크 (LSP 커스터마이징 실험) | +| ttypescript | 사실상 유지보수 중단, ts-patch가 후계 | +| Vue/Vite/Angular | Issue #516에서 우려 표명, 개별 대응 미공개 | ## 8. 타임라인 추정 -| 이벤트 | 시점 | 신뢰도 | -|---|---|---| -| TS 7.0 GA (컴파일러 중심) | 2026 Q3~Q4 | 높음 | -| Editor 기본 전환 | 2026 말~2027 | 중 | -| IPC API 1.0 stable | **2027 H1 이후** | 추정 | -| **Transformer/Plugin 공식 지원** | **빨라야 2027 하반기** | 추정 | -| ts-patch/typia 대응 완료 | 2027~2028 | 추정 | +| 이벤트 | 시점 | 신뢰도 | +| -------------------------------- | ---------------------- | ------ | +| TS 7.0 GA (컴파일러 중심) | 2026 Q3~Q4 | 높음 | +| Editor 기본 전환 | 2026 말~2027 | 중 | +| IPC API 1.0 stable | **2027 H1 이후** | 추정 | +| **Transformer/Plugin 공식 지원** | **빨라야 2027 하반기** | 추정 | +| ts-patch/typia 대응 완료 | 2027~2028 | 추정 | ## 인용 diff --git a/wiki/06-feedback/00-README.md b/wiki/06-feedback/00-README.md index dea7009cd9e..40c9506d76f 100644 --- a/wiki/06-feedback/00-README.md +++ b/wiki/06-feedback/00-README.md @@ -1,16 +1,12 @@ -# 06. 피드백 (★ 핵심 산출물) +# 06. Feedback -강점, 약점, 개선 과제를 한곳에 모은 섹션이다. +현재 구현 기준의 강점, 약점, 후속 과제만 적는다. ## 읽기 순서 -1. [01-strengths.md](01-strengths.md) — 정직한 강점 11가지 (비판 전 칭찬) -2. [02-weaknesses.md](02-weaknesses.md) — 정직한 약점 13가지 (우선순위 매트릭스 포함) -3. [03-improvement-proposals.md](03-improvement-proposals.md) — 실행 가능한 액션 아이템 14 -4. [04-philosophy-critique.md](04-philosophy-critique.md) — 사상 자체의 빈틈 8가지 - -## 우선 과제 - -1. Standard Schema 노출과 확장 -2. tsgo 대응 toolchain 정리 -3. setup와 문서 단순화 +1. [01-strengths.md](01-strengths.md) +2. [02-weaknesses.md](02-weaknesses.md) +3. [03-improvement-proposals.md](03-improvement-proposals.md) +4. [04-philosophy-critique.md](04-philosophy-critique.md) +5. [05-ttsc-ttsx-follow-ups.md](05-ttsc-ttsx-follow-ups.md) +6. [06-ttsc-cli-parity.md](06-ttsc-cli-parity.md) diff --git a/wiki/06-feedback/01-strengths.md b/wiki/06-feedback/01-strengths.md index 5881d8dee53..100f7567dd9 100644 --- a/wiki/06-feedback/01-strengths.md +++ b/wiki/06-feedback/01-strengths.md @@ -1,117 +1,29 @@ -# 01. 강점 — typia가 정말 잘하는 것들 +# 01. Strengths -> ⚠️ [09-audit/03-cycle3-feedback-honesty.md](../09-audit/03-cycle3-feedback-honesty.md) 감수 결과 반영: -> - **S1 "사상의 정직함"**: AI의 미덕 투사일 수 있음. 아래 설명은 그 경계를 인정함. -> - **S2 "객관적 우위"**: 특정 시점 머신 벤치이며, Zod v4 JIT 등 경쟁 이후 재측정 필요. [05-research/](../05-research/)의 수치 참조. -> - **S10 vs [04-philosophy-critique.md](04-philosophy-critique.md) 비판 8**: "일관된 비전"(강점)과 "bus factor"(약점)는 **같은 현상의 양면**이다. 프레이밍 편향 주의. -> - **누락 강점**: **nestia 생태계 통합** (실제 채택 드라이버의 상당수)과 **AI agent-time 적응성** (AutoBE 사례) 은 더 부각되어야 함. +## S1. single source of truth -피드백을 시작하기 전에, typia가 잘하는 것을 정리한다. 아래 각 항목은 **마케팅 수사가 아닌 관찰**이며, 감수 결과와 조화를 위해 원문 유지. +TypeScript 타입 하나에서 validator, JSON, LLM, protobuf, random 산출물이 나온다. -## S1. 사상의 정직함 +## S2. native backend path -이 항목이 가장 위에 와야 한다. 다른 라이브러리들이 "DX와 성능 둘 다"를 외치며 절충안을 만들 때, typia는 **"Pure TypeScript 하나"**라는 한 명제에 모든 것을 정렬했다. 이 정직함이 typia의 가장 큰 자산이다. +현재 변환은 Go native backend 로 이행했다. -증거: -- 마케팅 카피와 코드가 일치 (P1~P8 원칙이 코드 전반에 일관됨) -- 새 기능이 들어와도 IR이 흔들리지 않음 (LLM 통합조차 새 Programmer로 흡수) -- 사용자 친화 우회로(예: 별도 스키마 객체 옵션)를 추가하지 않음 — 사상에 충실 +- `packages/core/native` +- `packages/transform/native` +- `toolchain/ttsc` -대부분의 OSS는 1~2년 지나면 사상이 흐려진다. typia는 v1 → v12 동안 흐려지지 않았다. +## S3. clear host / consumer split -## S2. 객관적·정량적 우위 +`@typia/ttsc` 는 host 이고, typia는 consumer 다. `typia/lib/transform` 은 consumer plugin entry 다. -벤치마크 (i9-13900HX, v5.3.4): +## S4. toolchain sibling -| 지표 | typia | 차순위 | -|---|---|---| -| 단순 객체 validate | 270K MB/s | typebox 256K | -| 재귀 구조 validate | 20K | — | -| 에러 경로 (validate) | 782 MB/s | typebox 35, zod 109 | -| optimizer 효과 | 287K | typebox 8.45 (33×) | -| stringify | 2,045 MB/s | fast-json-stringify 688, JSON.stringify 124 | +`@typia/ttsx` 는 별도 runner 로 분리되어 있고, `@typia/ttsc` 를 재사용한다. -이 숫자들이 마케팅에 그치지 않는다 — 동일 머신에서 reproducible하게 검증된다 (`benchmark/results/*/`). +## S5. setup cleanup -## S3. 깨끗한 패키지 분리 +`typia setup` 은 현재 필요한 toolchain 을 설치하고 legacy `ts-patch` 설정을 제거한다. -9개 패키지가 0-순환, 4-layer 구조. [02-architecture/03-package-graph.md](../02-architecture/03-package-graph.md): +## S6. public API continuity -- `interface` (0-dep) -- `utils` -- `core` -- `transform / typia / unplugin / mcp / langchain / vercel` - -이 분리 덕에: -- LLM SDK 어댑터를 추가할 때 core는 불변 -- 0-dep `interface`가 모든 패키지의 ABI 역할 -- 사용자가 필요한 패키지만 install - -## S4. 자체 IR 보유 (MetadataSchema) - -`packages/core/src/schemas/metadata/MetadataSchema.ts:49-135`이 typia의 진짜 표준. **TypeScript Compiler API가 바뀌어도 IR 위 코드는 살아남는다**. - -이는 미래 대비의 가장 강력한 자산. tsgo 시대에 'TypeChecker만 다시 만들면 나머지는 그대로'라는 분리가 완성되어 있다. - -## S5. ts.* public API만 사용 - -`ts-expose-internals` devDependency는 declare만 — 실제 사용 거의 없음. 모든 AST 생성은 `ts.factory.*`. 호환성 표면이 최소화되어 있다. - -→ TypeScript 4.8 ~ 5.9 동안 거의 깨지지 않은 이유. - -## S6. 에러 메시지의 정확성 - -다른 라이브러리가 흉내 내기 어려운 부분. - -```js -{ - path: "$input.user.address[0].zipCode", - expected: "string & Format<\"postal-code\">", - value: 12345 -} -``` - -이 path는 컴파일 타임에 합성된다 (`AssertProgrammer.ts:56-84`). 런타임 reflection 없이 정확. 이 한 가지만으로도 typia를 쓸 이유가 된다 (특히 폼/API 입력 검증). - -## S7. 한 라이브러리에서 다영역 커버 - -검증·직렬화·JSON 스키마·OpenAPI·Protobuf·LLM 함수 호출·랜덤·HTTP 디코딩·notation 변환을 **한 IR에서 모두**. - -``` -한 interface 작성 → typia.is + typia.json.stringify + typia.json.schema - + typia.protobuf.encode + typia.llm.application + typia.random - + typia.http.queryObject ... -``` - -비교 라이브러리 (zod/valibot)는 검증만, typebox는 검증+JSON Schema, BAML은 LLM만. 다영역 통합은 typia 고유. - -## S8. LLM 함수 호출의 깊은 통합 - -ILlmFunction의 `parse / coerce / validate` 3종 메소드가 LLM 협상 프로토콜로 작동. AutoBE의 "6.75% → 100%" 정확도 사례가 이 위에서 가능. - -다른 라이브러리는 "스키마 주고 검증"이지만, typia는 **"스키마 + 복구 + 피드백 루프"**까지 제공. - -## S9. 활발한 문서 + Playground - -- typia.io 사이트의 "Pure TypeScript" 사상 페이지가 명확 -- 인-브라우저 playground가 즉시 실험 가능 -- 11개 블로그 포스트가 깊이 있는 설명 (15,000× 검증, V8 hidden class, Bun 비교 등) -- TypeDoc API 문서 - -웬만한 OSS의 두 배 수준의 문서량. - -## S10. 활성도 + 신뢰성 - -- 5.7k stars, 2026-04 활발 (master에 매주 PR/release) -- v12.0.2까지 - major bump가 사상 변경 아니라 점진적 개선 -- 한 사람(samchon)이 6년 가까이 단일 비전 유지 — OSS에서 보기 드문 일관성 -- nestia/AutoBE 같은 주변 OSS와 시너지 - -## S11. 상업적 백업 - -`AutoBE / Agentica`처럼 typia를 핵심 기술로 쓰는 상업/연구 프로젝트들이 있다. 이는: -- typia의 사용 사례가 실증됨 -- typia 개발 동기가 마르지 않음 (사용자 피드백이 직접 들어옴) -- 단순한 OSS 취미가 아닌 직업적 약속 - -이 11가지가 typia의 객관적 위치다. 이제 [02-weaknesses.md](02-weaknesses.md)에서 같은 정직함으로 약점을 본다. +사용자 API 는 계속 `typia.is()`, `typia.assert()`, `typia.json.*`, `typia.llm.*` 중심이다. diff --git a/wiki/06-feedback/02-weaknesses.md b/wiki/06-feedback/02-weaknesses.md index 6f8cd508f77..dc9b45ba2ee 100644 --- a/wiki/06-feedback/02-weaknesses.md +++ b/wiki/06-feedback/02-weaknesses.md @@ -1,169 +1,45 @@ -# 02. 약점·위험요소 — 정직한 진단 +# 02. Weaknesses -[01-strengths.md](01-strengths.md)와 짝이 되는 문서다. 목적은 위험을 미리 표면화하고, 개선 우선순위를 분명히 하는 것이다. +## W1. setup cost -## W1. tsgo 종속 위험 (생존 차원) +typia는 runtime-only library 가 아니다. 사용자는 compiler host 를 이해해야 한다. -**가장 심각**. [05-research/03-tsgo-status.md](../05-research/03-tsgo-status.md): -- TypeScript 7.0 (Corsa) GA: 2026 mid/late -- **기존 Strada API 미지원** (DevBlog 공식 명시) -- in-process plugin 불가 — IPC만 -- Issue #516 (Transformer Plugin) **milestone "Post-7.0"**, Microsoft 공식 답변 없음 -- typia Issue #1534 (2025-03-13) **공식 플랜 없음** -- ts-patch Issue #181 **maintainer 응답 없음** +현재 setup 은 자동화되었지만, 사용자는 여전히 다음 경로를 구분해야 한다. -**핵심 위험**: TS 7.0이 stable 되고 사용자가 이주하기 시작하면 typia는 (지금 형태로는) 새 사용자를 받을 수 없다. +- `ttsc` +- `ttsx` +- `@typia/unplugin` +- browser/static-hosting compatibility lane -**핵심 판단**: 단순 어댑터 교체가 아니라 **18~24개월급 Go 재구현**이 필요하다. +## W2. `ttsc` parity gap -→ 최신 단일 진실원: [08-tsgo-master-plan/](../08-tsgo-master-plan/). +현재 `ttsc` 는 full `tsc` clone 이 아니다. -## W2. Standard Schema 부분 구현 / 미홍보 (생태계 차원) +- project reference build +- `--showConfig` +- `--init` +- TS7 parallel flags +- full help surface -현재는 이미 부분 구현되어 있다. -- `packages/typia/src/internal/_createStandardSchema.ts` -- `typia.createValidate()` / `createValidateEquals()` 의 `~standard` -- transformer / programmer 경로의 `standardSchema` 옵션 배선 +## W3. plugin contract gap -**시급**. [05-research/02-competitors.md](../05-research/02-competitors.md): -- Zod, Valibot, ArkType, Effect Schema, TypeBox 모두 `~standard` 인터페이스 구현 -- MCP TS SDK 2025-11-25부터 Standard Schema 수용 -- Next.js Server Actions, Hono, Drizzle 모두 수용 +현재 public plugin contract 는 작다. -typia는 **이미 부분 구현 완료**이나: -- 공식 문서/마케팅 부재 — 외부에는 "typia는 Standard Schema 미지원" 인식 강함 -- `createValidate` / `createValidateEquals`만 지원 — `createIs` / `createAssert` 등 다른 factory는 확장 여지 -- MCP / AI SDK / LangChain / Hono 통합 예제 부족 +- native backend 하나 중심 +- `transformOutput()` text hook +- diagnostics / assets / phase model 은 좁음 -→ **문서화·홍보 + 나머지 factory 확장**이 핵심이다. +## W4. `ttsx` execution split -## W3. Setup 마찰 (신규 사용자 차원) +CJS 와 ESM 실행 모델이 다르다. -이 약점은 typia의 사상이 가진 **구조적 비용**이다. +- CJS: require hook +- ESM: cached project build + child process -신규 사용자가 typia를 시도하려면: -1. `npx typia setup` 또는 수동으로 `@typescript/native-preview` + `@typia/ttsc` + `typia/lib/ttsc/plugin` 배선 -2. 번들러 환경이면 `@typia/unplugin` 과 기본 `ttsc` 경로 중 어느 쪽을 쓸지 결정 -3. IDE TypeScript Service가 transformer 결과를 직접 보여주지 않음 → 에러 메시지가 빌드 시에만 보이기 쉬움 -4. `ts-node` / `tsx` 류 실행이 필요하면 `@typia/ttsx` 같은 runner를 추가로 이해해야 함 +## W5. website/browser lane split -대조: zod는 `npm i zod`로 끝. +native host lane 과 browser playground lane 이 같은 구현이 아니다. 문서에서 둘을 계속 분리해야 한다. -**완화 방향**: -- unplugin을 1급 시민으로 권장 (vite/webpack/rspack 사용자) -- `npx typia init` 한 줄로 100% 자동화 (이미 setup 있지만 더 매끄럽게) -- "왜 transformer가 필요한가"를 setup 페이지 첫 문단에서 인정 + 그 비용으로 무엇을 얻는지 즉시 보여줌 +## W6. downstream second consumer 미검증 -## W4. 거대 파일 (유지보수 차원) - -`packages/core/src` 내부: -- `programmers/internal/CheckerProgrammer.ts` — **1614 LOC** -- `programmers/RandomProgrammer.ts` — ~1200 LOC -- `programmers/json/JsonStringifyProgrammer.ts` — 1129 LOC -- `factories/internal/metadata/iterate_metadata_intersection.ts` — 211 LOC -- `factories/internal/metadata/emplace_metadata_object.ts` — 225 LOC - -크기 자체보다 **단일 파일에 여러 책임이 혼재**한 점이 부담: -- CheckerProgrammer: 원자 처리 + 조합기 + 조인어 모두 -- 알고리즘 핵심부 주석 거의 없음 (특히 union/intersection 처리) -- 신규 컨트리뷰터가 진입하기 어려운 곳 - -**제안**: 책임별로 파일 분리, 각 함수에 알고리즘 의도 주석 한 줄, 복잡한 분기에 ASCII 트리 주석 등. - -## W5. 회귀 테스트 부재 (품질 차원) - -`tests/test-typia-automated`가 168×40 조합으로 폭은 넓지만: -- **버전 간 회귀를 자동 잡는 fixture/snapshot 없음** -- 벤치마크는 머신별 폴더만, **시계열 추적 없음** -- LLM 통합(@typia/mcp/langchain/vercel)은 환경변수 의존 → CI에서 실제 LLM 콜 안 함 -- TypeScript 마이너 버전 매트릭스 빌드 없음 (5.5/5.7/5.9 동시) - -**위험**: 미래 어느 PR이 silent regression을 만들면 사용자가 issue 열기 전까지 모름. - -**제안**: -- 핵심 기능별 emit 코드의 snapshot test -- 매주 main 머지 후 벤치 1줄 추가하는 시계열 -- TS 매트릭스 CI (5.5/5.7/5.9/native-preview) -- LLM 통합은 mock LLM 응답으로 결정적 테스트 - -## W6. LLM 모델 분기 매뉴얼 - -`ILlmSchema`의 `IConfig.strict`와 모델별 호환 모드는 OpenAI strict 변경, Anthropic prompt caching, Gemini schema 진화에 따라 매번 손으로 추적해야 함. - -**위험**: 한 모델 SDK가 breaking change를 하면 typia 사용자가 침묵 실패할 수 있음. - -**제안**: -- 각 LLM 공급자별 호환성 페이지 (실제 함수 호출 결과 캡처) -- 매 분기 1회 호환성 smoke test (각 공급자 실제 호출) -- LLM 호환 모드는 별도 모듈로 분리 (`@typia/llm-openai`, `@typia/llm-anthropic` 등) - -## W7. 어댑터 패키지의 SDK private API 의존 - -`packages/mcp/src/internal/McpControllerRegistrar.ts:101-190` Preserve 모드는 MCP SDK의 `private _registeredTools / _toolHandlersInitialized`에 접근. - -**위험**: SDK 마이너 업그레이드로 private 명명이 바뀌면 즉시 깨짐. - -**제안**: -- private API 사용 부분에 명확한 주석 + SDK 버전 범위 -- `private API not available` fallback 경로 -- CI 매주 latest SDK로 smoke test - -## W8. JSON Schema OpenAPI 3.2 미지원 - -`@typia/interface/openapi/`는 V3.0 / V3.1 / V3.2 / SwaggerV2 모델을 들고 있지만 V3.2는 아직 정식 지원 부족 (Emended 모델로 대체). - -**위험**: OpenAPI 3.2가 본격 채택되면 변환 갭 발생. - -**제안**: V3.2 변환 정식 추가, 차이점 문서화. - -## W9. Browser/Edge 런타임 지원 정보 부족 - -typia는 emit 코드만 남아 사실상 어디서나 동작하지만, **공식 호환성 매트릭스가 명시되지 않음**: -- Cloudflare Workers, Vercel Edge, Deno, Bun, Node 22+ -- 각 런타임에서 typia 헬퍼(`_isFormatEmail` 등)가 동작하는지 - -**제안**: setup 페이지에 호환성 표 추가. - -## W10. 마이그레이션 가이드 부재 - -class-validator → typia, zod → typia 마이그레이션 가이드가 없다. 신규 채택자가 가장 먼저 부딪히는 질문. - -**제안**: `class-validator-to-typia.mdx`, `zod-to-typia.mdx` 페이지 작성. 같은 스키마를 양쪽으로 작성한 비교 + 변환 자동화 도구(가능하다면). - -## W11. 사용자 정의 Format 확장 비용 - -`tags.Format<"my-custom-format">` 같은 사용자 정의 포맷을 추가하려면 typia 자체를 패치해야 함. 사용자가 brand 타입과 별도 검증 함수로 우회는 가능하지만 typia native 통합은 불가. - -**제안**: 외부에서 `registerFormat("custom-name", regex | function)` 같은 API. 어렵지만 영향 큼. - -## W12. SSR/RSC 시대 생태계 트렌드 추적 - -Next.js Server Actions, RSC, Tanstack Start 등이 빠르게 자리 잡는 중. typia는 이들과의 통합이 명시적이지 않음 (작동은 함). - -**제안**: utilization 섹션에 Next.js Server Action / RSC / Tanstack 통합 가이드. - -## W13. 마케팅의 "속도" 강조가 사상을 가린다 - -홈페이지 첫 인상이 "20,000× faster"인데, typia의 진짜 차별점은 **사상의 단일성**이다. 속도는 결과일 뿐. - -**제안**: 첫 인상 메시지를 "Type once. Validate, serialize, schema-ify, LLM-ify, randomize. All from one TypeScript type."로 바꾸고, 속도는 **증거**로 한 단계 아래에 놓기. (이미 pure.mdx는 이 방향이지만 홈페이지 위쪽이 약함) - -## 약점 13개의 우선순위 - -| # | 우선순위 | 영향 | 비용 | -|---|---|---|---| -| W2 Standard Schema 문서화·확장 | **최고** | 생태계 진입 | ~~1주~~ → **문서 1주 + 확장 2-3주** (부분 구현됨) | -| W1 tsgo 대응 | **최고** | 생존 | 6개월~1년 | -| W3 setup 마찰 | 높 | 신규 채택 | 1~2주 (자동화 + 문서) | -| W10 마이그레이션 가이드 | 높 | 채택 | 2주 (3개 가이드) | -| W5 회귀 테스트 | 높 | 품질 | 1~2주 | -| W13 메시지 정렬 | 중 | 인지 | 며칠 | -| W11 Format 확장 | 중 | DX | 1개월 | -| W12 RSC/Server Action 통합 | 중 | 트렌드 | 1주 | -| W4 거대 파일 리팩토링 | 중 | 컨트리뷰션 | 1~2개월 | -| W9 Edge 호환성 표 | 중 | 신뢰 | 며칠 | -| W6 LLM 모델 호환 자동화 | 중 | 품질 | 1개월 | -| W7 SDK private API | 낮 | 격리됨 | 며칠 | -| W8 OpenAPI 3.2 | 낮 | 미래 | 1개월 | - -→ 다음 [03. 구체적 개선 제안](03-improvement-proposals.md)에서 위 항목 각각에 대한 실행 가능한 액션 아이템. +typia는 첫 consumer 다. nestia 같은 second consumer 로 generic host contract 를 검증해야 한다. diff --git a/wiki/06-feedback/03-improvement-proposals.md b/wiki/06-feedback/03-improvement-proposals.md index 8c63ec30a9d..e51a36d5875 100644 --- a/wiki/06-feedback/03-improvement-proposals.md +++ b/wiki/06-feedback/03-improvement-proposals.md @@ -1,308 +1,40 @@ -# 03. 구체적 개선 제안 — Action Items 모음 +# 03. Improvement Proposals -[02-weaknesses.md](02-weaknesses.md)의 13개 약점을 **실행 가능한 작업 단위**로 변환. 각 항목에 (우선순위 / 작업량 / 영향) 표기. +현재 구현 기준의 작업만 적는다. ---- +## A1. `ttsc` diagnostics 정리 -## A1. Standard Schema 문서화·확장 — **최우선** +- structured diagnostics payload +- watch mode diagnostic UX +- plugin diagnostics boundary -**우선 / 문서화 후 확장 / 영향 매우 큼** +## A2. `ttsc` CLI parity 확대 -### 무엇을 -이미 구현된 Standard Schema 지원을 공식 노출하고, 다른 factory 함수까지 확장한다. +- project reference build +- `--showConfig` +- `--init` +- TS7 parallel flags -**이미 구현된 것**: -- `typia.createValidate()` → `~standard` 자동 주입 -- `typia.createValidateEquals()` → 동일 -- path 파서 (`typiaPathToStandardSchemaPath`) 내장 +## A3. native plugin composition -**확장할 것**: -- `typia.createIs()` — Standard Schema는 validate 함수가 필수이므로 createIs 위에 createValidate 논리를 wrapping -- `typia.createAssert()` 마찬가지 +- 여러 native backend composition +- rewrite ownership +- asset output ownership -### 왜 -- Zod·Valibot·ArkType·Effect Schema·TypeBox가 모두 구현 -- MCP TS SDK / Next.js Server Actions / Hono / Drizzle이 수용 -- typia가 Zod 자리에 그대로 들어갈 수 있게 됨 +## A4. `ttsx` runner hardening -### 어떻게 -```ts -// 새 패키지 또는 typia에 추가 -const validate = typia.createValidate(); -validate["~standard"] = { - version: 1, - vendor: "typia", - validate: (value) => { - const result = validate(value); - return result.success - ? { value: result.data } - : { issues: result.errors.map(e => ({ message: ..., path: e.path.split(".") })) }; - }, - types: { input: null as Member, output: null as Member }, -}; -``` +- sourcemap +- cache invalidation +- CJS/ESM behavior documentation +- preload behavior -이상적으로는 `@typia/transform`이 createValidate 호출을 변환할 때 자동으로 `~standard` 속성을 같이 emit. +## A5. setup experiment 유지 -### 검증 -- MCP TS SDK 1.x latest로 typia validator를 tool inputSchema로 등록 → 동작 확인 -- Vercel AI SDK `tool({ inputSchema: typiaValidator })` 동작 확인 -- Next.js Server Action 입력 검증 +- npm tarball install +- package.json cleanup +- tsconfig mutation +- failed setup side-effect guard ---- +## A6. second consumer -## A2. 홈페이지 메시지 정렬 — **빠른 win** - -**중 / 며칠 / 영향 큼 (인지)** - -### 무엇을 -홈페이지의 첫 인상을 "속도"에서 "사상"으로 옮김. - -### Before -> Runtime validator is 20,000x faster than class-validator - -### After -> **Type once. Validate, serialize, schema-ify, LLM-ify, randomize.** -> One TypeScript type. Zero runtime cost. Every output you need. -> -> *(20,000× faster than class-validator — that's the side effect.)* - -### 왜 -- 속도는 증거지 본질이 아님 — 본질은 "타입 한 번 = 모든 산출물" -- 다른 라이브러리들이 속도 따라잡기 시작하면 (Zod v4 JIT 등) 차별점이 사라짐 -- "Pure TypeScript" 사상이 typia의 영구적 해자(moat) - ---- - -## A3. Setup 마찰 완화 — **높** - -**높 / 1~2주 / 영향 큼** - -### 무엇을 -1. `npx typia setup` 경로를 더 짧고 명확하게 정리 -2. 신규 사용자 가이드 첫 문단에 "왜 transformer가 필요한가"를 솔직히 설명 + 그 비용으로 얻는 것을 즉시 시각화 -3. `vite-create`/`create-next-app` template (typia 옵션 포함) - -### 왜 -신규 채택의 가장 큰 장벽이 setup. zod는 `npm i zod`로 끝인데 typia는 여전히 compiler host / bundler path / optional runner를 이해해야 한다. - -### 어떻게 -- `typia setup`이 패키지 매니저 감지 + 의존 설치 + tsconfig plugin 주입 + legacy 설정 정리 + IDE 설정(.vscode/extensions.json 권장) 을 한 번에 -- "Why do I need a transformer?" FAQ 페이지 (3분 분량) -- 별도 starter template (typia-starter-vite, typia-starter-nextjs, typia-starter-nestjs) - ---- - -## A4. 마이그레이션 가이드 3개 — **높** - -**높 / 2주 / 영향 큼** - -### 무엇을 -`docs/migrations/`에 다음 3개 페이지: -1. `class-validator → typia` — DTO 클래스 변환, decorator → tags 매핑 -2. `zod → typia` — schema → interface, z.infer → 직접 사용 -3. `joi → typia` - -각 페이지는: -- 같은 스키마를 양쪽으로 보여줌 -- 전환 시 발생하는 일반 함정 (예: zod transform → typia에서는 별도 함수) -- 부분 전환 가능성 (혼용) -- 자동 변환 도구 (가능하면 codemod) - -### 왜 -신규 채택자의 첫 질문이 항상 "기존 Z를 어떻게 typia로 바꾸나". 답을 미리 준비. - ---- - -## A5. 회귀 테스트 + 시계열 벤치 — **높** - -**높 / 1~2주 / 영향 큼 (품질)** - -### 무엇을 -1. 핵심 기능 emit 코드의 **snapshot test** (jest snapshot) -2. 매주 main 머지 후 GitHub Action이 **벤치 1줄을 history.csv에 추가** -3. 전환면 매트릭스 CI (`@typia/ttsc` package-local test + `@typia/test-typia-ttsc` smoke + current native-preview baseline) -4. LLM 통합 mock test (실제 LLM 호출 없이 결정적 응답) - -### 왜 -- 현재 자동 테스트는 **regression depth 높지만 over-time visibility 없음** -- 미래 PR의 silent regression 방지 - -### 어떻게 -``` -.github/workflows/regression.yml -.github/workflows/benchmark-history.yml -benchmark/history.csv (typia 버전, 핵심 지표 5~10개) -benchmark/charts/ (시계열 SVG 자동 갱신) -``` - -→ typia.io에 "Performance over time" 인터랙티브 차트 페이지 - ---- - -## A6. unplugin 대체 경로 강화 — **중~높** - -**중 / 1주 / 영향 큼 (tsgo 대응의 일부)** - -### 무엇을 -1. setup 페이지에 vite/Next.js/rspack 사용자에게 `@typia/unplugin` 을 bundler-native 대체 경로로 명시 -2. 기본 경로는 여전히 `@typia/ttsc` 라고 분명히 적기 -3. `@typia/unplugin` 이 필요한 환경과 필요 없는 환경을 표로 분리 - -### 왜 -- `ttsc` 가 현재 기본 계약이어도, 번들러 사용자는 `unplugin` 경로를 더 자연스럽게 받아들인다 -- `unplugin` 은 빌드 도구의 transform 훅을 쓰므로 별도 host를 직접 다루지 않아도 된다 -- "기본 경로" 와 "bundler-native 대체 경로" 를 분리해야 setup 문서가 덜 헷갈린다 - ---- - -## A7. RSC / Server Action / Tanstack 통합 가이드 — **중** - -**중 / 1주 / 영향 중간 (트렌드)** - -### 무엇을 -`docs/utilization/` 추가: -- `nextjs-server-action.mdx` — typia + Server Action 입력 검증 -- `nextjs-rsc.mdx` — RSC props 검증 -- `tanstack-start.mdx` -- `hono.mdx` (sValidator 통합) -- `cloudflare-workers.mdx` (Edge 런타임 호환성) - -### 왜 -- 2025~2026 빠르게 자리 잡는 패러다임 -- 현재 utilization은 NestJS/tRPC/MCP/Vercel/LangChain만 - ---- - -## A8. 거대 파일 리팩토링 — **중** - -**중 / 1~2개월 / 영향 중 (컨트리뷰션 + 유지보수)** - -### 무엇을 -- `CheckerProgrammer.ts` (1614 LOC) → 원자 / 조합기 / 조인어 / utility 4개 파일 -- `RandomProgrammer.ts` (1200 LOC) → 타입별 generator 분리 -- `JsonStringifyProgrammer.ts` (1129 LOC) → core / property / array / dynamic 분리 -- `iterate_metadata_intersection.ts` (211 LOC) → 핵심부 ASCII 트리 주석 - -### 왜 -- 신규 컨트리뷰터 진입 장벽 -- LLM 코드 어시스턴트가 컨텍스트 윈도우에 못 담음 -- 단위 테스트 가능성 ↑ - -### 위험 -- 리팩토링 도중 동작 변경 위험 → snapshot test 먼저 (A5와 묶음) - ---- - -## A9. LLM 모델 호환성 자동 검증 — **중** - -**중 / 1개월 / 영향 큼 (품질)** - -### 무엇을 -1. 각 공급자별 호환성 페이지 + 실제 함수 호출 결과 캡처 -2. 매 분기 1회 호환성 smoke test (OpenAI / Anthropic / Google / Llama 실제 호출) -3. 호환 모드를 분리 모듈로 (`@typia/llm-openai-strict`, `@typia/llm-anthropic`, ...) - -### 왜 -- 현재 `IConfig.strict` 한 플래그가 모든 차이를 흡수 → 디버깅 어려움 -- 한 공급자 SDK가 breaking change를 하면 침묵 실패 - ---- - -## A10. SDK 어댑터 안정화 — **낮~중** - -**낮 / 며칠 / 영향 작음 (격리됨)** - -### 무엇을 -- `mcp/internal/McpControllerRegistrar.ts:101-190` Preserve 모드의 private API 사용 부분에 SDK 버전 범위 명시 + fallback 경로 -- 각 어댑터에 SDK latest CI smoke test (매주) - ---- - -## A11. OpenAPI 3.2 정식 지원 — **낮** - -**낮 / 1개월 / 영향 작음 (미래)** - -### 무엇을 -`packages/interface/openapi/OpenApiV3_2.ts` 모델 추가 + `OpenApiConverter` 변환. - ---- - -## A12. Format 사용자 확장 — **중** - -**중 / 1개월 / 영향 큼 (DX)** - -### 무엇을 -```ts -// typia.config.ts (TypeScript 설정 파일) -export default { - formats: { - "my-custom": /^X-[0-9]+$/, - "my-validator": (value) => isValid(value), - }, -}; -``` -typia transformer가 이 설정을 읽어 사용자 정의 Format을 코드 emit에 포함. - -### 왜 -사용자가 `tags.Format<"my-custom">`를 native하게 쓸 수 있음. - -### 어려움 -transformer config 로딩 메커니즘 추가, brand 타입 typing 확장 (declare module). - ---- - -## A13. Edge 런타임 호환성 매트릭스 — **낮** - -**낮 / 며칠 / 영향 작음 (신뢰)** - -### 무엇을 -setup 페이지에 호환성 표 추가: - -| 런타임 | typia core | LLM 어댑터 | 비고 | -|---|---|---|---| -| Node 18+ | ✅ | ✅ | 표준 | -| Node 22 | ✅ | ✅ | 권장 | -| Bun 1.x | ✅ | ✅ | | -| Deno 2.x | ✅ | ⚠️ | LLM SDK 호환성 확인 필요 | -| Cloudflare Workers | ✅ | ✅ | | -| Vercel Edge | ✅ | ⚠️ | | -| Browser | ✅ | ❌ | LLM SDK는 보통 서버용 | - -각 행마다 actual smoke test가 CI에 있어야 신뢰 가능. - ---- - -## A14. tsgo 대응 (별도 문서로) — **최우선** - -→ [07-strategy/01-tsgo-strategy.md](../07-strategy/01-tsgo-strategy.md) 에서 단계별 시나리오와 함께. - ---- - -## 우선순위 매트릭스 (실행 순서) - -``` -즉시 (0~2주): - A1 Standard Schema ★★★★★ - A2 메시지 정렬 ★★★★ - A3 setup 자동화 1단계 ★★★★ - -단기 (1~3개월): - A4 마이그레이션 가이드 3개 - A5 회귀 + 시계열 벤치 - A6 unplugin 1급 시민 - A10 SDK 어댑터 안정화 - A13 Edge 호환성 표 - -중기 (3~6개월): - A7 RSC/Server Action 가이드 - A9 LLM 호환 자동화 - A12 Format 사용자 확장 - -장기 (6개월~1년): - A8 거대 파일 리팩토링 - A11 OpenAPI 3.2 - A14 tsgo 대응 (전 기간 병행) -``` - -→ 다음 [04. 사상에 대한 비판적 회고](04-philosophy-critique.md) +nestia 같은 typia 외 consumer 로 `@typia/ttsc` contract 를 검증한다. diff --git a/wiki/06-feedback/04-philosophy-critique.md b/wiki/06-feedback/04-philosophy-critique.md index 3a7d5c844e3..e8f63f00552 100644 --- a/wiki/06-feedback/04-philosophy-critique.md +++ b/wiki/06-feedback/04-philosophy-critique.md @@ -1,115 +1,23 @@ -# 04. 사상에 대한 비판적 회고 — typia 사상의 빈틈 +# 04. Philosophy Critique -이 절은 [01-philosophy/](../01-philosophy/)에서 칭찬한 사상에 대한 **건강한 비판**이다. 사상을 사랑할수록 그 빈틈도 정직하게 봐야 한다. +현재 기준의 비판만 남긴다. -## 비판 1. "Pure TypeScript"의 자기참조 — TS의 한계가 곧 typia의 한계 +## C1. compiler host cost -typia의 명제는 "TS 타입 = 진실"이다. 그러나 TS 타입 시스템이 모델링 못 하는 영역이 있다: +TypeScript 타입을 단일 진실원으로 삼는 대신 compiler host 가 필요하다. -- **단위(unit) 시스템**: `Meter`와 `Foot`이 모두 `number`. typia는 `tags.Type<"int32">`까지는 가지만 `tags.Unit<"meter">`는 만들 수 있어도 dimensional analysis는 못 함 -- **시간적 invariant**: "이 객체는 N초 후 만료"같은 시점 의존 검증 -- **상태 머신**: type-state pattern은 TS로 표현 가능하지만 typia는 검증 시점에 그 상태가 어디인지 모름 -- **다중 객체 간 invariant**: "user.id == post.authorId" 같은 cross-entity 제약 +## C2. dynamic schema 한계 -이 빈틈은 TS의 빈틈이고, typia가 채울 수 없다. **사상의 일관성을 위해 풀지 못하는 문제 클래스가 있다는 사실을 인정**할 필요가 있다. +compile time 에 타입이 있어야 한다. runtime 에 스키마가 바뀌는 시스템에는 맞지 않는다. -→ 대안: typia가 "validate 후 추가 비즈니스 검증을 짧고 명확하게 쓰는 패턴"을 가이드. validate는 typia, business invariant는 사용자 함수. +## C3. TypeScript boundary -## 비판 2. "타입 한 번"의 배신 — JSDoc과 brand의 공존 +다른 언어가 원천 schema 인 시스템에서는 typia의 type-first 모델을 바로 적용하기 어렵다. -typia는 같은 의미를 두 가지 표현으로 받는다: +## C4. native backend parity risk -```ts -interface A { - /** @minimum 0 @maximum 150 */ - age: number; -} -// vs -interface B { - age: number & tags.Minimum<0> & tags.Maximum<150>; -} -``` +Go native backend 가 legacy TypeScript transformer 와 완전히 같은 결과를 내야 한다. 이 parity 가 핵심 위험이다. -이는 사용자 친화이지만 **사상의 순수성**을 약간 깬다 — "타입 한 번"이라는 명제에서 "타입의 한 가지 표현"이 둘이 됐다. 팀마다 컨벤션이 갈리면 코드베이스 안에서 두 스타일이 섞일 수 있다. +## C5. public contract 선택 -→ 정직한 인정: 두 표현 모두 TS 타입 시스템 안에 있어 typia 사상의 본질을 깨지 않는다. 그러나 **공식적으로 "권장 스타일"을 한쪽으로 정해 가이드**할 가치가 있다 (개인적으로는 brand가 더 IDE 친화). - -## 비판 3. 자체 IR이 가져오는 변경 비용 - -`MetadataSchema`가 typia의 진짜 표준이라는 강점은 동시에 약점이다: -- 이 IR을 바꾸면 모든 Programmer가 영향받음 -- 새 표준(예: JSON Schema 2020-12 newer features)을 표현하려면 IR 확장이 필요 -- IR 자체가 **typia 내부 ABI** — semver-major가 자주 발생할 위험 - -→ 완화: IR 변경 정책을 명시 (semver-major만, deprecation 1버전 유지 등). 외부에는 안정 ABI를 유지. - -## 비판 4. "0 외부 런타임"의 과장 - -typia의 마케팅 중 하나가 "0 외부 런타임 의존"이다. 그러나 사실은: -- `_isFormatEmail`, `_isFormatUuid`, `TypeGuardError`, `_ProtobufWriter` 등 **타입에 따라 외부 헬퍼 import** -- emit된 코드가 typia 라이브러리를 import하지 않는다는 의미일 뿐 -- bundle size를 진짜 0으로 보면 안 됨 - -→ 정직한 표현: "최소한의 타입별 헬퍼만 import (수 KB)" 가 정확. 그래도 zod/valibot 대비 **여전히 작다**는 비교가 사실에 맞음. - -## 비판 5. tsgo 대응 — 사상의 시험대 - -이 부분은 사상의 진짜 시험대다. 두 갈래의 길: - -**길 A (사상 고수)**: tsgo의 IPC API를 받아들여 새 transformer 어댑터를 만든다. 사상 변경 없음. 시간이 오래 걸리고 작업량 큼. 그 동안 신규 사용자는 TS 6.x에 잔류. - -**길 B (사상 일부 양보)**: post-compile codegen 모델 — 사용자가 `interface`를 작성하고 `typia generate` 명령으로 별도 파일에 검증 함수를 emit. 빌드 통합 마찰 ↓, 사상 일부 양보 (코드가 두 파일). - -길 B가 신규 사용자에게 더 친근하지만 사상의 핵심("한 줄 사용")을 약간 깬다. **이 결정이 향후 1~2년 typia의 정체성을 결정**한다. - -→ 개인 의견: 길 A를 메인으로 하되, **길 B를 보조 모드**로 함께 제공. "transformer 못 쓰는 환경"에서도 typia를 쓰도록. 이는 사상 양보가 아니라 **사상의 적용 범위 확장**. - -## 비판 6. 사상의 외부 노출 부족 - -typia 코드와 문서에는 사상이 흠뻑 배어있지만, **외부 커뮤니티는 typia를 "빠른 zod 대안"으로만 인식**하는 경우가 많다. (벤치마크 차트가 가장 자주 인용됨) - -원인: -- 홈페이지 첫 인상이 속도 → 사상 인지가 늦음 -- "Pure TypeScript" 사상 페이지가 docs/pure에 묻혀 있음 (홈에서 1 클릭 거리지만 노출 약함) -- 컨퍼런스 발표 / 팟캐스트 / 블로그가 부족 (samchon님 본인 블로그 외) - -→ "사상 자체"를 외부에 마케팅. 발표·인터뷰·게스트 포스트. - -## 비판 7. AI 시대의 사상 재정렬 - -LLM/agentic 시대에 typia의 사상이 진화해야 할 부분: - -- **Type-first가 vibe coding과 자연스럽게 맞물린다**는 메시지 — Cursor/Claude Code가 interface만 쓰면 typia가 검증·LLM·랜덤 다 해결 -- AutoBE/Agentica 사례를 typia 자체 마케팅에 통합 — 현재는 별도 프로젝트로 분리되어 typia가 가진 진짜 경쟁점이 묻힘 -- "AI가 코드를 쓰는 시대의 검증 라이브러리는 type-first여야 한다"는 thesis를 만들 수 있음 - -→ 사상의 새 챕터: "Pure TypeScript in the Age of AI Code Generation". - -## 비판 8. 컨트리뷰션 진입 장벽 - -typia의 가치는 사상의 일관성에서 오지만, 그 일관성이 **한 사람(samchon)의 비전**에 강하게 의존한다. 이는: - -- 강점: 비전 흐려지지 않음 -- 약점: bus factor 낮음, 외부 컨트리뷰션이 사상 동기화에 시간 걸림 - -→ 보완: -- 사상을 코드로 명시 (이 wiki 같은 문서가 그 시도) -- "사상 review checklist" — PR이 사상에 맞는지 자동 점검 가이드 -- 두 번째 핵심 컨트리뷰터 키우기 (멘토링) - -## 종합 — 사상의 진짜 강함은 비판을 받아도 흔들리지 않는 것 - -위 8개 비판은 모두 "사상이 틀렸다"가 아니라 **"사상의 적용에 빈틈이 있다"** 이다. typia 사상 자체는 견고하다. 문제는 사상의 외연(사용 범위, 표현, 마케팅, 기술 환경 변화 대응)이다. - -가장 중요한 결론: -> **"사상을 양보하지 않으면서 사상의 적용 범위를 넓히는 것"** — 이게 typia의 다음 1~2년 핵심 과제. - -구체적으로: -1. Standard Schema 어댑터 (사상 양보 0, 적용 범위 +50%) -2. unplugin 1급화 (사상 양보 0, 채택 마찰 ↓) -3. tsgo 대응 (사상 양보 0, 미래 생존) -4. AI 시대 사상 재정렬 (사상 확장, 새 메시지) - -이 네 가지가 잘 풀리면 typia는 다음 5년도 안전하다. 풀리지 않으면 사상은 살아있어도 사용자가 옮겨간다 — OSS의 가장 슬픈 시나리오. - -→ 다음 [07-strategy/](../07-strategy/) 에서 tsgo 대응이 가장 시급하므로 별도 chapter로. +`ttsc` plugin API 를 너무 넓히면 host 가 무거워지고, 너무 좁히면 second consumer 가 붙기 어렵다. diff --git a/wiki/06-feedback/05-ttsc-ttsx-follow-ups.md b/wiki/06-feedback/05-ttsc-ttsx-follow-ups.md new file mode 100644 index 00000000000..03045d1439a --- /dev/null +++ b/wiki/06-feedback/05-ttsc-ttsx-follow-ups.md @@ -0,0 +1,60 @@ +# 05. ttsc / ttsx Follow-ups + +현재 구현 기준의 남은 일만 적는다. + +## F1. native plugin composition + +현재 `ttsc` 는 한 invocation 에서 하나의 native mode / binary 조합을 중심으로 돈다. + +필요한 정리: + +- 여러 native consumer 를 순차 실행할 수 있는지 +- 같은 파일을 여러 plugin 이 rewrite 할 때 ownership 을 어떻게 나눌지 +- diagnostics 와 asset output 을 plugin 별로 어떻게 분리할지 + +## F2. diagnostics API + +현재 CLI 는 stderr 와 exit code 로 실패를 드러낸다. JS API 는 `build/check` 에서 `{ status, stdout, stderr }` 를 돌려주고, `transform` 은 실패 시 throw 한다. + +필요한 정리: + +- structured diagnostics callback +- plugin diagnostics payload +- watch mode diagnostic UX + +## F3. `ttsx` CJS / ESM 차이 + +현재: + +- CJS: require hook + per-file transform +- ESM: project build + cache emit + child Node + +필요한 정리: + +- sourcemap +- preload behavior +- cache invalidation +- debugger UX + +## F4. CLI parity + +현재 사용자-facing 기본은 `ttsc`, `ttsc -p`, `ttsc --noEmit`, `ttsc --watch` 다. + +아직 좁은 부분: + +- project reference build +- `--showConfig` +- `--init` +- TS7 parallel flags +- full `tsc --help --all` + +## F5. release + +`toolchain/*` 는 first-class publish 대상이다. + +확인할 것: + +- `@typia/ttsc` native binary 포함 +- `@typia/ttsx` launcher 포함 +- publish 순서: toolchain 후 consumer packages +- npm tarball dry-run diff --git a/wiki/06-feedback/06-ttsc-cli-parity.md b/wiki/06-feedback/06-ttsc-cli-parity.md new file mode 100644 index 00000000000..e9ed892dc4b --- /dev/null +++ b/wiki/06-feedback/06-ttsc-cli-parity.md @@ -0,0 +1,51 @@ +# 06. ttsc CLI Parity + +현재 공개 CLI 기준만 적는다. + +## primary commands + +```bash +ttsc +ttsc -p tsconfig.json +ttsc --noEmit +ttsc --watch +ttsc --version +ttsc --help +``` + +의미: + +- `ttsc`: 현재 project build +- `ttsc -p tsconfig.json`: 지정 project build +- `ttsc --noEmit`: check lane +- `ttsc --watch`: JS host watch loop + +## compatibility / extension commands + +```bash +ttsc build +ttsc check +ttsc transform --file=src/index.ts +ttsc demo --type=string +``` + +- `build`: `ttsc` 와 같은 build lane alias +- `check`: `--noEmit` alias +- `transform`: bundler adapter 용 per-file API +- `demo`: native backend smoke 용 + +## current gaps vs full `tsc` + +- project references build parity +- `--ignoreConfig` +- `--showConfig` +- `--init` +- TS7 `--checkers`, `--builders`, `--singleThreaded` +- full help surface + +## error surface + +- missing tsconfig: `ttsc:` prefix error +- invalid tsconfig parse/options: TypeScript-Go diagnostics on stderr +- invalid CLI option: exit code `2` +- build/emit failure: non-zero status and stderr diff --git a/wiki/07-strategy/04-ttsc-design/03-ttsc-vision.md b/wiki/07-strategy/04-ttsc-design/03-ttsc-vision.md index 2d637d42ce1..71ed7eee8d2 100644 --- a/wiki/07-strategy/04-ttsc-design/03-ttsc-vision.md +++ b/wiki/07-strategy/04-ttsc-design/03-ttsc-vision.md @@ -68,7 +68,7 @@ npm run build # tsc가 ts-patch와 함께 작동 ``` ```bash npx typia setup # 내부적으로 ttsc를 install -npx ttsc --build # 또는 npm run build에 ttsc 지정 +npx ttsc # 또는 npm run build에 ttsc 지정 ``` **변한 것**: `tsc` → `ttsc` (또는 `npm run build`가 내부적으로 ttsc 호출). diff --git a/wiki/07-strategy/04-ttsc-design/04-ttsc-architecture.md b/wiki/07-strategy/04-ttsc-design/04-ttsc-architecture.md index cbb04d23926..8cb8b8ffe6d 100644 --- a/wiki/07-strategy/04-ttsc-design/04-ttsc-architecture.md +++ b/wiki/07-strategy/04-ttsc-design/04-ttsc-architecture.md @@ -14,7 +14,7 @@ │ typia.is(input); │ │ tsconfig.json: plugins: [{ transform: "typia/lib/transform" }] │ └──────────────┬───────────────────────────────────────────────────┘ - │ npx ttsc --build + │ npx ttsc ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Layer 3: @ttsc/cli (Node, ~200 LOC) │ diff --git a/wiki/07-strategy/04-ttsc-design/05-ttsc-implementation-plan.md b/wiki/07-strategy/04-ttsc-design/05-ttsc-implementation-plan.md index 0a069630c40..caa39d87efc 100644 --- a/wiki/07-strategy/04-ttsc-design/05-ttsc-implementation-plan.md +++ b/wiki/07-strategy/04-ttsc-design/05-ttsc-implementation-plan.md @@ -68,7 +68,7 @@ Month 10-12 Phase 4: Public beta + typia setup 자동화 **W11-12: @ttsc/cli** - commander 기반 CLI -- `ttsc --build`, `ttsc --watch` (watch는 stub) +- `ttsc`, `ttsc --watch` (watch는 stub) - tsconfig 파싱 + 바이너리 spawn - platform detection (@ttsc-{platform}-{arch} resolve) @@ -130,7 +130,7 @@ Month 10-12 Phase 4: Public beta + typia setup 자동화 **W31-32**: - `ttsc --noEmit` 검증 모드 -- `ttsc --build` project references +- `ttsc` project references ### Month 9. 배포 인프라 diff --git a/wiki/08-tsgo-master-plan/00-README.md b/wiki/08-tsgo-master-plan/00-README.md index fafa68f6b81..4a61e2975ba 100644 --- a/wiki/08-tsgo-master-plan/00-README.md +++ b/wiki/08-tsgo-master-plan/00-README.md @@ -1,22 +1,33 @@ # 08. tsgo Master Plan -이 폴더는 `ttsc` / `ttsx` 를 **처음부터 standalone general-purpose product** 로 만드는 계획만 다룬다. +이 폴더는 현재 구현 기준의 `ttsc` / `ttsx` 계약만 적는다. -핵심 전제: +## 현재 결론 -1. `ttsc` 는 standalone compiler adapter / plugin host 다. -2. `ttsx` 는 standalone runner 다. -3. typia는 `ttsc` / `ttsx` 위에 올라가는 첫 consumer 다. -4. TS plugin / Go plugin / mixed plugin 은 모두 1급이다. -5. 계획의 출발선은 standalone repo/package 다. +- `ttsc` 는 standalone compiler adapter / plugin host 다. +- `ttsx` 는 `ttsc` host 를 재사용하는 standalone runner 다. +- typia는 첫 consumer 다. +- TypeScript v7 native lane 에서 legacy `ts.Program` transformer 호환을 재현하지 않는다. +- plugin contract 는 native backend, emitted text, diagnostics, asset output 중심이다. -읽는 순서: +## 현재 코드 위치 + +| 영역 | 위치 | +| --- | --- | +| `ttsc` JS API / CLI | `toolchain/ttsc/src`, `toolchain/ttsc/cmd/ttsc` | +| `ttsx` runner | `toolchain/ttsx/src` | +| typia plugin entry | `packages/typia/src/transform.ts` | +| typia native backend | `packages/core/native`, `packages/transform/native` | +| setup wizard | `packages/typia/src/executable/TypiaSetupWizard.ts` | + +## 읽는 순서 1. [01. Principles](01-principles.md) 2. [02. Products](02-products.md) 3. [03. Plugin Contract](03-plugin-contract.md) -4. [04. typia Consumer Plan](04-typia-consumer.md) -5. [05. Stage 0 Kickoff](05-stage0-kickoff.md) -6. [06. Roadmap](06-roadmap.md) +4. [04. typia Consumer](04-typia-consumer.md) +5. [05. Current Outcome](05-stage0-kickoff.md) +6. [06. Current Gaps](06-roadmap.md) 7. [07. Open Questions](07-open-questions.md) -8. [08. Current Spike](08-current-spike.md) +8. [08. Product State](08-current-spike.md) +9. [09. References](09-references.md) diff --git a/wiki/08-tsgo-master-plan/01-principles.md b/wiki/08-tsgo-master-plan/01-principles.md index 0ae2fa8b99e..7786fea364e 100644 --- a/wiki/08-tsgo-master-plan/01-principles.md +++ b/wiki/08-tsgo-master-plan/01-principles.md @@ -1,22 +1,25 @@ # 01. Principles -## 하드 룰 +## 현재 원칙 -1. `ttsc` 와 `ttsx` 는 처음부터 typia 바깥 product 다. -2. typia는 첫 consumer 이자 integration case 다. -3. public contract 는 TS / Go / mixed 구현을 같은 급으로 수용한다. -4. TS plugin 과 Go plugin 은 같은 급의 공식 경로다. -5. current spike 구현은 standalone product 를 향한 reference evidence 로 읽는다. -6. packaging, naming, repo boundary 도 day-one 부터 standalone 기준으로 잡는다. +1. `ttsc` 와 `ttsx` 는 typia 전용 부속물이 아니다. +2. typia는 첫 consumer 이며, `typia/lib/transform` 으로 native backend 를 선언한다. +3. TypeScript v7 lane 은 Go native backend 와 JS host API 로 구성한다. +4. legacy TypeScript transformer object 호환은 현재 목표가 아니다. +5. 공개 계약은 작게 유지한다. -## 공식 표현 +## 공개 계약 -- "`ttsc` 는 standalone host 다" -- "`ttsx` 는 standalone runner 다" -- "`typia는 첫 consumer 다`" -- "`TS plugin / Go plugin / mixed plugin 은 모두 공식 경로다`" -- "`분리는 출발 조건이다`" +- `@typia/ttsc.build()` +- `@typia/ttsc.check()` +- `@typia/ttsc.transform()` +- `@typia/ttsc.definePlugin()` +- `@typia/ttsx` CLI / `register()` / `prepareExecution()` +- plugin `native.mode`, `native.binary`, `contractVersion: 1` -## 한 줄 결론 +## 비공개 구현 -> **분리는 미래 단계가 아니라 출발 조건이다.** +- `typescript-go` internal struct pointer +- `go:linkname` shim detail +- typia metadata 분석 세부 구조 +- emitted JS rewrite 내부 알고리즘 diff --git a/wiki/08-tsgo-master-plan/02-products.md b/wiki/08-tsgo-master-plan/02-products.md index 464a8bc72ca..9e72e8a7e1d 100644 --- a/wiki/08-tsgo-master-plan/02-products.md +++ b/wiki/08-tsgo-master-plan/02-products.md @@ -1,42 +1,42 @@ # 02. Products -## `ttsc` - -- 역할: standalone compiler adapter / plugin host -- 책임: - - compiler discovery - - project loading - - plugin dispatch - - rewrite / emit orchestration - - diagnostics / cache / watch - -## `ttsx` - -- 역할: standalone runner -- 책임: - - `ttsc` 코어 재사용 - - `ttsx src/index.ts` - - argv / env pass-through - - source map - - cache reuse - -## 설치 모델 - -- preview 기본 경로: - - `npm i typia` - - `npx typia setup` - - 결과: `@typescript/native-preview` + `@typia/ttsc` -- preview 수동 경로: - - `npm i -D @typescript/native-preview @typia/ttsc` - - runner 필요 시 `npm i -D @typia/ttsx` -- stable 예상 경로: - - `npm i typia` - - `npx typia setup` - - 결과: `typescript@7` + `@typia/ttsc` - - runner 필요 시 `npm i -D @typia/ttsx` - -## typia와의 관계 - -- typia는 `@typia/ttsc` 를 기본 toolchain 으로 쓰는 첫 consumer 다. -- `typia setup` 의 기본 설치 대상은 `@typia/ttsc` 이고, `@typia/ttsx` 는 별도 sibling runner package 다. -- typia integration 은 standalone host / runner product identity 위에서 정렬된다. +## `@typia/ttsc` + +역할: TypeScript-Go 기반 compiler adapter / plugin host. + +현재 제공: + +- CLI: `ttsc`, `ttsc -p tsconfig.json`, `ttsc --noEmit`, `ttsc --watch`, `ttsc transform --file=...` +- JS API: `build`, `check`, `transform`, `transformAsync`, `version` +- plugin API: `definePlugin`, `loadProjectPlugins`, `transformOutput` +- project helper: `resolveProjectConfig`, `resolveProjectRoot`, `readProjectConfig` +- native backend 선택: plugin 의 `native.mode` / `native.binary` + +## `@typia/ttsx` + +역할: `ttsc` host 를 재사용하는 runner. + +현재 제공: + +- CLI: `ttsx src/index.ts` +- option: `--project`, `--cwd`, `--cache-dir`, `--binary`, `-r/--require` +- CJS: require hook + per-file `@typia/ttsc.transform()` +- ESM: project build + cache emit + child Node 실행 + +## 설치 + +현재 `typia setup` 결과: + +```bash +npm i -D @typescript/native-preview@latest +npm i -D @typia/ttsc@latest +npm i -D @typia/ttsx@latest +``` + +수동 설치: + +```bash +npm i -D @typescript/native-preview @typia/ttsc @typia/ttsx +``` + +`@typia/ttsx` 는 `@typia/ttsc` 에 의존하지만, setup wizard 는 runner 사용을 바로 가능하게 하려고 둘 다 설치한다. diff --git a/wiki/08-tsgo-master-plan/03-plugin-contract.md b/wiki/08-tsgo-master-plan/03-plugin-contract.md index f4e1995bdb8..6cfe1ee120c 100644 --- a/wiki/08-tsgo-master-plan/03-plugin-contract.md +++ b/wiki/08-tsgo-master-plan/03-plugin-contract.md @@ -1,43 +1,52 @@ # 03. Plugin Contract -## 목표 - -`ttsc` 는 plugin host 여야 하며, plugin author 가 TS / Go / mixed 중 어느 경로를 택해도 같은 contract 위에서 동작해야 한다. - -## host 책임 - -- call-site inventory -- type/context handoff -- diagnostics surface -- rewrite plan application -- emitted asset 관리 - -## plugin 책임 - -- marker call 해석 -- 타입 분석 -- JS emit / asset emit -- plugin-specific diagnostics - -## 언어 정책 - -- TS plugin: 공식 경로 -- Go plugin: 공식 경로 -- mixed plugin: 공식 경로 - -문서는 세 경로를 같은 급의 공식 경로로 서술한다. - -## 공개 경계 - -public contract 구성 요소: - -- serialized request / response -- rewrite plan -- emitted asset description -- diagnostic payload - -implementation-private 예시: - -- TS internal object shape -- Go internal struct layout -- typia-specific helper naming +`ttsc` plugin 은 tsconfig 의 `compilerOptions.plugins[]` 에서 로드된다. + +```json +{ + "compilerOptions": { + "plugins": [{ "transform": "typia/lib/transform" }] + } +} +``` + +## plugin module + +plugin module 은 다음 중 하나를 export 한다. + +- `default` +- `plugin` +- `createTtscPlugin` + +factory 형태: + +```ts +import { definePlugin } from "@typia/ttsc"; + +export default definePlugin((config, context) => ({ + name: "my-plugin", + native: { + mode: "my-plugin", + binary: "/absolute/path/to/backend", + contractVersion: 1, + }, +})); +``` + +## 현재 plugin shape + +| 필드 | 의미 | +| --- | --- | +| `name` | plugin 이름 | +| `native.mode` | native rewrite backend id | +| `native.binary` | consumer native backend launcher | +| `native.contractVersion` | 현재 `1` | +| `native.capabilities` | `"rewrite"`, `"diagnostics"`, `"assets"` 같은 선언 | +| `transformOutput(context)` | emitted JS text 후처리 hook | + +## 현재 제약 + +- 서로 다른 native mode/binary 를 동시에 compose 하지 않는다. +- 여러 `transformOutput()` 은 순서대로 적용된다. +- legacy `ts.Program` / `ts.TypeChecker` / `ts.NodeFactory` 를 JS plugin 에 넘기지 않는다. +- richer diagnostics callback, asset API, phase model 은 아직 public API 가 아니다. diff --git a/wiki/08-tsgo-master-plan/04-typia-consumer.md b/wiki/08-tsgo-master-plan/04-typia-consumer.md index b5cf8a38fe4..09f74384685 100644 --- a/wiki/08-tsgo-master-plan/04-typia-consumer.md +++ b/wiki/08-tsgo-master-plan/04-typia-consumer.md @@ -1,40 +1,30 @@ -# 04. typia Consumer Plan +# 04. typia Consumer -## 위치 +typia는 `ttsc` / `ttsx` 의 첫 consumer 다. -typia는 `ttsc` / `ttsx` 의 consumer 다. +## 현재 경로 -## 의미 +1. 사용자는 `typia.is()`, `typia.json.stringify()` 같은 public API 를 호출한다. +2. `typia setup` 이 `tsconfig.json` 에 `{ "transform": "typia/lib/transform" }` 를 넣는다. +3. `typia/lib/transform` 은 `@typia/ttsc.definePlugin()` 으로 native backend 를 선언한다. +4. `@typia/ttsc` 가 `packages/typia/lib/executable/generate/ttsc.js` 또는 source checkout 의 `src/executable/generate/ttsc.ts` 를 실행한다. +5. 그 launcher 가 `packages/transform/native/cmd/ttsc-typia` 를 실행한다. +6. `packages/transform/native` 가 call site 를 찾고, `packages/core/native` 가 타입 분석과 JS emit 을 수행한다. -- `@typia/core` -- `@typia/transform` -- `typia` -- `@typia/utils` +## setup wizard -이 패키지들은 `ttsc` / `ttsx` platform 위에 올라가는 typia consumer 구현으로 재편된다. +현재 동작: -## 선택지 +- 없으면 `tsconfig.json` 생성 +- `compilerOptions` 가 없으면 생성 +- `compilerOptions.plugins` 가 없으면 배열 생성 +- `plugins` 가 배열이 아니면 실패 +- `typia/lib/transform` 중복 제거 후 하나만 추가 +- `strictNullChecks: true`, `skipLibCheck: true` 보정 +- legacy `prepare` 의 `typia patch` / `ts-patch install` 제거 +- `dependencies.ts-patch`, `devDependencies.ts-patch` 제거 +- `@typescript/native-preview`, `@typia/ttsc`, `@typia/ttsx` 설치 -### typia plugin 구현 +## legacy 경계 -- TS 구현 -- Go 구현 -- mixed 구현 - -세 경로 모두 가능하다. - -## setup - -`npx typia setup` 의 목표는 다음이어야 한다. - -1. `@typia/ttsc` 설치 -2. preview 기간에는 `@typescript/native-preview` 설치 -3. 필요 시 `@typia/ttsx` 설치 -4. legacy `ts-patch` 제거 -5. typia config / tsconfig 정렬 (`typia/lib/ttsc/plugin` 주입) - -## 표현 원칙 - -- typia repo 내부 구조는 typia consumer 구현으로 서술한다. -- typia-specific codegen 구조는 typia plugin 구현으로 서술한다. -- `ttsc` 와 `ttsx` 는 standalone product 로 서술한다. +`@typia/core` / `@typia/transform` TypeScript transformer 패키지는 현재 코드베이스에 없다. legacy transformer 가 필요한 사용자는 해당 기능이 남아 있는 구버전 typia lane 을 사용한다. diff --git a/wiki/08-tsgo-master-plan/05-stage0-kickoff.md b/wiki/08-tsgo-master-plan/05-stage0-kickoff.md index 2a14327fb2f..583c3d805e9 100644 --- a/wiki/08-tsgo-master-plan/05-stage0-kickoff.md +++ b/wiki/08-tsgo-master-plan/05-stage0-kickoff.md @@ -1,34 +1,24 @@ -# 05. Stage 0 Kickoff - -## Week 0 - -1. `ttsc` / `ttsx` standalone repo/package 정체성 확정 -2. npm name / GitHub repo / binary packaging 확인 -3. typia repo 안의 작업은 integration mirror 로만 취급 - -## Week 1 - -1. `ttsc` host skeleton -2. `ttsx` runner skeleton -3. TS plugin 최소 예제 -4. Go plugin 최소 예제 - -## Week 2 - -1. tsgo 연결 -2. rewrite / diagnostics / cache 최소선 -3. `ttsx src/index.ts` 최소선 - -## Week 3 - -1. typia consumer spike -2. setup / migration spike -3. source map / performance 측정 - -## Exit Criteria - -- `ttsc build` 동작 -- `ttsx src/index.ts` 동작 -- TS plugin 예제 동작 -- Go plugin 예제 동작 -- typia consumer 최소 경로 동작 +# 05. Current Outcome + +현재 repo 에서 이미 확인되는 결과만 적는다. + +## 완료 + +- `toolchain/ttsc` package 존재 +- `toolchain/ttsx` package 존재 +- `ttsc` CLI 무인자 build 동작 +- `ttsc -p tsconfig.json`, `ttsc --noEmit`, `ttsc transform --file=...` 경로 존재 +- `ttsx src/index.ts` runner 존재 +- `typia/lib/transform` native plugin entry 존재 +- typia package 는 `@typia/ttsc` 에 의존 +- `typia setup` 은 `@typescript/native-preview`, `@typia/ttsc`, `@typia/ttsx` 를 설치 +- `typia setup` 은 legacy `ts-patch` 설정을 제거 +- native typia backend 는 Go 로 존재 + +## 아직 좁은 부분 + +- native plugin composition 은 한 invocation 안에서 넓지 않다. +- `ttsx` CLI 는 JS API 보다 옵션 표면이 좁다. +- CJS runner 와 ESM runner 는 실행 방식이 다르다. +- `@typescript/native-preview` 기반 preview lane 이다. +- `typescript@7` stable lane 전환은 아직 현재 동작이 아니다. diff --git a/wiki/08-tsgo-master-plan/06-roadmap.md b/wiki/08-tsgo-master-plan/06-roadmap.md index 3b8ecf37533..a9e3dbbcba0 100644 --- a/wiki/08-tsgo-master-plan/06-roadmap.md +++ b/wiki/08-tsgo-master-plan/06-roadmap.md @@ -1,29 +1,29 @@ -# 06. Roadmap +# 06. Current Gaps -## Stage 0 +현재 구현을 기준으로 남은 gap 만 적는다. -- standalone product skeleton -- TS/Go plugin contract 검증 -- typia consumer spike +## `ttsc` -## Stage 1 +- full `tsc` parity 는 아니다. +- project-reference build, `--showConfig`, `--init`, TS7 parallel flags 는 아직 public guide 의 중심이 아니다. +- plugin diagnostics / asset emit / phase model 은 아직 좁다. +- 서로 다른 native backend 여러 개를 한 번에 compose 하는 모델은 없다. -- host stabilization -- runner stabilization -- typia validator path dogfooding +## `ttsx` -## Stage 2 +- CJS 는 in-process require hook 이다. +- ESM 은 build 후 child Node 실행이다. +- CLI option 은 JS API option 보다 적다. +- sourcemap/debugger/cache invalidation UX 는 더 검증해야 한다. -- typia coverage 확대 -- setup 자동화 -- second consumer 탐색 +## typia -## Stage 3 +- native backend 중심으로 동작한다. +- website playground 같은 browser/static-hosting lane 은 별도 compatibility lane 이다. +- legacy TypeScript transformer 경로는 현재 코드베이스의 기본 경로가 아니다. -- second consumer 실검증 -- plugin SDK 정제 -- packaging / release hardening +## release -## 성공 모습 - -`ttsc` / `ttsx` 는 typia 없이도 설명되고, typia는 그 위의 강력한 consumer 로만 설명된다. +- `toolchain/*` 는 `packages/*` 와 같은 first-class publish 대상이어야 한다. +- `@typia/ttsc` publish 후 `typia`, `@typia/ttsx` publish 순서를 맞춰야 한다. +- tarball 에 launcher, `lib`, native binary 포함 여부를 계속 dry-run 으로 확인해야 한다. diff --git a/wiki/08-tsgo-master-plan/07-open-questions.md b/wiki/08-tsgo-master-plan/07-open-questions.md index eb875c49c11..aa36f433436 100644 --- a/wiki/08-tsgo-master-plan/07-open-questions.md +++ b/wiki/08-tsgo-master-plan/07-open-questions.md @@ -1,15 +1,17 @@ # 07. Open Questions -## 아직 열려 있는 것 +닫힌 질문: -1. plugin transport 를 IPC 로 둘지, file contract 로 둘지, hybrid 로 둘지 -2. TS plugin runtime 을 Node child 로 둘지, 공식 JS client 로 둘지 -3. Go plugin ABI 를 어떻게 안정화할지 -4. `ttsx` 의 ESM loader 전략을 어떻게 잡을지 +- `ttsc` 는 standalone host 다. +- `ttsx` 는 standalone runner 다. +- typia는 첫 consumer 다. +- TypeScript v7 lane 에서 legacy transformer object 호환을 목표로 두지 않는다. +- 현재 setup 은 `@typescript/native-preview`, `@typia/ttsc`, `@typia/ttsx` 를 설치한다. -## 이미 확정된 것 +열린 질문: -1. `ttsc` / `ttsx` 의 standalone product 정체성 -2. TS / Go / mixed plugin 의 동등한 공식 지위 -3. typia 의 consumer 위치 -4. `typia setup` 의 기본 설치 계약: preview 기간 `@typescript/native-preview` + `@typia/ttsc`, `@typia/ttsx` 는 별도 optional runner +- native plugin 여러 개를 어떻게 compose 할지 +- diagnostics / assets / phase hook 을 어디까지 public API 로 고정할지 +- `ttsx` CJS/ESM 실행 차이를 어떻게 줄일지 +- `typescript@7` stable lane 전환 시 setup 기본값을 어떻게 바꿀지 +- nestia 같은 second consumer 가 공유할 core API 를 어떤 패키지로 제공할지 diff --git a/wiki/08-tsgo-master-plan/08-current-spike.md b/wiki/08-tsgo-master-plan/08-current-spike.md index 339ce0adcc0..a2c0d288641 100644 --- a/wiki/08-tsgo-master-plan/08-current-spike.md +++ b/wiki/08-tsgo-master-plan/08-current-spike.md @@ -1,13 +1,41 @@ -# 08. Current Spike +# 08. Product State -현재 typia repo 안에 있는 구현의 역할: +이 문서는 더 이상 spike 계획서가 아니다. 현재 repo 상태 요약이다. -- standalone `ttsc` / `ttsx` 를 향한 integration spike -- typia consumer 실험장 -- current Go-heavy path 의 feasibility evidence +## package -문서 해석 기준: +- `@typia/ttsc`: `toolchain/ttsc` +- `@typia/ttsx`: `toolchain/ttsx` +- `typia`: `packages/typia` -- standalone product 중심 -- typia consumer 중심 -- integration spike 중심 +## compile + +``` +typia setup + -> tsconfig plugins += typia/lib/transform + -> install @typescript/native-preview + -> install @typia/ttsc + -> install @typia/ttsx + +ttsc + -> load tsconfig + -> load plugins + -> run TypeScript-Go + -> run native backend when plugin declares it + -> rewrite emitted JS +``` + +## run + +``` +ttsx src/index.ts + -> resolve project + -> reuse @typia/ttsc + -> CJS require hook or ESM cached build +``` + +## invalid config behavior + +- JS project helper reports missing `tsconfig`, missing `extends`, and circular `extends` as `ttsc:` errors. +- native build path parses tsconfig with TypeScript-Go and prints diagnostics to stderr before exiting non-zero. +- setup wizard rejects non-array `compilerOptions.plugins` before installing packages or rewriting `package.json`. diff --git a/wiki/08-tsgo-master-plan/09-references.md b/wiki/08-tsgo-master-plan/09-references.md new file mode 100644 index 00000000000..953229767dd --- /dev/null +++ b/wiki/08-tsgo-master-plan/09-references.md @@ -0,0 +1,79 @@ +# 09. References + +`ttsc` / `ttsx` 와 typia native backend 가 참고한 local reference map 이다. + +## local repositories + +| repo | local path | current relevance | +| --- | --- | --- | +| `microsoft/typescript-go` | `/home/samchon/github/contributions/typescript-go` | compiler, tsconfig parsing, Program, emit, diagnostics, CLI shape | +| `tsgonest/tsgonest` | `/home/samchon/github/contributions/tsgonest` | Go native transform, shim layout, emit-time rewrite, tsgonest-style host structure | +| `oxc-project/tsgolint` | `/home/samchon/github/contributions/tsgolint` | `go:linkname` shim model and `tools/gen_shims` pattern | +| `elliotshpherd/typical` | `/home/samchon/github/contributions/typical` | Go-backed runtime-safety compiler, runner/bundler integration ideas | +| `samchon/ttsc` | `/home/samchon/github/samchon/ttsc` | standalone-package migration target | + +No local `typist` repository was found during this check. The likely intended reference is `typical`. + +## how current code uses the references + +### `typescript-go` + +Current code depends on `github.com/microsoft/typescript-go` and local shim modules. + +Representative locations: + +- `toolchain/ttsc/go.mod` +- `toolchain/ttsc/driver/program.go` +- `toolchain/ttsc/driver/host.go` +- `packages/core/native/go.mod` +- `packages/transform/native/go.mod` + +Current use: + +- parse `tsconfig.json` +- create TypeScript-Go Program and Checker +- read diagnostics +- emit JS +- expose internal packages through shim modules + +### `tsgonest` + +Current code follows the same practical family: + +- OS-backed VFS + bundled lib host +- TypeScript-Go Program facade +- emitted JS rewrite +- Go native analyzer/emitter + +`toolchain/ttsc/driver/host.go` explicitly notes that the helper shape is adapted from tsgonest's compiler host pattern. + +### `tsgolint` + +Current shim generation is adapted from tsgolint. + +Representative locations: + +- `toolchain/ttsc/tools/gen_shims/main.go` +- `toolchain/ttsc/shim/*` + +Current use: + +- re-export selected TypeScript-Go internal APIs +- expose unexported functions through `go:linkname` +- keep native code out of TypeScript-Go source patches where possible + +### `typical` + +Typical is not copied directly into the current implementation. It remains useful as evidence for: + +- Go-backed TypeScript runtime-safety compilation +- thin JS wrapper around native compiler behavior +- runner / bundler integration shapes + +## archived study + +Detailed older prior-art notes remain under: + +`wiki/07-strategy/04-ttsc-design/02-prior-art/` + +Those files are archived. Use this current page as the entry point, then open the archived files when exact historical detail is needed. diff --git a/wiki/09-audit/00-README.md b/wiki/09-audit/00-README.md index 7d8e01bb023..c43bc93d887 100644 --- a/wiki/09-audit/00-README.md +++ b/wiki/09-audit/00-README.md @@ -19,6 +19,7 @@ 7. [07-cycle7-missing-perspectives.md](07-cycle7-missing-perspectives.md) — 누락된 14개 관점 8. [08-cycle8-v2-remeasurement.md](08-cycle8-v2-remeasurement.md) 9. [09-cycle9-stage0-critical-review.md](09-cycle9-stage0-critical-review.md) +10. [10-native-core-transform-port-review.md](10-native-core-transform-port-review.md) — Go native core/transform 포팅 감수와 nestia 재사용성 평가 ## 메모 diff --git a/wiki/09-audit/09-cycle9-stage0-critical-review.md b/wiki/09-audit/09-cycle9-stage0-critical-review.md index a156b2d40fe..7fd74ac3edf 100644 --- a/wiki/09-audit/09-cycle9-stage0-critical-review.md +++ b/wiki/09-audit/09-cycle9-stage0-critical-review.md @@ -72,7 +72,7 @@ - 위 Critical에서 다룸 #### D7. 사용자 설치 UX 상세 미작성 -- `bin/ttsc.js` 내용 템플릿: `require.resolve(`@typia/ttsc-${process.platform}-${process.arch}/bin/ttsc${ext}`)` +- `src/launcher/ttsc.js` 는 추적되는 JS launcher 로 유지하고, native binary 는 optional dependency 또는 local `native/ttsc-native` 에서 찾는다. - Linux musl (Alpine) 호환 (glibc vs musl) - Windows 권한 (executable bit, SmartScreen) - `postinstall` fallback (optional dep 설치 실패 시 안내) diff --git a/wiki/09-audit/10-native-core-transform-port-review.md b/wiki/09-audit/10-native-core-transform-port-review.md new file mode 100644 index 00000000000..7185c75d865 --- /dev/null +++ b/wiki/09-audit/10-native-core-transform-port-review.md @@ -0,0 +1,360 @@ +# 10. Native core/transform port review + +이 문서는 현재 브랜치의 `packages/core/native`, +`packages/transform/native` 구현과 `../typia` master 브랜치의 기존 +TypeScript `packages/core`, `packages/transform` 구현을 비교한 감수 기록이다. + +초점은 두 가지다. + +1. typia의 TypeScript v7 전환 경로에서 Go native 구현이 실제로 어떤 책임을 + 맡는가. +2. 이 구조가 향후 nestia 또는 제3 라이브러리에서 재사용 가능한 모듈 표면으로 + 정리되어 있는가. + +## 결론 + +현재 Go 포팅은 typia 내부 `ttsc` 플러그인 백엔드로는 방향이 맞다. IR은 +`metadata.Schema`로 정리되었고, 분석기는 `analyzer`, 생성기는 `emitter`, +typia 호출 연결은 `transform/native/ttsc`로 나뉘어 있다. 기존 TS 구현에서 +많은 파일을 차지하던 AST factory, per-feature transformer wrapper, import +rewriter, TypeScript AST 조립 코드가 Go 쪽에서는 문자열 emitter와 단일 라우팅 +스위치로 압축되면서 총량이 크게 줄었다. + +단, 이것은 아직 `@typia/core`와 `@typia/transform`의 공용 재사용 표면을 대체한 +상태가 아니다. nestia가 기존에 쓰던 `MetadataFactory`, +`JsonMetadataFactory`, `JsonSchemasProgrammer`, `Http*Programmer`, +`ImportProgrammer`, `TransformerError`, `TypeFactory`, `IdentifierFactory` 같은 +Node/TypeScript API가 Go native 표면으로 제공되지 않는다. 현재 구조는 typia +호출을 찾아 typia runtime 코드를 emit하는 내부 백엔드에 가깝다. + +또한 native Go 모듈의 독립 검증은 현재 깨져 있다. `packages/core/native`는 +`go.mod`의 shim replace 경로가 틀려 analyzer package setup 자체가 실패하고, +emitter 테스트도 현재 출력과 fixture 기대값이 어긋난다. +`packages/transform/native`는 OpenAPI fixture 기반 테스트가 +`target call site not found`로 실패한다. 이 둘은 public reusable core로 보기 +전에 반드시 고쳐야 한다. + +## 확인 범위 + +측정은 git tracked file 기준으로 진행했다. + +| 대상 | 파일 수 | 라인 수 | +| --------------------------------------- | ------: | ------: | +| 현재 `packages/core/native` + transform | 69 | 18,297 | +| `../typia` `packages/core` + transform | 342 | 34,877 | + +사용한 명령: + +```bash +git ls-files packages/core/native packages/transform/native | sort | xargs wc -l +git -C ../typia ls-files packages/core packages/transform | sort | xargs -I{} wc -l ../typia/{} +rg -l "@typia/core" ../nestia/packages ../nestia/tests ../nestia/website | wc -l +cd packages/core/native && go test ./... +cd packages/transform/native && go test ./... +``` + +`../nestia`의 `@typia/core` 직접 사용 파일은 62개다. 이 수치는 단순 +compatibility 위험이 아니라, 실제로 nestia가 `@typia/core`를 public toolkit로 +사용한다는 증거다. + +## 책임 이동 + +| 기존 TS 구현 | 현재 Go 구현 | 판정 | +| --------------------------------------------------------------------------- | ------------------------------------------------- | ---------------------------------------- | +| `@typia/core` npm package | `packages/core/native` Go module | npm public API 대체 표면 없음 | +| `@typia/transform` npm package | `packages/transform/native` Go module | typia 전용 native plugin backend | +| `MetadataSchema` class family | `metadata.Schema`, `Collection`, related structs | IR 형태는 보존 | +| `MetadataFactory.analyze()` | `analyzer.New(...).Walk/WalkWithTypeNode` | 핵심 분석 진입점 포팅 | +| `iterate_metadata_*` family | `analyzer/iterate_metadata_*.go` | 파일 배치와 책임이 비교 가능 | +| `JsonMetadataFactory` | `AnalysisOptions`, `Unsupported*Reason`, emitters | 별도 public factory 표면 없음 | +| `JsonSchemasProgrammer.writeSchemas()` | `emitter.EmitJsonSchemasExpression` | OpenAPI emit은 있으나 TS API 대체 아님 | +| `HttpParameterProgrammer`, `HttpQueryProgrammer` | `emitter/http.go`, adapter unsupported checks | typia emit 경로 중심 | +| `AssertProgrammer`, `IsProgrammer`, `ValidateProgrammer` | `emitter/assert.go`, `is.go`, `diagnostic.go` | source-string emitter로 압축 | +| `ImportProgrammer` | 없음 | nestia 재사용 API 공백 | +| `TypeFactory`, `IdentifierFactory`, `StatementFactory`, `ExpressionFactory` | 없음 | TS AST helper API 공백 | +| `CallExpressionTransformer` functor map | `ttsc.EmitCall` switch | 중앙 스위치로 압축, plugin registry 아님 | +| `FileTransformer` + `ImportTransformer` | `CollectCallSites` + native rewrite command | typia call site 전용 | +| `TransformerError` | `UnsupportedReason`, Go `error` | 구조화된 TS diagnostic API 공백 | + +## 핵심 라인 근거 + +아래 라인들은 이번 판정을 좌우한 직접 근거다. + +| 파일 | 라인 | 의미 | +| --------------------------------------------- | ------: | ------------------------------------------------------------------------------------------------------------------------------- | +| `packages/core/native/analyzer/analyzer.go` | 1-27 | Go analyzer가 TS `MetadataFactory.ts`와 `iterate_metadata_*` 포팅임을 명시하고, 현재 구현 bucket과 증분 대상 bucket을 구분한다. | +| `packages/core/native/analyzer/analyzer.go` | 39-59 | TS `MetadataFactory.IOptions`에 해당하는 analyzer option 표면이다. | +| `packages/core/native/analyzer/analyzer.go` | 96-180 | `Walk`/`WalkWithTypeNode`가 public entry이고 checker type과 syntax fallback을 결합한다. | +| `packages/core/native/metadata/schema.go` | 3-30 | Go IR이 TS `MetadataSchema` bucket 구조를 필드 단위로 유지한다. | +| `packages/core/native/metadata/collection.go` | 3-9 | collection은 registry 역할을 하지만 TS판 object union/recursive index 전체는 아직 확장 대상으로 남아 있다. | +| `packages/core/native/emitter/http.go` | 12-29 | `typia.http.parameter` emit은 sole atomic/literal 중심이며 union/nullable wiring은 단순화되어 있다. | +| `packages/transform/native/ttsc/visit.go` | 11-24 | `CallSite`가 typia native plugin shape임을 명시한다. | +| `packages/transform/native/ttsc/visit.go` | 181-201 | call-site 인식이 `typia/lib`, `typia/src`, `packages/typia/src` 선언 경로에 묶인다. | +| `packages/transform/native/ttsc/adapter.go` | 17-62 | transform adapter가 analyzer를 호출하고 qualified import fallback을 붙인다. | +| `packages/transform/native/ttsc/adapter.go` | 86-205 | method별 syntax/string 기반 unsupported guard다. | +| `packages/transform/native/ttsc/adapter.go` | 471-824 | typia module/method dispatch가 registry가 아니라 큰 switch로 구현되어 있다. | +| `packages/core/native/go.mod` | 5-18 | shim replace 경로가 현재 저장소 구조와 맞지 않는다. | +| `packages/transform/native/go.mod` | 5-20 | transform native 쪽은 `toolchain/ttsc`와 `core/native`를 직접 replace한다. | + +## 현재 native 파일 ledger + +아래 목록은 현재 native 쪽 git tracked 파일 전수 라인 수와 감수 판정이다. + +| 파일 | 라인 | 판정 | +| ---------------------------------------------------------------- | ----: | -------------------------------------------------------- | +| `packages/core/native/analyzer/analyzer.go` | 220 | analyzer entry, TS `MetadataFactory` 포팅 선언과 options | +| `packages/core/native/analyzer/analyzer_test.go` | 36 | smoke 수준 | +| `packages/core/native/analyzer/comment_tags.go` | 426 | JSDoc/tag extraction | +| `packages/core/native/analyzer/iterate_metadata.go` | 82 | dispatcher | +| `packages/core/native/analyzer/iterate_metadata_array.go` | 48 | array bucket | +| `packages/core/native/analyzer/iterate_metadata_atomic.go` | 33 | atomic bucket | +| `packages/core/native/analyzer/iterate_metadata_constant.go` | 48 | literal bucket | +| `packages/core/native/analyzer/iterate_metadata_function.go` | 85 | function bucket | +| `packages/core/native/analyzer/iterate_metadata_intersection.go` | 153 | intersection/tag merge | +| `packages/core/native/analyzer/iterate_metadata_mapset.go` | 51 | map/set path | +| `packages/core/native/analyzer/iterate_metadata_object.go` | 648 | object, property, recursion registry | +| `packages/core/native/analyzer/iterate_metadata_tuple.go` | 37 | tuple bucket | +| `packages/core/native/analyzer/iterate_metadata_union.go` | 127 | union merge | +| `packages/core/native/analyzer/jsdoc.go` | 140 | JSDoc parsing | +| `packages/core/native/analyzer/native.go` | 91 | native object classification | +| `packages/core/native/analyzer/shim_type_string.go` | 134 | checker string access | +| `packages/core/native/analyzer/tag.go` | 432 | typia tag extraction | +| `packages/core/native/analyzer/type_key.go` | 53 | stable type keys | +| `packages/core/native/analyzer/type_node_fallback.go` | 2,073 | syntax fallback, largest analyzer risk area | +| `packages/core/native/emitter/assert.go` | 74 | assert wrapper | +| `packages/core/native/emitter/assert_test.go` | 47 | smoke fixture | +| `packages/core/native/emitter/diagnostic.go` | 1,180 | validation/assert diagnostics | +| `packages/core/native/emitter/format.go` | 7 | formatting helper | +| `packages/core/native/emitter/http.go` | 305 | http decode emit, common but simplified | +| `packages/core/native/emitter/is.go` | 881 | predicate emit, helper hoisting | +| `packages/core/native/emitter/is_test.go` | 196 | stale expected strings now failing | +| `packages/core/native/emitter/json_parse.go` | 95 | parse wrapper | +| `packages/core/native/emitter/json_schema.go` | 830 | JSON Schema/OpenAPI expression | +| `packages/core/native/emitter/json_schema_test.go` | 73 | schema fixture | +| `packages/core/native/emitter/json_stringify.go` | 349 | stringify emit | +| `packages/core/native/emitter/json_stringify_test.go` | 119 | stringify fixture | +| `packages/core/native/emitter/llm.go` | 396 | LLM schema/application emit | +| `packages/core/native/emitter/misc.go` | 351 | clone/prune/literals | +| `packages/core/native/emitter/misc_test.go` | 39 | smoke fixture | +| `packages/core/native/emitter/notations.go` | 39 | notation emit | +| `packages/core/native/emitter/object_dynamic.go` | 143 | dynamic object handling | +| `packages/core/native/emitter/protobuf.go` | 1,260 | protobuf encode/decode/message | +| `packages/core/native/emitter/protobuf_test.go` | 79 | protobuf fixture | +| `packages/core/native/emitter/random.go` | 759 | random emit | +| `packages/core/native/emitter/reflect.go` | 721 | reflection metadata/schema emit | +| `packages/core/native/emitter/schema_inspect.go` | 191 | schema fact extraction | +| `packages/core/native/emitter/tag_compose.go` | 34 | tag composition | +| `packages/core/native/emitter/tags.go` | 321 | tag validation helpers | +| `packages/core/native/emitter/tags_test.go` | 100 | tag fixture | +| `packages/core/native/go.mod` | 34 | replace 경로 오류 | +| `packages/core/native/go.sum` | 22 | module checksum | +| `packages/core/native/metadata/alias.go` | 20 | alias model | +| `packages/core/native/metadata/atomic.go` | 49 | atomic enum | +| `packages/core/native/metadata/collection.go` | 126 | registry, TS collection보다 축소 | +| `packages/core/native/metadata/container.go` | 62 | object/array/tuple refs | +| `packages/core/native/metadata/format.go` | 9 | format enum | +| `packages/core/native/metadata/jslit.go` | 101 | JS literal serialization | +| `packages/core/native/metadata/metadata.go` | 28 | common metadata | +| `packages/core/native/metadata/metadata_test.go` | 285 | metadata fixture | +| `packages/core/native/metadata/name.go` | 191 | name rendering | +| `packages/core/native/metadata/schema.go` | 209 | IR core | +| `packages/core/native/metadata/special.go` | 50 | special refs | +| `packages/core/native/metadata/tag.go` | 33 | tag model | +| `packages/transform/native/cmd/ttsc-typia/build.go` | 208 | native command build orchestration | +| `packages/transform/native/cmd/ttsc-typia/main.go` | 125 | CLI entry | +| `packages/transform/native/cmd/ttsc-typia/transform.go` | 144 | rewrite command path | +| `packages/transform/native/go.mod` | 46 | core/native and ttsc replace path | +| `packages/transform/native/go.sum` | 22 | module checksum | +| `packages/transform/native/ttsc/adapter.go` | 1,824 | typia method routing and unsupported gates | +| `packages/transform/native/ttsc/cleanup.go` | 74 | output cleanup | +| `packages/transform/native/ttsc/openapi_test.go` | 446 | stale fixture test | +| `packages/transform/native/ttsc/qualified_import_fallback.go` | 404 | checker/syntax fallback | +| `packages/transform/native/ttsc/visit.go` | 268 | typia call-site collector | +| `packages/transform/native/ttsc/writefile.go` | 11 | file write helper | + +## 구버전 TS 구조 판정 + +`../typia`의 TS 구현은 라인 수가 긴 이유가 단순 중복 때문만은 아니다. public +toolkit 책임이 넓었다. + +| 구역 | 대표 파일 | 기존 책임 | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| context | `ITypiaContext.ts`, `ITransformOptions.ts`, `TransformerError.ts` | transformer가 공유하는 TS Program, checker, options, diagnostic contract | +| factories | `MetadataFactory.ts`, `JsonMetadataFactory.ts`, `ProtobufFactory.ts` | 타입 분석, JSON/Protobuf 전용 검증, public analyzer API | +| AST helpers | `IdentifierFactory.ts`, `LiteralFactory.ts`, `StatementFactory.ts`, `TypeFactory.ts`, `ExpressionFactory.ts`, `ImportProgrammer.ts` | nestia도 직접 쓰는 TS AST 생성 toolkit | +| metadata schemas | `schemas/metadata/*.ts` | runtime/SDK/schema composer가 공유하는 class 기반 IR | +| generic programmers | `AssertProgrammer.ts`, `IsProgrammer.ts`, `ValidateProgrammer.ts`, `RandomProgrammer.ts` | typia runtime codegen | +| feature programmers | `json`, `http`, `llm`, `misc`, `notations`, `protobuf` | 각 feature별 public programmer API | +| internal checkers | `CheckerProgrammer.ts`, `FeatureProgrammer.ts`, `iterate/*` | predicate, union, object, stringify, schema 세부 생성 | +| transformer entry | `transform.ts`, `FileTransformer.ts`, `CallExpressionTransformer.ts` | TS transformer pipeline | +| transformer wrappers | `features/**/*.ts` | typia API별 generic extraction과 programmer 호출 | +| generation mode | `TypiaGenerator.ts` | generate/unplugin 계열 지원 | + +이 중 현재 Go native가 직접 대체하는 것은 type analysis, IR struct, typia runtime +emitter, typia call rewrite path다. TS AST helper와 public composer API는 현재 +대체되지 않았다. + +## 모듈화 평가 + +### 잘 된 부분 + +`metadata`는 가장 재사용 가능성이 높다. `Schema`가 `MetadataSchema`의 bucket +구조를 보존하고, JSON tag가 붙은 Go struct라서 언어 중립 IR로 키우기 쉽다. + +`analyzer`는 `MetadataFactory`와 `iterate_metadata_*` 책임을 Go package로 묶었다. +entry point가 `Walk`, `WalkWithTypeNode`, `FromType`로 좁아져 내부 typia backend가 +쓰기에는 명료하다. + +`emitter`는 typia runtime 생성 책임을 feature별 파일로 나눴다. `is`, +`assert/diagnostic`, `json_schema`, `json_stringify`, `http`, `llm`, `protobuf`, +`random`, `reflect`의 파일 경계는 기존 programmer folder 경계와 대응된다. + +`transform/native/ttsc`는 TypeScript checker와 typia API 연결을 담당한다. +`adapter.go` 안의 `UnsupportedReason`, `UnsupportedSchemaReason`은 feature별 unsupported +shape를 빌드 전에 명시하려는 좋은 방향이다. + +### 부족한 부분 + +`packages/core/native/go.mod`의 replace 경로가 `../../ttsc/shim/*`로 되어 있다. +현재 저장소 구조에서는 `packages/ttsc`가 아니라 `toolchain/ttsc`가 존재하므로 +독립 `go test`가 analyzer setup 단계에서 실패한다. 반면 +`packages/transform/native/go.mod`는 `../../../toolchain/ttsc/shim/*`를 가리킨다. +두 모듈의 계약이 이미 어긋난 상태다. + +`visit.go`의 call-site 인식은 `typia/lib/`, `typia/src/`, +`packages/typia/src/` 선언 경로만 인정하고, 파일명에 subpath slash가 있으면 +버린다. 이 설계는 typia API 탐지에는 충분하지만, nestia나 제3 플러그인의 API를 +등록해서 재사용하는 구조는 아니다. + +`adapter.go`의 `EmitCall`은 feature registry가 아니라 350라인대 switch다. method +dispatch, create/non-create 여부, wrapper composition, http/protobuf 조합 로직이 +한 함수에 모여 있다. typia 내부 기능을 빠르게 연결하기에는 좋지만, 다른 +라이브러리가 자기 API를 얹을 extension point는 없다. + +`emitter`는 TypeScript AST node가 아니라 JavaScript source string을 반환한다. +이 덕분에 포팅 양은 줄었지만, nestia가 기존에 해오던 AST helper 재사용, +import insertion, SDK code generation과 직접 결합하기는 어렵다. + +`metadata.Collection` 주석에도 남아 있듯이 기존 TS `MetadataCollection`의 object +union/recursive index 관리가 아직 완전한 public contract로 정리된 상태는 아니다. +IR 구조는 남았지만, SDK/OpenAPI generator가 기대하는 collection operation +전체가 살아 있는 것은 아니다. + +## nestia 재사용성 + +`../nestia`는 `@typia/core`를 62개 tracked 위치에서 사용한다. import 성격은 대략 +다음과 같다. + +| 사용 성격 | 실제 import 예 | +| ------------------------- | --------------------------------------------------------------------------------------------- | +| transformer context/error | `ITypiaContext`, `ImportProgrammer`, `TransformerError` | +| metadata analysis | `MetadataFactory`, `JsonMetadataFactory`, `MetadataFactory.Validator` | +| metadata IR | `MetadataSchema`, `MetadataCollection`, `IMetadataDictionary` | +| OpenAPI/schema generation | `JsonSchemasProgrammer` | +| HTTP helpers | `HttpParameterProgrammer`, `HttpQueryProgrammer`, `HttpHeadersProgrammer` | +| TS AST factories | `IdentifierFactory`, `LiteralFactory`, `StatementFactory`, `TypeFactory`, `ExpressionFactory` | + +따라서 현재 Go native 코드만으로 nestia를 옮기는 선택지는 성립하지 않는다. +nestia 관점에서 필요한 것은 Go package import가 아니라 Node/TypeScript에서 +호출 가능한 호환 표면이다. + +가능한 방향은 세 가지다. + +1. `@typia/core` 호환 facade를 별도 패키지로 유지하고 내부에서 Go native 분석 + 결과를 받는다. +2. `metadata.Schema`를 JSON IR로 고정하고, TS 쪽 SDK/OpenAPI/AST generator는 그 + IR을 소비한다. +3. nestia의 generator까지 Go native로 같이 옮긴다. + +현재 구조는 2번의 출발점에 가장 가깝다. `metadata.Schema`는 IR로 쓸 만하지만, +분석 결과를 Node 쪽으로 안정적으로 넘기는 API, schema collection operation, +OpenAPI composer, diagnostic contract는 별도 설계가 필요하다. + +## legacy transformer 방향성 + +기존 TypeScript v5/v6 transformer 를 `ttsc` 가 TypeScript v7 native compiler 안에서 +그대로 실행하는 방향은 폐기한다. `typescript-go` 의 공식 API 방향은 in-process +`ts.Program` object graph 제공이 아니라 IPC 기반 curated API 이며, 현재 +`@typia/ttsc` plugin contract 도 JS-side AST hook 을 제공하지 않는다. + +따라서 `ttsc` 의 장기 방향은 다음과 같이 고정한다. + +1. TypeScript v7 native lane: Go native backend 또는 serialized IR bridge +2. JS-side plugin: manifest/config, emitted text post-processing, IR client +3. legacy transformer: TS v5/v6 또는 구버전 typia lane + +이 방향은 이상적인 호환성보다 실제 compiler boundary를 우선한다. + +## 검증 결과 + +현재 native Go 검증은 통과하지 않는다. + +`packages/core/native`: + +```text +FAIL github.com/samchon/typia/packages/core/native/analyzer [setup failed] +replacement directory ../../ttsc/shim/ast does not exist +replacement directory ../../ttsc/shim/checker does not exist +replacement directory ../../ttsc/shim/scanner does not exist +``` + +같은 실행에서 `emitter` 테스트도 실패한다. + +```text +TestEmitIsAtomic/number: +got "\"number\" === typeof input && Number.isFinite(input)" +want "\"number\" === typeof input" + +TestEmitIsObject: +expected input-inline fragments, +got helper-hoisted "__is_0(v)" body +``` + +이는 현재 emitter 구현이 `number`에 `Number.isFinite`를 추가하고 object predicate를 +helper로 hoist하도록 바뀌었는데, 테스트 fixture가 이전 기대값에 머물러 있음을 +의미한다. + +`packages/transform/native`: + +```text +--- FAIL: TestAnalyzeOpenApiIJsonSchema +openapi_test.go:45: target call site not found +``` + +테스트가 찾는 OpenAPI probe fixture가 현재 테스트 트리에 맞지 않는다. 이 상태에서 +native transform backend를 독립적으로 검증했다고 말할 수 없다. + +## 최종 판정 + +현재 포팅의 축소는 자연스럽다. 기존 TS 구현은 transformer backend이면서 동시에 +public AST toolkit, metadata toolkit, programmer toolkit, OpenAPI/schema composer, +diagnostic contract였다. 현재 Go 구현은 typia native backend에 필요한 type +analysis, IR, runtime JS emit, typia call rewrite만 남긴 구조다. 그래서 라인 수가 +약 절반으로 줄었다. + +하지만 재사용성 목표까지 포함하면 아직 합격선이 아니다. `core/native`와 +`transform/native`가 물리적으로 분리되어 있다는 사실만으로는 모듈화가 완성되지 +않는다. 제3 라이브러리가 사용할 안정 API, Node-facing facade, registry 기반 +dispatch, 독립 테스트, package contract가 필요하다. + +## 필요한 후속 작업 + +1. `packages/core/native/go.mod`의 shim replace 경로를 + `../../../toolchain/ttsc/shim/*`로 맞춘다. +2. `packages/core/native`와 `packages/transform/native`에 `go test ./...` CI를 + 추가한다. +3. stale emitter fixture를 현재 의도에 맞게 갱신하거나, `Number.isFinite` 및 helper + hoist가 잘못된 변경이면 emitter를 되돌린다. +4. `openapi_test.go`의 fixture 경로와 target call-site 조건을 현재 + `tests/test-typia-automated` 구조에 맞춘다. +5. `EmitCall` switch를 registry/table 기반으로 쪼개고, typia 외 plugin이 method + descriptor를 등록할 수 있는 형태로 만든다. +6. `metadata.Schema` JSON IR을 public contract로 고정할지 결정하고, nestia가 쓸 + Node-facing analyzer/schema composer API를 설계한다. +7. `@typia/core`를 제거한 상태에서 nestia가 요구하는 helper surface를 어디로 + 이전할지 문서화한다. + +위 후속 작업 전까지의 정확한 상태는 다음과 같다. + +> typia TypeScript v7 native transform backend로는 구조가 잡혀 있다. 그러나 +> nestia/third-party 재사용 가능한 `core`와 `transform` 모듈로는 아직 미완성이다. diff --git a/wiki/10-ecosystem/00-README.md b/wiki/10-ecosystem/00-README.md index 352aa2a5431..d2913656557 100644 --- a/wiki/10-ecosystem/00-README.md +++ b/wiki/10-ecosystem/00-README.md @@ -1,60 +1,28 @@ -# 10. Ecosystem — typia · nestia · agentica · autobe (세트) - -typia를 중심으로 연결된 4개 프로젝트를 다룬다. - -## 이 폴더의 구성 - -| # | 문서 | 역할 | -|---|---|---| -| 00 | (이 문서) | 네비게이션 | -| 01 | [nestia 개요 + tsgo 전환](01-nestia-and-tsgo.md) | @nestia/* 8 패키지 구조 + typia와 함께 Go 전환 | -| 02 | [agentica 개요](02-agentica.md) | LLM function calling 프레임워크 (autobe가 사용) | -| 03 | [autobe 개요](03-autobe.md) | vibe coding / compiler-driven development 실증 | -| 04 | [Philosophy Pyramid](04-philosophy-pyramid.md) | typia 사상의 계층적 확장 (타입→백엔드 전체) | -| 05 | [세트 tsgo 전환 통합 계획](05-integrated-tsgo-transition.md) | 4 프로젝트의 동시 전환 일정 | - -## 한 눈에 보는 관계 - -``` -Layer 4 — 응용 (Vibe Coding) - AutoBE 자연어 → 백엔드 전체 생성 - ↑ (LLM agent가 호출) -Layer 3 — Agent 프레임워크 - Agentica LLM function calling - ↑ (typia.llm.application 사용) -Layer 2 — 웹 프레임워크 통합 - nestia (@core + @sdk + ...) NestJS 데코레이터 + SDK + Swagger - ↑ (typia transformer 확장) -Layer 1 — 타입 엔진 (기반) - typia 타입 → 검증/직렬화/스키마/LLM/Protobuf - ↑ (typescript-go 정복) -Layer 0 — 컴파일러 - typescript-go (Microsoft) -``` - -## 왜 같이 보나 - -- typia가 공통 기반이다. -- nestia는 transformer와 schema를 직접 소비한다. -- agentica와 autobe는 typia의 LLM 경계를 소비한다. -- tsgo 전환 영향이 네 프로젝트에 연결된다. - -## tsgo 전환의 함의 - -typia가 Go로 전환하면 **nestia·agentica·autobe도 동시 전환** 불가피: -- nestia: legacy `ts-patch` 의존 제거, `@nestia/core` transformer를 `ttsc` host/plugin 모델로 재배치 -- agentica: typia.llm.application의 Go 구현을 그대로 소비 (표면 변화 적음) -- autobe: typia interface 소비 (표면 변화 거의 없음) - -상세: [05-integrated-tsgo-transition.md](05-integrated-tsgo-transition.md) - -## 현재 상태 (작성 시점) - -| 프로젝트 | 저장소 | 공개/비공개 | 버전 | -|---|---|---|---| -| typia | samchon/typia | 공개 | v12.0.2 | -| nestia | samchon/nestia | 공개 | v11.0.2 | -| agentica | samchon/agentica | 공개 | (미측정) | -| autobe | wrtnlabs/autobe | private | (@autobe/agent npm 공개) | - -→ 상세는 각 단원 참조. +# 10. Ecosystem + +현재 repo 기준으로 downstream 영향만 적는다. + +## current fact + +- typia는 `@typia/ttsc` / `@typia/ttsx` 전환을 repo 안에 구현했다. +- typia native backend 는 Go 로 존재한다. +- `@typia/core` / `@typia/transform` TypeScript package 는 현재 코드베이스에 없다. +- `typia/lib/transform` 은 native plugin entry 다. + +## downstream meaning + +| project | 영향 | +| --- | --- | +| nestia | legacy transformer 를 current `ttsc` contract 위로 다시 맞춰야 한다. | +| agentica | `typia.llm.application()` surface 를 주로 소비하므로 public API 변화는 작다. | +| autobe | typia/agentica 결과물을 소비하므로 compiler host 변화는 간접 영향이다. | + +## 읽을 문서 + +- [01-nestia-and-tsgo.md](01-nestia-and-tsgo.md) +- [02-agentica.md](02-agentica.md) +- [03-autobe.md](03-autobe.md) +- [04-philosophy-pyramid.md](04-philosophy-pyramid.md) +- [05-integrated-tsgo-transition.md](05-integrated-tsgo-transition.md) + +나머지 문서는 downstream planning note 로 읽는다. 현재 typia 구현 사실과 충돌하면 현재 구현을 우선한다. diff --git a/wiki/10-ecosystem/01-nestia-and-tsgo.md b/wiki/10-ecosystem/01-nestia-and-tsgo.md index 7d34e800f0b..85b5836ad50 100644 --- a/wiki/10-ecosystem/01-nestia-and-tsgo.md +++ b/wiki/10-ecosystem/01-nestia-and-tsgo.md @@ -1,213 +1,26 @@ -# 01. nestia 개요 + tsgo 전환 계획 +# 01. nestia -> `samchon/nestia`, v11.0.2, MIT. +현재 typia repo 기준으로 nestia에 주는 영향만 적는다. -## 한 줄 정의 +## 현재 사실 -> **nestia = NestJS 위에 typia 사상을 적용한 프레임워크 통합 레이어. 타입 한 번에서 SDK·Swagger·E2E 테스트까지 자동 생성.** +- typia의 current transformer lane 은 `@typia/ttsc` + `typia/lib/transform` + Go native backend 다. +- `@typia/core` / `@typia/transform` TypeScript package 는 현재 typia 코드베이스에 없다. +- nestia가 legacy `@typia/core` 또는 TypeScript transformer 구현에 기대고 있다면 current typia lane 과 바로 맞지 않는다. -## 실측 규모 (2026-04) +## 필요한 adapter boundary -- **전체 TS 파일**: 566개 -- **전체 src LOC**: 13,363 (packages/ 기준) -- **패키지 수**: 8개 +nestia가 붙어야 할 표면은 다음 둘이다. -> (이전 초안의 "47,655" 수치는 잘못되었음. Agent 실측으로 정정.) +| 표면 | 용도 | +| --- | --- | +| `@typia/ttsc` CLI / JS API | build, check, transform orchestration | +| shared native/core API | nestia decorator 분석과 schema generation. 아직 public API 로 고정되지 않았다. | -## 8 @nestia/* 패키지 책임 +## 현재 판단 -| 패키지 | 책임 | typia 직접 의존 | -|---|---|---| -| **@nestia/core** | `@TypedRoute/Body/Query/Param/Headers/FormData/Exception` + `@WebSocketRoute` + transformer 8 + programmer 22 | `@typia/core` (MetadataFactory 직접 import), `@typia/interface`, `@typia/utils` | -| **@nestia/sdk** | OpenAPI(3.0/3.1) + 타입 안전 SDK + Mockup Simulator + E2E 자동 생성 | `@typia/core`, `@typia/interface`, `@typia/utils`, `typia` | -| **@nestia/fetcher** | 런타임 SDK fetch wrapper (`PlainFetcher`, `EncryptedFetcher`, `PartialFetcher`) | `@typia/interface`, `@typia/utils` (0-dep 런타임) | -| **@nestia/editor** | Swagger UI + Online TypeScript Editor | `@typia/interface`, `typia` (클라이언트) | -| **@nestia/migrate** | Swagger → NestJS 코드 생성 | `@typia/core` (MetadataFactory) | -| **@nestia/e2e** | E2E 테스트 유틸 (ArrayUtil, RandomGenerator, TestValidator) | 직접 의존 없음 (런타임) | -| **@nestia/benchmark** | 벤치마크 (E2E 기반) | 테스트용 | -| **nestia (CLI)** | CLI 진입점 | 다른 패키지 호출만 | +- nestia decorator runtime 은 TypeScript/NestJS 영역에 남는다. +- build-time transformer logic 은 `ttsc` plugin/consumer 모델로 다시 맞춰야 한다. +- SDK/migrate 같은 generator 는 current typia native engine 과 연결할 별도 API 가 필요하다. -## Agent가 발견한 **가장 중요한 의존 3곳** - -### A. @nestia/core transformer 체이닝 -- 진입: `packages/core/src/transform.ts:1` — `ITypiaContext` import -- tsconfig: `plugins: [{ transform: "typia/lib/ttsc/plugin" }]` (nestia 쪽 typia 경로) -- 사용자 tsconfig: **2개 transformer 순차 실행** — typia → nestia - -### B. @nestia/core programmer들의 MetadataFactory 직접 호출 ⚠️ -파일: -- `packages/core/src/programmers/TypedBodyProgrammer.ts:1-12` -- `packages/core/src/programmers/TypedQueryProgrammer.ts` -- `packages/core/src/programmers/PlainBodyProgrammer.ts` -- 외 다수 - -패턴: -```ts -import { JsonMetadataFactory, MetadataFactory } from "@typia/core/..."; -// 직접 호출 -``` - -**위험도 ★★★★★**: typia v13에서 @typia/core가 Go로 포팅되면 nestia 빌드 즉시 불가. - -### C. @nestia/sdk·migrate의 **런타임** MetadataFactory 호출 ⚠️⚠️ -파일: `packages/sdk/src/generates/SwaggerGenerator.ts:1-14` - -패턴: CLI가 실행 타임에 `MetadataFactory.analyze()` 호출 → `OpenApiConverter.from(metadata)` 적용. - -**위험도 ★★★★**: 빌드 타임 transformer가 아닌 **런타임 JS가 MetadataFactory 직접 호출**. Go 포팅 후 이들은 Node에서 Go engine에 IPC 호출로 접근해야 함. - -## 사용자 tsconfig (현재 이중 transformer) - -```json -{ - "compilerOptions": { - "plugins": [ - { "transform": "typia/lib/ttsc/plugin" }, - { "transform": "@nestia/core/lib/transform" } - ] - } -} -``` - -## tsgo 전환 시 3가지 통합 방안 비교 - -### 방안 A ★★★★★ 권장: ttsc에 nestia 기능 완전 흡수 -``` -@typia/ttsc (build adapter + bridge) -├─ typia-go engine (metadata, 13 programmers) -├─ nestia-go driver (@Typed* 데코레이터 감지) -├─ nestia-go programmer 22 (Http*/Validate*/WebSocket 등) -└─ openapi-go + sdk generator-go - -@typia/ttsx (runner) -└─ @typia/ttsc 코어 재사용 - -사용자: plugins = [ - { transform: "typia/lib/ttsc/plugin" }, - { transform: "@nestia/core/lib/transform" } -] -(tsconfig 변경 없음. ttsc가 두 plugin 모두 내부 dispatch) -``` - -### 방안 B: @nestia/ttsc 별도 바이너리 -- ttsc가 typia만 실행하고, @nestia/ttsc가 after hook -- 2 transformer 순서 관리 필요, IPC 오버헤드 - -### 방안 C: @nestia/core 부분 Go 포팅 (혼합) -- cgo/JSON-RPC로 Go engine과 통신 -- 복잡한 IPC, 성능 저하 - -**방안 A 확정**: 단일 바이너리, 최고 성능, 사용자 마이그레이션 최소. - -## 패키지 경계 확정표 - -| 패키지 | 결정 | 근거 | -|---|---|---| -| `@nestia/core` decorator body | **TS 유지** | NestJS 런타임 데코레이터 필수 | -| `@nestia/core` transformer 8 | **Go 포팅** (ttsc 흡수) | 빌드 타임 전용, MetadataFactory 직접 의존 | -| `@nestia/core` programmer 22 | **Go 포팅** (ttsc 흡수) | typia.CheckerProgrammer 확장 | -| `@nestia/sdk` runtime | **TS 유지 + API 변경** | CLI 기반, ttsc Go API 호출로 | -| `@nestia/sdk` generator 로직 | **Go 포팅** 고려 | 35K 규모 감안, 혼합도 가능 | -| `@nestia/migrate` CLI | **TS 유지 + API 변경** | ttsc Go API 호출 | -| `@nestia/fetcher` | **TS 유지** | 런타임 HTTP 클라이언트 | -| `@nestia/editor` | **TS 유지** | 브라우저 React SPA | -| `@nestia/e2e` | **TS 유지** | 테스트 유틸 | -| `@nestia/benchmark` | **TS 유지** | 벤치 | -| `nestia` CLI | **TS 유지, 축소** | ttsc launcher | - -## nestia Go 포팅 LOC 예상 - -- @nestia/core transformer 8: ~7.5K Go -- @nestia/core programmer 22: ~15K Go (typia.CheckerProgrammer 확장) -- @nestia/sdk generator 일부 포팅: ~10K Go (선택적, 혼합) -- **총 nestia Go 추가: ~20~35K Go LOC** - -typia-go 100~150K + nestia 20~35K = **총 120~185K Go LOC** (ttsc 바이너리). - -## ttsc 연동 표면 - -지금 기준으로 nestia가 기대해야 할 것은 가상의 `@typia/ttsc/client` 가 아니라 다음 두 층이다. - -1. **현재 확정 표면**: `@typia/ttsc` CLI와 JS API (`build/check/transform`) -2. **미래 추출 표면**: 공통 코어가 안정화된 뒤의 generic `ttsc` contract - -즉, nestia는 당장 존재하지 않는 장기 IPC client에 걸지 말고, `@typia/ttsc` 기준의 adapter 경계를 먼저 맞춰야 한다. - -## Phase별 이식 일정 (Agent 권고 5단계) - -| Phase | 시점 | nestia 작업 | -|---|---|---| -| **Stage 0** | 2026 Q2 | `@typia/ttsc` adapter 경계 정리 + nestia-go driver 구조 설계 | -| **Stage 1** | 2026 Q3-Q4 | nestia v12.1~12.3 minor — @typia/core 호출을 "타입 안전 wrapper"로 격리 (예: `createValidateWithFactory()`) | -| **Stage 2** | 2027 Q1-Q2 | nestia-go transformer 기본 구현 (ttsc에서 @TypedRoute/Body 인식). @nestia/core v13-beta — deprecated marker + ttsc redirect 경고 | -| **Stage 3** | 2027 Q3-Q4 | @nestia/sdk·migrate CLI를 `@typia/ttsc` 연동 기준으로 재구성. 공통 코어가 검증되면 그때 generic API로 승격 | -| **Stage 4** | 2028 이후 | **nestia v14 major** — Go transformer 완전. legacy `ts-patch` 경로 제거와 migration guide 정리 | - -## 사용자 마이그레이션 경로 - -### 현재 (v11) -```bash -npm i -D typescript typia nestia @nestia/core @nestia/sdk ts-patch -# package.json: "prepare": "ts-patch install" -# tsconfig: plugins: [{ transform: "typia/lib/ttsc/plugin" }, ...] -``` - -### 전환 후 (v14 / typia v14 동시) -```bash -npm i -D typia nestia @nestia/core @nestia/sdk -npx typia setup -# 결과: @typescript/native-preview + @typia/ttsc -# nestia transformer는 별도 integration layer에서 정렬 -``` - -**tsconfig.json**: typia plugin 쪽은 `typia/lib/ttsc/plugin` 기준으로 바뀌고, nestia transformer는 그 위에서 별도 정렬이 필요하다. - -## tsgonest 경쟁 대응 (Agent 재확인) - -tsgonest가 `tsgonest migrate --apply`로 nestia 자동 흡수 시도. nestia의 방어: - -1. **성능 동등**: Go 포팅으로 10× tsc+ts-patch -2. **기능 우위**: OpenAPI 3.0/3.1/3.2 전체 (tsgonest는 3.2만), Mockup Simulator, E2E 자동 생성, Online Editor, WebSocket 지원 -3. **마이그레이션 문턱 0**: tsconfig/데코레이터 API 완전 동일 -4. **AutoBE/Agentica 생태계**: tsgonest 불가능 영역 - -## 불확실성 (Agent 명시) - -- **@typia/core의 v13 deprecation 시점** 미확정 -- **ttsc의 IPC/API 스펙** 추가 안정화 여지 있음 -- **pnpm 호이스팅**: nestia transformer의 substring matching이 pnpm virtual store에서 작동? -- **@nestia/editor standalone 번들**: browser에서 SDK 분석 시 Go engine 없이 어떻게? - -## samchon에게 구체적 실행 권고 5 - -1. **Stage 0 (2026-06 말)**: `@typia/ttsc` adapter 경계와 nestia-go driver 설계 정리 -2. **Stage 1 (2026 Q3-Q4)**: nestia v12.1~12.3 — @typia/core 호출을 wrapper로 격리 (향후 swap 용이) -3. **Stage 2 (2027 Q1-Q2)**: ttsc에 nestia-go driver 구현. @nestia/core v13-beta (deprecated 경고) -4. **Stage 3 (2027 Q3-Q4)**: @nestia/sdk·migrate CLI를 ttsc IPC로 재구성. v13 출시 -5. **Stage 4 (2028+)**: nestia v14 major — ts-patch 완전 제거, migration guide + tsgonest 통합 문서 - -## 주요 파일 (Agent 조사 40개 중 핵심 15) - -| 파일 | 책임 | -|---|---| -| `packages/core/src/transform.ts` | transformer 진입 | -| `packages/core/src/transformers/FileTransformer.ts` | 파일 순회 + import 주입 | -| `packages/core/src/transformers/TypedRouteTransformer.ts` | @TypedRoute 분석 | -| `packages/core/src/transformers/ParameterDecoratorTransformer.ts` | @TypedBody 등 감지 | -| `packages/core/src/transformers/WebSocketRouteTransformer.ts` | @WebSocketRoute | -| `packages/core/src/programmers/TypedBodyProgrammer.ts` | **@typia/core MetadataFactory 직접 호출** | -| `packages/core/src/programmers/TypedQueryProgrammer.ts` | 동일 | -| `packages/core/src/programmers/internal/CoreMetadataUtil.ts` | MetadataSchema 유틸 | -| `packages/core/src/decorators/TypedBody.ts` | decorator 런타임 (41 LOC) | -| `packages/core/src/decorators/TypedRoute.ts` | decorator 런타임 | -| `packages/core/src/decorators/WebSocketRoute.ts` | WebSocket decorator | -| `packages/sdk/src/generates/SwaggerGenerator.ts` | **런타임 MetadataFactory** | -| `packages/sdk/src/generates/SdkGenerator.ts` | 타입 안전 SDK 생성 | -| `packages/sdk/src/executable/sdk.ts` | CLI 진입 | -| `package.json` (루트) | 현행 setup 계약과 migration 영향면 검토 포인트 | - -## 한 줄 결론 - -> **nestia의 핵심(@nestia/core transformer 8 + programmer 22)은 Go 포팅해 `@typia/ttsc` 공통 코어 후보 위에 얹는다. @nestia/sdk·migrate·editor는 TS 유지하되 현재는 `@typia/ttsc` 연동, 나중에 generic `ttsc` contract가 검증되면 그쪽으로 승격한다.** - -→ 다음 [02-agentica.md](02-agentica.md) +정확한 이식 일정은 이 저장소의 current fact 가 아니다. diff --git a/wiki/10-ecosystem/02-agentica.md b/wiki/10-ecosystem/02-agentica.md index 591cf275f3c..78936b8f967 100644 --- a/wiki/10-ecosystem/02-agentica.md +++ b/wiki/10-ecosystem/02-agentica.md @@ -1,45 +1,17 @@ -# 02. agentica — LLM function calling 프레임워크 +# 02. agentica -> `samchon/agentica`. AutoBE의 핵심 의존. +agentica는 `typia.llm.application()` 결과를 소비하는 downstream 이다. -## 한 줄 정의 +## 현재 영향 -> **agentica = typia.llm.application 위에 얹은 agentic LLM 프레임워크.** Class interface 한 번 쓰면 LLM이 함수를 호출할 수 있게 해주는 "agent DSL". +- typia public API 가 유지되면 agentica 표면 변화는 작다. +- compiler host 변화는 agentica 내부가 아니라 typia build/setup 경로의 문제다. +- agentica가 받는 핵심 값은 계속 `ILlmApplication` / function schema 다. -## typia와의 관계 +## 확인할 것 -- **핵심 의존**: `typia.llm.application()` 결과를 소비 -- agentica가 LLM 호출 오케스트레이션, 에러 복구, 상태 관리를 담당 -- typia는 "함수 스키마"를, agentica는 "실행 루프"를 제공 +- typia native backend 가 생성하는 LLM schema parity +- `typia.llm.application()` regression test +- agentica 실제 function calling scenario smoke -## 주요 제공 기능 - -- **MicroAgentica**: 작은 범위 agent 구성 유틸 -- **IPointer**: 상태 관리용 포인터 기반 추상 -- **Function calling abstraction**: OpenAI/Claude/Gemini/Llama 공통 인터페이스 -- **Prompt histories**: 캐싱 + 컨텍스트 최적화 -- **Agentic loop**: 에러 재시도, parse/coerce/validate 사이클 - -## AutoBE에서의 역할 - -AutoBE의 40+ 전문 agent는 각자 자체 `typia.llm.application()` 스키마를 가지고, agentica의 **MicroAgentica 패턴**으로 LLM을 호출한다. agentica 없이는 AutoBE의 pipeline 불가능. - -## tsgo 전환 영향 - -- **사용자 API 표면**: 전혀 불변 -- **내부**: typia.llm.application이 Go engine 결과로 바뀌지만 agentica 입장에서는 동일한 ILlmApplication 객체를 받는다 -- **릴리스 타이밍**: typia v13 (2028 Q2, LLM Go) 출시와 동기화 - -## 전환 전 / 후 - -| 시점 | agentica 구조 변화 | -|---|---| -| 현재 (2026 Q2) | typia v12 + 자체 TS 코드 | -| Phase 4 (2028 Q1-Q2) | typia v13 Go LLM engine 소비 시작, API 표면 동일 | -| Phase 6 (2029 Q1-Q2) | 검증 완료, "agentica next" 릴리스 | - -## Wiki 상세 - -agentica 자체 구조·코드 분석은 별도 저장소 작업 범위. 본 master plan에서는 "typia의 LLM 엔진을 소비하는 레이어"로만 위치 확정. - -→ 상세 흐름: [03. AutoBE](03-autobe.md) 에서 agentica 사용 예 참고. +정확한 rollout 일정은 이 저장소의 current fact 가 아니다. diff --git a/wiki/10-ecosystem/03-autobe.md b/wiki/10-ecosystem/03-autobe.md index 8be93deea8d..2f7e52e2420 100644 --- a/wiki/10-ecosystem/03-autobe.md +++ b/wiki/10-ecosystem/03-autobe.md @@ -1,213 +1,18 @@ -# 03. AutoBE — vibe coding의 실증 +# 03. autobe -> `wrtnlabs/autobe`, AGPL-3.0 오픈 코어. +AutoBE는 typia와 agentica를 크게 소비하는 downstream 이다. -## 한 줄 정의 +## 현재 영향 -> **AutoBE = 자연어 대화만으로 프로덕션 레디 백엔드(TypeScript + NestJS + Prisma)를 100% 컴파일 성공하며 자동 생성하는 LLM 기반 코드 생성 플랫폼.** +- AutoBE가 직접 보는 typia 표면은 validator, JSON/schema, LLM application 쪽이다. +- typia public API 가 유지되면 compiler host 변경의 영향은 간접적이다. +- native backend parity 가 깨지면 AutoBE의 agent schema, validation, generated project tests 에 영향이 간다. -## 실측 규모 (2026-04) +## 확인할 것 -- **TS 코드**: 94,154 LOC (packages/ 기준) -- **파일 수**: 734개 -- **패키지 수**: 9개 (interface, agent, compiler, rpc, utils, filesystem, ui, benchmark, estimate) -- **에이전트**: 40+ 전문화 -- **이벤트 타입**: 65+ -- **시스템 프롬프트**: 700+ 페이지, 30+ 마크다운 파일 +- `typia.llm.application()` parity +- `typia.assert` / `validate` parity +- JSON schema output parity +- AutoBE sample project smoke -## 3 핵심 개념 - -### 1. Waterfall + Spiral Pipeline -``` -Requirements → Analyze → Database → Interface → Test → Realize -``` -5 phase × 내부 spiral loop. 실패 시 재생성·수정. - -### 2. Compiler-Driven Development -``` -AutoBE Prisma Compiler → AutoBE OpenAPI Compiler → TypeScript Compiler -``` -3-tier 검증. 진단이 AI에 피드백되어 **self-healing loop**. - -### 3. Vibe Coding -``` -Conversation → Requirements → AST → Code → Application -``` -대화가 소프트웨어가 되는 event-driven pipeline (65+ event type). - -## typia 5개 카테고리 활용 (Agent 실측) - -| API | 용도 | 빈도 | -|---|---|---| -| `typia.llm.application()` | **40+ agent 각각의 function schema** | 매우 높음 | -| `typia.llm.controller` | LLM controller 결합 | 높음 | -| `typia.is/assert/validate` | RPC 호출·agent 간 통신 검증 | 매우 높음 | -| `typia.random()` | Mock·테스트 데이터 | 중간 | -| `typia.tags.Format<"uuid"|"date-time"|...>` | 모든 interface 제약 | 매우 높음 | -| `typia.json.schemas()` | OpenAPI 생성 | 높음 | -| `typia.misc.literals()` | RPC 자동 파싱 (method 반사적 등록) | 중간 | - -→ **단일 프로젝트 기준 typia 최대 규모 실증**. - -## Discriminated Union + Mapper Pattern (AutoBE 기초) - -`packages/interface/src/histories/AutoBeHistory.ts` 실측 패턴: -```ts -export type AutoBeHistory = - | AutoBeUserMessageHistory - | AutoBeAnalyzeHistory - | AutoBeDatabaseHistory - | AutoBeInterfaceHistory; - -export interface Mapper { - userMessage: AutoBeUserMessageHistory; - analyze: AutoBeAnalyzeHistory; - // ... 65+ event/history -} - -function handle( - type: T, - history: AutoBeHistory.Mapper[T] -) { /* TS exact type 추론 */ } -``` - -이 패턴이 **RPC, compiler feedback, UI 렌더링**까지 연쇄 활용. - -## nestia 의존 - -| 영역 | 사용 | 용도 | -|---|---|---| -| `@nestia/core` | NestJS 프로젝트 템플릿 | 컨트롤러/DTO/모듈 자동 생성 | -| `@nestia/sdk` | SDK 생성 from OpenAPI | 타입 안전 클라이언트 SDK | -| `@nestia/e2e` | ArrayUtil, RandomGenerator, TestValidator | E2E 테스트 데이터 준비·검증 | -| `@nestia/fetcher` | `IConnection` | HTTP 연결 관리 | - -## 생성물 (한 대화 → 한 프로젝트) - -1. **Requirements Analysis** — 자연어 요구 정리 -2. **Database Schema** — Prisma 스키마 + ERD -3. **API Specification** — OpenAPI 3.x -4. **E2E Tests** — `test/features/api` 전체 -5. **API Implementation** — `src/providers/` -6. **Type-Safe SDK** — nestia 기반 - -실증 예시 (autobe-examples): **todo, reddit-클론, shopping(이커머스), ERP**. - -## typia 사상의 수직 확장 - -### typia 원본 명제 -> "타입 한 번 쓰면 검증·직렬화·스키마·LLM·Protobuf·random 자동" - -### AutoBE 확장 명제 -> "**자연어 한 번** → 타입 → **백엔드 전체**" - -동일 `IUser` 타입에서 AutoBE가 생성하는 산출물: -- Prisma: `model User { id String @unique ... }` -- OpenAPI: `POST /api/users { id: "uuid" format, ... }` -- E2E: `prepare_random_user()` 함수 자동 -- NestJS: `CreateUserDto`, `UserController`, `UserService` -- 타입 안전 SDK: `api.functional.users.create(connection, { id, email })` - -**typia의 "타입 하나로 모든 것"이 "자연어 하나로 백엔드 전체"로 한 층 상승**. - -## agentica와의 관계 - -``` -Agentica (Function Calling Framework) - ↑ - │ MicroAgentica 패턴 - │ 40개 agent = 40개 일회용 Agentica 인스턴스 - │ 각 agent의 typia.llm.application() = function schema - │ IPointer = 호출 결과 캡처 - ↓ -AutoBE (Orchestration) -``` - -- **Agentica**: "LLM이 함수를 호출하는 기술" -- **AutoBE**: "LLM이 호출할 함수들을 어떤 순서·어떤 입력으로 호출할지 조율" - -## tsgo 전환 시 AutoBE 영향 - -### 사용자 API 불변 → AutoBE 내부 수정 거의 없음 -- `typia.llm.application()` 표면 API 동일 -- 반환 `ILlmApplication` 구조 동일 -- Go engine이 내부 스키마 생성 → AutoBE에는 투명 - -### 성능 이점 (기대) -- 40+ agent × 다수 호출 → typia 검증 hot path -- Go engine으로 **런타임 검증 10~100× 향상** 가능 -- Prisma/OpenAPI 생성 속도 향상 -- 전체 pipeline throughput ↑ - -### 전환 일정 -- **2028 Q1-Q2** (Phase 4 typia LLM Go): AutoBE 내부 dogfooding -- **2029 Q2** (Phase 6 typia v14): "AutoBE next" 릴리스 - -## AutoBE → typia 개선 피드백 3개 (Agent 발견) - -### 1. Discriminated Union 자동 매핑 -- 현재: 65+ event type union discriminator 수동 Mapper 작성 -- **제안**: `typia.discriminator()` 유틸 추가 - -### 2. 동적 스키마 검증 -- 현재: AutoBE의 Function Calling Schema가 런타임 수정됨 -- **제안**: `typia.validate(value, { partialSchema: {...} })` 검토 - -### 3. 조건부 Tag 통합 -- 현재: 같은 필드가 context별 다른 제약 (예: 관리자 무제한 vs 일반 사용자 100) -- **제안**: 조건부 태그 검증 메커니즘 - -→ typia v13/v14 우선순위에 **반드시 반영 검토**. - -## 상업 가치 - -- **AGPL-3.0 오픈 코어** + 상업 라이선스 -- Wrtn Technologies 운영 -- **typia 자금 조달 경로**: Wrtn이 typia 스폰서 가능 -- **최대 레퍼런스**: "AutoBE가 typia로"는 typia 브랜딩에 큰 자산 - -## 경쟁자 비교 (Agent 분석) - -| 항목 | v0 / bolt.new / lovable | **AutoBE** | -|---|---|---| -| 생성 대상 | 프론트엔드 (React) | **백엔드** (TS/NestJS/Prisma) | -| 정확도 | 프롬프트 기반, 재생성 반복 | **100% 컴파일**, compiler feedback loop | -| 문서화 | UI 스크린샷만 | **ERD + OpenAPI + 분석 리포트** | -| 테스트 | 없음 | **E2E 자동 생성** (typia + @nestia/e2e) | -| 타입 안전성 | 선택적 | **전 계층** (DB → API → SDK) | -| 언어 | React만 | TS 기본, Java/Python 계획 | - -## 핵심 발견 5개 (Agent) - -1. **"Pure TypeScript" → "Type-Driven Backend Generation"** — typia 사상의 자연스러운 수직 확장 -2. **Discriminated Union Mapper Pattern = AutoBE 기초** — 65+ event, 8 history, 전 계층 타입 안전 -3. **Function Calling as Intent Recognition** — Agentica + `typia.llm.application`으로 LLM 의도 구조화 -4. **Compiler Feedback Loop = Self-Healing AI** — 생성 → 검증 → 실패 시 자동 수정 = 100% 컴파일 성공 비결 -5. **WebSocket RPC + Event Sourcing = Reproducible AI** — 모든 상태 이벤트 기록 → "지난주 세션 재실행" 가능 - -## 상업 인프라 - -- **playground-ui / server**: 무료 온라인 데모 (delta 로드맵에서 SQLite 영속화 중) -- **hackathon-ui / server / api**: B2B 이벤트용 (토큰 제한 벤더 관리) -- **vscode-extension**: IDE 통합 (VS Code Marketplace) -- **website**: 공식 문서 + 벤치마크 (autobe.dev) - -## typia 문서 확장 권고 (Agent) - -### wiki 신설 -`docs/04-ecosystem/autobe/`: -- `overview.md` -- `why-typesafe-backend.md` -- `llm-function-calling.md` -- `compiler-feedback-loop.md` -- `integration.md` - -### 01-philosophy/ 에 추가할 것 -"타입에서 코드까지: 자동 코드 생성의 진화" -- typia v1: "타입 → 검증" (런타임) -- typia v2: "타입 → API 명세" (아키텍처) -- typia v3 (미래): "타입 → 전체 백엔드" (AutoBE 모델) - -## 최종 결론 - -> **AutoBE는 typia의 최대 실증이자 상업 기반. tsgo 전환 후 Go native가 되면 AutoBE 성능 10~100× 향상 — typia 브랜딩·재정 조달의 핵심 동력. AutoBE에서 나온 피드백 3개(discriminator·동적 스키마·조건부 tag)는 v13/v14 우선순위에 반드시 반영.** +정확한 AutoBE rollout 일정은 이 저장소의 current fact 가 아니다. diff --git a/wiki/10-ecosystem/04-philosophy-pyramid.md b/wiki/10-ecosystem/04-philosophy-pyramid.md index 1723f24573e..f560933acb1 100644 --- a/wiki/10-ecosystem/04-philosophy-pyramid.md +++ b/wiki/10-ecosystem/04-philosophy-pyramid.md @@ -1,115 +1,26 @@ -# 04. Philosophy Pyramid — typia 사상의 계층 확장 +# 04. Philosophy Pyramid -> typia의 "Pure TypeScript" 명제가 nestia → agentica → autobe로 **수직 확장**되는 구조. 이 확장이 세트의 진짜 이유. +현재 의미만 남긴다. -## 5층 피라미드 - -``` - ┌─────────────────────────────────────────────┐ - │ Layer 4 — 자연어 (AutoBE) │ - │ "대화 한 번으로 백엔드 전체 생성" │ - └─────────────────────────────────────────────┘ - ┌─────────────────────────────────────────────┐ - │ Layer 3 — Agent (Agentica) │ - │ "interface 한 번 쓰면 LLM이 함수를 호출" │ - └─────────────────────────────────────────────┘ - ┌─────────────────────────────────────────────┐ - │ Layer 2 — HTTP (Nestia) │ - │ "controller 한 번 쓰면 SDK · Swagger · 테스트"│ - └─────────────────────────────────────────────┘ - ┌─────────────────────────────────────────────┐ - │ Layer 1 — 타입 (Typia) │ - │ "타입 한 번 쓰면 검증 · 직렬화 · 스키마 · 랜덤"│ - └─────────────────────────────────────────────┘ - ┌─────────────────────────────────────────────┐ - │ Layer 0 — 컴파일러 (TypeScript / tsgo) │ - │ "타입 시스템이 진실을 보장" │ - └─────────────────────────────────────────────┘ ``` +AutoBE + consumes Agentica / typia / nestia outputs -각 층의 공통 원리: **"한 번 쓰면 나머지가 따라 나온다"** (single source of truth). - -## 층별 명제의 정직한 진술 - -### Layer 1 — Typia -> **TypeScript 타입 한 번 쓰면, 검증/직렬화/JSON 스키마/LLM 스키마/Protobuf/랜덤이 자동 생성된다.** - -진정성: interface/type alias 하나에서 13 namespace가 다 나옴. - -### Layer 2 — Nestia -> **NestJS 컨트롤러 한 번 쓰면, 타입 안전 SDK · Swagger · E2E 테스트 · Mockup이 자동 생성된다.** - -진정성: `@TypedRoute`, `@TypedBody` 한 번 작성으로 클라이언트 코드와 문서가 따라 나옴. - -### Layer 3 — Agentica -> **클래스 인터페이스 한 번 쓰면, LLM이 그 함수를 호출 가능하다.** - -진정성: `typia.llm.application()` 호출로 LLM function calling 전체가 설정됨. - -### Layer 4 — AutoBE -> **자연어 대화 한 번 쓰면, NestJS + Prisma 백엔드가 100% 컴파일 가능한 상태로 생성된다.** - -진정성: "todo 앱 만들어줘" → 요구분석 + ERD + OpenAPI + E2E 테스트 + 구현 + SDK까지. - -## 피라미드의 수학 - -각 상위 층은 **하위 층의 결과를 입력으로 소비**한다: - -- **Nestia가 typia를 소비**: `@TypedBody(dto)`는 내부적으로 `typia.assert(body)` 실행 -- **Agentica가 typia를 소비**: `typia.llm.application()` 결과를 LLM에 전달 -- **AutoBE가 모두를 소비**: Agentica로 LLM을 부르고, nestia로 HTTP 생성하고, typia로 타입을 검증 +Agentica + consumes typia.llm.application() -**사상의 압축률**: -- 사용자가 작성: N줄의 타입/인터페이스 -- 산출물: typia가 M1줄 생성 + nestia가 M2줄 + autobe가 M3줄 -- M1 + M2 + M3 ≫ N +nestia + needs typia-compatible transform/schema generation -이것이 "Pure TypeScript" 사상의 **수직적 효용**. +typia + owns type-driven validation, JSON, LLM, protobuf, random generation -## 사상적 일관성 (3대 공통 원칙) - -### 1. 타입이 진실 -- typia: 스키마 별도 작성 금지 -- nestia: 데코레이터+타입으로 충분, class-validator 금지 -- agentica: 함수 시그니처가 LLM 스키마 -- autobe: AST가 중간 언어, 자연어 → AST → 코드 - -### 2. 컴파일 타임 경계 -- typia: 빌드 시 검증 코드 emit -- nestia: 빌드 시 SDK·Swagger emit -- agentica: 빌드 시 function calling 스키마 emit -- autobe: 빌드 시 NestJS 앱 전체 emit (LLM 호출로) - -### 3. 런타임 제로 오버헤드 -- typia: 스키마 객체 없이 inline 검증 -- nestia: 데코레이터가 transformer로 치환 -- agentica: LLM 호출 외 런타임 비용 최소 -- autobe: 생성된 앱 자체가 표준 NestJS (특수 런타임 없음) - -## tsgo 전환 시 피라미드 영향 - -typia가 Go로 건너갈 때 각 층은 어떻게 영향받는가: - -| 층 | 영향 | 변경 | -|---|---|---| -| Layer 4 AutoBE | **없음** (표면 API 불변) | typia interface를 그대로 소비. Prisma/NestJS 생성도 변함 없음 | -| Layer 3 Agentica | **거의 없음** | `typia.llm.application()` 표면 불변 | -| Layer 2 Nestia | **중간** | 자체 transformer를 ttsc에 통합 필요 | -| Layer 1 Typia | **내부 Go 포팅** | 이미 master plan | -| Layer 0 tsgo | **도달 대상** | typescript-go 정복 | - -→ 사용자 API 불변이 **4층 모두에서 보장**되면 피라미드는 건재. - -## 경쟁자의 피라미드? - -tsgonest는 Layer 1~2만 덮는다 (typia + nestia 기능 부분). AutoBE 같은 Layer 4 자동 생성은 없다. - -Zod/Valibot은 Layer 1만 덮는다. nestia와 동등 기능은 없고, 에이전틱 레이어도 공식 아님. - -**samchon의 피라미드는 유일 — 이것이 tsgonest가 쉽게 대체할 수 없는 해자**. - -## 한 줄 결론 +TypeScript-Go / ttsc + provides the current compiler host lane +``` -> **typia는 기반이고, nestia는 웹이며, agentica는 agent이고, autobe는 vibe coding이다. 한 명제 "Pure TypeScript"가 4층으로 수직 확장되며, tsgo 시대에도 이 피라미드는 그대로 유지된다.** +핵심은 단순하다. -→ 다음: 구체 통합 전환 계획은 [05-integrated-tsgo-transition.md](05-integrated-tsgo-transition.md) +- typia public API 는 downstream 의 안정 경계다. +- `ttsc` / `ttsx` 는 compiler/runtime toolchain 경계다. +- native backend parity 가 downstream 안정성을 결정한다. diff --git a/wiki/10-ecosystem/05-integrated-tsgo-transition.md b/wiki/10-ecosystem/05-integrated-tsgo-transition.md index 1af2205a91e..17cb01043d0 100644 --- a/wiki/10-ecosystem/05-integrated-tsgo-transition.md +++ b/wiki/10-ecosystem/05-integrated-tsgo-transition.md @@ -1,132 +1,29 @@ -# 05. 세트 tsgo 전환 통합 계획 (typia + nestia + agentica + autobe) +# 05. Integrated Transition -> 4 프로젝트의 동시 전환 일정. master plan의 Stage 0~6을 ecosystem 전체로 확장. +연도별 통합 계획은 현재 wiki 기준에서 제거한다. -## 원칙 - -1. **typia가 선행** — 모든 상위 층이 typia를 소비하므로 기반 완성 필수 -2. **nestia 동기** — typia Phase와 보조 맞춰 nestia transformer 이식 -3. **agentica 표면 불변** — typia.llm.application 표면 API 불변이면 agentica 내부 변경 최소 -4. **autobe 표면 불변** — typia/nestia/agentica 표면 API 불변이면 autobe는 변경 없음 -5. **한 릴리스, 네 프로젝트** — typia v14·nestia v12·agentica·autobe 동시 출시 (2029 Q2) - -## Phase 매트릭스 - -| Phase | 시점 | typia | nestia | agentica | autobe | -|---|---|---|---|---|---| -| 0 Spike | 2026 Q2 | ttsc spike | (관찰) | (관찰) | (관찰) | -| 1 Walking | 2026 Q3-Q4 | engine 기초 | (관찰) | (관찰) | (관찰) | -| 2 Validators | 2027 Q1-Q2 | **validators Go** | typed* 분석 시작 | (영향 없음) | (영향 없음) | -| 3 JSON | 2027 Q3-Q4 | JSON Go | `@TypedRoute` 포팅 시작 | (영향 없음) | (영향 없음) | -| 4 LLM | 2028 Q1-Q2 | **llm Go + typia v13** | `@TypedBody`, `@TypedQuery` Go | **agentica 내부 전환** | (영향 없음) | -| 5 Misc + SDK | 2028 Q3-Q4 | misc Go | **@nestia/sdk Swagger 포팅** | (안정화) | autobe 내부 검증 | -| 6 v1.0 | 2029 Q1-Q2 | **typia v14** | **nestia v12** | **agentica next** | **autobe next** | - -## 의존 순서 (토폴로지) +## current dependency order ``` -Stage 2 typia validators - ↓ (소비) -Stage 3 nestia @TypedRoute (typia.assert 사용) - ↓ -Stage 4 typia LLM + nestia @TypedBody - ↓ -Stage 4 agentica (typia.llm.application Go 엔진 사용) - ↓ -Phase 5 @nestia/sdk Swagger generator (typia.json.schema Go) - ↓ -Phase 6 autobe (agentica + nestia + typia 전체 Go 엔진 소비) +@typescript/native-preview + -> @typia/ttsc + -> typia + -> @typia/ttsx + -> @typia/unplugin + -> downstream projects ``` -## 각 프로젝트의 Phase별 변화 - -### typia -상세: [08-tsgo-master-plan/](../08-tsgo-master-plan/) - -### nestia -상세: [01-nestia-and-tsgo.md](01-nestia-and-tsgo.md) - -주요 일정: -- 2027 Q3: `@TypedRoute` Go 포팅 -- 2028 Q1-Q2: `@TypedBody`, `@TypedQuery`, `@TypedParam`, `@TypedHeaders` Go -- 2028 Q3-Q4: `@nestia/sdk` Swagger/SDK/E2E generator Go -- 2029 Q2: nestia v12 = Go native - -### agentica -- 전환 기간 내 **표면 API 불변** (typia.llm.application 소비만) -- 내부: typia.llm.application의 반환값이 Go 엔진이 생성한 것으로 바뀌지만, 소비자인 agentica에서는 구분 불가 -- 2028 Q1-Q2: dogfooding 시점 (typia v13 LLM 출시와 동기) -- 2029 Q2: agentica next 릴리스 (안정성 검증 완료) - -### autobe -- 가장 상위 레이어 — 타 프로젝트 완성에 종속 -- **표면 API 전혀 변화 없음** (사용자 자연어 입력 / 생성 코드 불변) -- 내부 Waterfall+Spiral 파이프라인은 유지 -- 2028 Q3-Q4: 내부 검증 (typia + nestia Go 엔진 사용) -- 2029 Q2: autobe next 릴리스 - -## 동시 릴리스 전략 (2029 Q2) - -4 프로젝트를 **한 번의 launch event**로: - -### Launch Day (2029 Q2 예정) -- typia v14 (Go native) -- nestia v12 (Go native) -- agentica next (LLM native Go) -- autobe next (vibe coding, full Go backend) -- 블로그 시리즈 "The ecosystem goes native" -- 컨퍼런스 발표 (TSConf / Korea JS Conference) - -### 메시지 -> "타입 하나에서 백엔드 전체까지. 4 프로젝트가 하나의 Go 바이너리와 하나의 사상을 공유한다." - -## 리스크 (세트 공통) - -| 리스크 | 영향 | 완화 | -|---|---|---| -| samchon 번아웃 | 매우 큼 | 조력자 + AutoBE 상업화로 유지비 조달 | -| typia Phase 지연 | 세트 전체 지연 | typia 범위 축소 우선, nestia/agentica/autobe 동시 축소 | -| tsgonest 세트 전체 잠식 | 큼 | AutoBE·Agentica 차별화로 방어 (tsgonest는 autobe 레벨 부재) | -| 사용자 이주 혼란 | 중 | typia setup은 typia toolchain만 정렬하고, 나머지 프로젝트는 별도 migration guide로 단계 이행 | - -## tsgonest 경쟁 대응의 층위 - -| 층 | tsgonest | typia 세트 | -|---|---|---| -| 타입 검증 | ✅ | ✅ (typia) | -| JSON | ✅ | ✅ (typia) | -| OpenAPI 3.2 | ✅ | ✅ (typia + nestia — 3.0/3.1/3.2 모두) | -| SDK 생성 | ✅ | ✅ (nestia @sdk) | -| Swagger UI + Editor | ❌ | ✅ (@nestia/editor) | -| E2E 테스트 자동 | ❌ | ✅ (@nestia/e2e + @nestia/sdk) | -| LLM function calling | ❌ | ✅ (typia.llm + agentica) | -| Protobuf | ❌ | ✅ (typia) | -| vibe coding (자연어→백엔드) | ❌ | ✅ (autobe) | -| 프레임워크 | NestJS only | NestJS (nestia) + 그 외 (typia 범용) | - -→ **tsgonest가 영원히 따라오지 못할 3층 (agentica, autobe, 범용 typia)이 결정적 해자**. - -## Go 조력자 관점 - -4 프로젝트 동시 Go 포팅은 혼자 불가. 권장 구성: -- samchon: 전체 아키텍처 + typia engine + autobe 연결 -- Go 조력자 1: ttsc driver + nestia transformer 포팅 -- Go 조력자 2: @nestia/sdk generator 포팅 + CI/빌드 -- (선택) 프론트엔드 조력자: @nestia/editor 유지 - -연간 비용 추정 (이상적): $100~200K (2인 시니어 Go 개발자 + 인프라). - -**조달**: AutoBE 상업화 수익 + OpenCollective + 엔터프라이즈 스폰서. - -## 이 통합 계획이 가진 구조적 장점 - -1. **중복 투자 제거**: typia 엔진과 nestia 엔진이 **같은 ttsc 바이너리** 안 — 공유 shim, 공유 metadata -2. **동시 릴리스 파급효과**: 4 프로젝트 **한 번의 뉴스 사이클**에 집중 -3. **사상 일관성**: 4 층 모두 동시 이주 → "일부는 TS, 일부는 Go" 혼란 없음 -4. **사용자 학습 비용 단일**: typia toolchain을 중심으로 4 프로젝트 migration guide를 같은 방향으로 정렬 +## current transition rule -## 한 줄 결론 +1. typia native backend parity 를 먼저 맞춘다. +2. `@typia/ttsc` plugin contract 를 좁고 안정적으로 유지한다. +3. `@typia/ttsx` runner semantics 를 검증한다. +4. nestia 같은 second consumer 로 generic boundary 를 검증한다. +5. agentica/autobe 는 typia public API parity 로 영향 범위를 확인한다. -> **typia + nestia + agentica + autobe 네 프로젝트가 **한 Go 바이너리** 안에서, **한 사상**으로, **한 번의 릴리스**에 Go native로 이주한다. 2029 Q2, "Pure TypeScript" 피라미드의 완성.** +## not current fact -→ 네트워크 복귀: [08-tsgo-master-plan/](../08-tsgo-master-plan/) +- 특정 연도 릴리스 +- 네 프로젝트 동시 릴리스 +- downstream major version number +- Go 포팅 범위 확정치