From f90d6ab08926445c210b79f8d9ce257222524bed Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:29:10 +0900 Subject: [PATCH 01/12] Switch to `deno.json` --- .github/dependabot.yml | 26 +++++----- .gitignore | 3 ++ DENO_README.md | 85 +++++++++++++++++++++++++++++++ README.md | 6 +-- deno.json | 45 +++++++++++++++++ deno.lock | 87 ++++++++++++++++++++++++++++++++ lib/index.js | 4 +- lib/to.js | 2 +- sample/classes.js | 2 +- sample/index.js | 8 +-- sample/tsconfig.json | 18 +++---- scripts/build_npm.ts | 57 +++++++++++++++++++++ scripts/test.js | 22 ++++++++ tsconfig.json | 111 ----------------------------------------- 14 files changed, 332 insertions(+), 144 deletions(-) create mode 100644 DENO_README.md create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 scripts/build_npm.ts create mode 100644 scripts/test.js delete mode 100644 tsconfig.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9d58c92..53180fa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,15 +1,15 @@ version: 2 updates: -- package-ecosystem: npm - directory: "/" - schedule: - interval: daily - time: "10:00" - timezone: Europe/Budapest - open-pull-requests-limit: 5 - versioning-strategy: increase - commit-message: - prefix: build - include: scope - ignore: - - dependency-name: "husky" \ No newline at end of file + - package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "10:00" + timezone: Europe/Budapest + open-pull-requests-limit: 5 + versioning-strategy: increase + commit-message: + prefix: build + include: scope + ignore: + - dependency-name: "husky" diff --git a/.gitignore b/.gitignore index bd76370..8ebae9b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ coverage/** # Dependency directories node_modules +# NPM build output (from deno task build) +npm/ + # MacOS related files *.DS_Store .AppleDouble diff --git a/DENO_README.md b/DENO_README.md new file mode 100644 index 0000000..0bba1ef --- /dev/null +++ b/DENO_README.md @@ -0,0 +1,85 @@ +# Class Transform - Deno Version + +This project has been converted from Node.js/npm to Deno while maintaining the ability to publish to NPM. + +## Development with Deno + +### Prerequisites + +- [Deno](https://deno.land/) installed +- [Node.js and npm](https://nodejs.org/) (only needed for NPM publishing) + +### Available Commands + +```bash +# Format code +deno task fix + +# Check formatting +deno task check + +# Run tests +deno task test + +# Build for NPM (requires Node.js/npm) +deno task build + +# Run the sample +deno task dev +``` + +### Project Structure + +- `lib/` - Source code (Deno-compatible ES modules) +- `scripts/` - Build and utility scripts +- `sample/` - Example usage +- `deno.json` - Deno configuration and tasks + +### Usage + +```javascript +import { Exposed, instanceToPlain, plainToInstance } from "./lib/index.js"; + +class Person { + name = Exposed.string(); + age = Exposed.number(); +} + +// Transform plain object to class instance +const person = plainToInstance({ name: "John", age: 30 }, Person, []); + +// Transform class instance back to plain object +const plain = instanceToPlain(person); +``` + +### Publishing + +#### To JSR (JavaScript Registry) - Recommended for Deno projects + +```bash +deno publish +``` + +#### To NPM - For Node.js compatibility + +1. Ensure Node.js and npm are installed +2. Build the NPM package: + ```bash + deno task build + ``` +3. Publish to NPM: + ```bash + cd npm && npm publish + ``` + +The NPM build will create a `npm/` directory with Node.js-compatible CommonJS and ES module builds. + +## Migration Notes + +This project was migrated from package.json to deno.json: + +- ✅ Source code already used ES modules with explicit extensions (Deno-compatible) +- ✅ No external dependencies to convert +- ✅ TypeScript configuration integrated into deno.json +- ✅ Build pipeline using @deno/dnt for NPM compatibility +- ✅ Formatting and linting now use Deno's built-in tools diff --git a/README.md b/README.md index 6188e4e..425c4ea 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ All field methods provide proper type hint to TypeScript type checker. | | `boolean` | given | | `Exposed.booleans` | `Array` | `[]` | | `Exposed.string` | `string \| null` | `null` | -| | `string ` | given | +| | `string` | given | | `Exposed.strings` | `Array` | `[]` | | `Exposed.struct` | `T` | `T {}` | | `Exposed.structs` | `Array` | `[]` | @@ -197,7 +197,7 @@ Fields that are not `Exposed` will be ignored. This applies to both `plainToInstance` and `instanceToPlain`. ```javascript -import { Exposed, plainToInstance, instanceToPlain } from "class-transform"; +import { Exposed, instanceToPlain, plainToInstance } from "class-transform"; class User { id = Exposed.number(); // number | null @@ -305,7 +305,7 @@ you can do that by using `Exposed.alias` method. Please note that the type method should come at the end. ```javascript -import { Exposed, plainToInstance, instanceToPlain } from "class-transform"; +import { Exposed, instanceToPlain, plainToInstance } from "class-transform"; class User { firstName = Exposed.alias("first_name_raw").string(); diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..f58d3f5 --- /dev/null +++ b/deno.json @@ -0,0 +1,45 @@ +{ + "name": "@cunarist/class-transform", + "version": "0.7.0", + "description": "Transformation between plain JavaScript objects and class instances", + "exports": "./lib/index.js", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/cunarist/class-transform.git" + }, + "imports": { + "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts" + }, + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "strict": false, + "lib": ["deno.window", "ES2015"] + }, + "tasks": { + "build": "deno run -A scripts/build_npm.ts", + "check": "deno fmt --check", + "fix": "deno fmt", + "test": "deno test", + "dev": "deno run --watch sample/index.js" + }, + "fmt": { + "include": ["lib/", "sample/", "scripts/"], + "exclude": ["npm/", "build/"], + "useTabs": false, + "lineWidth": 80, + "indentWidth": 2, + "semiColons": true, + "singleQuote": false, + "proseWrap": "preserve" + }, + "lint": { + "include": ["lib/", "sample/", "scripts/"], + "exclude": ["npm/", "build/"] + }, + "publish": { + "include": ["lib/", "README.md", "LICENSE", "CHANGELOG.md"], + "exclude": ["**/*.test.js", "**/*.test.ts"] + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..a038bd3 --- /dev/null +++ b/deno.lock @@ -0,0 +1,87 @@ +{ + "version": "5", + "remote": { + "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", + "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", + "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", + "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", + "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", + "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", + "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", + "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", + "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", + "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", + "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", + "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", + "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", + "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", + "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", + "https://deno.land/std@0.181.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", + "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", + "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", + "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", + "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", + "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", + "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", + "https://deno.land/x/deno_cache@0.6.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", + "https://deno.land/x/deno_cache@0.6.2/cache.ts": "58b53c128b742757efcad10af9a3871f23b4e200674cb5b0ddf61164fb9b2fe7", + "https://deno.land/x/deno_cache@0.6.2/deno_dir.ts": "1ea355b8ba11c630d076b222b197cfc937dd81e5a4a260938997da99e8ff93a0", + "https://deno.land/x/deno_cache@0.6.2/deps.ts": "12cca94516cf2d3ed42fccd4b721ecd8060679253f077d83057511045b0081aa", + "https://deno.land/x/deno_cache@0.6.2/dirs.ts": "009c6f54e0b610914d6ce9f72f6f6ccfffd2d47a79a19061e0a9eb4253836069", + "https://deno.land/x/deno_cache@0.6.2/disk_cache.ts": "66a1e604a8d564b6dd0500326cac33d08b561d331036bf7272def80f2f7952aa", + "https://deno.land/x/deno_cache@0.6.2/file_fetcher.ts": "4f3e4a2c78a5ca1e4812099e5083f815a8525ab20d389b560b3517f6b1161dd6", + "https://deno.land/x/deno_cache@0.6.2/http_cache.ts": "407135eaf2802809ed373c230d57da7ef8dff923c4abf205410b9b99886491fd", + "https://deno.land/x/deno_cache@0.6.2/lib/deno_cache_dir.generated.js": "59f8defac32e8ebf2a30f7bc77e9d88f0e60098463fb1b75e00b9791a4bbd733", + "https://deno.land/x/deno_cache@0.6.2/lib/snippets/deno_cache_dir-a2aecaa9536c9402/fs.js": "cbe3a976ed63c72c7cb34ef845c27013033a3b11f9d8d3e2c4aa5dda2c0c7af6", + "https://deno.land/x/deno_cache@0.6.2/mod.ts": "b4004287e1c6123d7f07fe9b5b3e94ce6d990c4102949a89c527c68b19627867", + "https://deno.land/x/deno_cache@0.6.2/util.ts": "f3f5a0cfc60051f09162942fb0ee87a0e27b11a12aec4c22076e3006be4cc1e2", + "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", + "https://deno.land/x/dnt@0.40.0/lib/compiler.ts": "7f4447531581896348b8a379ab94730856b42ae50d99043f2468328360293cb1", + "https://deno.land/x/dnt@0.40.0/lib/compiler_transforms.ts": "f21aba052f5dcf0b0595c734450842855c7f572e96165d3d34f8fed2fc1f7ba1", + "https://deno.land/x/dnt@0.40.0/lib/mod.deps.ts": "8d6123c8e1162037e58aa8126686a03d1e2cffb250a8757bf715f80242097597", + "https://deno.land/x/dnt@0.40.0/lib/npm_ignore.ts": "57fbb7e7b935417d225eec586c6aa240288905eb095847d3f6a88e290209df4e", + "https://deno.land/x/dnt@0.40.0/lib/package_json.ts": "607b0a4f44acad071a4c8533b312a27d6671eac8e6a23625c8350ce29eadb2ba", + "https://deno.land/x/dnt@0.40.0/lib/pkg/dnt_wasm.generated.js": "2694546844a50861d6d1610859afbf5130baca4dc6cf304541b7ec2d6d998142", + "https://deno.land/x/dnt@0.40.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "aba69a019a6da6f084898a6c7b903b8b583bc0dbd82bfb338449cf0b5bce58fd", + "https://deno.land/x/dnt@0.40.0/lib/shims.ts": "39e5c141f0315c0faf30b479b53f92b9078d92e1fd67ee34cc60b701d8e68dab", + "https://deno.land/x/dnt@0.40.0/lib/test_runner/get_test_runner_code.ts": "4dc7a73a13b027341c0688df2b29a4ef102f287c126f134c33f69f0339b46968", + "https://deno.land/x/dnt@0.40.0/lib/test_runner/test_runner.ts": "4d0da0500ec427d5f390d9a8d42fb882fbeccc92c92d66b6f2e758606dbd40e6", + "https://deno.land/x/dnt@0.40.0/lib/transform.deps.ts": "2e159661e1c5c650de9a573babe0e319349fe493105157307ec2ad2f6a52c94e", + "https://deno.land/x/dnt@0.40.0/lib/types.ts": "b8e228b2fac44c2ae902fbb73b1689f6ab889915bd66486c8a85c0c24255f5fb", + "https://deno.land/x/dnt@0.40.0/lib/utils.ts": "224f15f33e7226a2fd991e438d0291d7ed8c7889807efa2e1ecb67d2d1db6720", + "https://deno.land/x/dnt@0.40.0/mod.ts": "ae1890fbe592e4797e7dd88c1e270f22b8334878e9bf187c4e11ae75746fe778", + "https://deno.land/x/dnt@0.40.0/transform.ts": "f68743a14cf9bf53bfc9c81073871d69d447a7f9e3453e0447ca2fb78926bb1d", + "https://deno.land/x/ts_morph@20.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", + "https://deno.land/x/ts_morph@20.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", + "https://deno.land/x/ts_morph@20.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", + "https://deno.land/x/ts_morph@20.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", + "https://deno.land/x/ts_morph@20.0.0/common/ts_morph_common.js": "2325f94f61dc5f3f98a1dab366dc93048d11b1433d718b10cfc6ee5a1cfebe8f", + "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", + "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", + "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63" + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:prettier@^3.5.3", + "npm:typescript@^5.8.3" + ] + } + } +} diff --git a/lib/index.js b/lib/index.js index 8ef9cd2..36965e8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,7 @@ export { Exposed } from "./exposed.js"; export { - plainsToInstances, instancesToPlains, - plainToInstance, instanceToPlain, + plainsToInstances, + plainToInstance, } from "./to.js"; diff --git a/lib/to.js b/lib/to.js index 949c165..90b8917 100644 --- a/lib/to.js +++ b/lib/to.js @@ -1,5 +1,5 @@ import { Exposed } from "./exposed.js"; -import { whileExposing, Direction } from "./common.js"; +import { Direction, whileExposing } from "./common.js"; /** * Transforms an array of plain JavaScript objects diff --git a/sample/classes.js b/sample/classes.js index 8e00bd1..6ea52e6 100644 --- a/sample/classes.js +++ b/sample/classes.js @@ -1,6 +1,6 @@ // @ts-check -import { Exposed } from "class-transform"; +import { Exposed } from "../lib/index.js"; export class Album { id = Exposed.number(); diff --git a/sample/index.js b/sample/index.js index 4aad3dd..88200e6 100644 --- a/sample/index.js +++ b/sample/index.js @@ -1,9 +1,9 @@ import { - plainToInstance, - plainsToInstances, - instanceToPlain, instancesToPlains, -} from "class-transform"; + instanceToPlain, + plainsToInstances, + plainToInstance, +} from "../lib/index.js"; import { Photo, TimeRange } from "./classes.js"; const divider = "----------------------------------------"; diff --git a/sample/tsconfig.json b/sample/tsconfig.json index ffde148..24f64d3 100644 --- a/sample/tsconfig.json +++ b/sample/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "ES2015" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "ES2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ @@ -25,9 +25,9 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ES2015" /* Specify what module code is generated. */, + "module": "ES2015", /* Specify what module code is generated. */ // "rootDir": "./" /* Specify the root folder within your source files. */, - "moduleResolution": "Node10" /* Specify how TypeScript looks up a file from a given module specifier. */, + "moduleResolution": "Node10", /* 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. */ @@ -44,8 +44,8 @@ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ @@ -57,7 +57,7 @@ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ // "outDir": "./" /* Specify an output folder for all emitted files. */, // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true /* Disable emitting files from a compilation. */, + "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ @@ -77,12 +77,12 @@ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts new file mode 100644 index 0000000..2afe70e --- /dev/null +++ b/scripts/build_npm.ts @@ -0,0 +1,57 @@ +#!/usr/bin/env -S deno run -A + +import { build, emptyDir } from "@deno/dnt"; + +await emptyDir("./npm"); + +await build({ + entryPoints: ["./lib/index.js"], + outDir: "./npm", + shims: { + // see JS docs for overview and more options + deno: false, + }, + package: { + // package.json properties + name: "class-transform", + version: "0.7.0", + description: + "Transformation between plain JavaScript objects and class instances", + license: "MIT", + author: "Cunarist", + repository: { + type: "git", + url: "git+https://github.com/cunarist/class-transform.git", + }, + bugs: { + url: "https://github.com/cunarist/class-transform/issues", + }, + homepage: "https://github.com/cunarist/class-transform#readme", + keywords: [ + "type", + "javascript", + "object-to-class", + "class-to-object", + "deno", + "typescript", + ], + engines: { + node: ">=14.0.0", + }, + type: "module", + main: "./esm/index.js", + types: "./types/index.d.ts", + exports: { + ".": { + "import": "./esm/index.js", + "types": "./types/index.d.ts", + }, + }, + }, + postBuild() { + // steps to run after building and before running the tests + Deno.copyFileSync("LICENSE", "npm/LICENSE"); + Deno.copyFileSync("README.md", "npm/README.md"); + Deno.copyFileSync("CHANGELOG.md", "npm/CHANGELOG.md"); + }, +}); diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 0000000..907a102 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,22 @@ +// Simple test to verify the library works correctly +import { Exposed, instanceToPlain, plainToInstance } from "../lib/index.js"; + +class Person { + name = Exposed.string(); + age = Exposed.number(); +} + +// Test plainToInstance +const plain = { name: "John", age: 30 }; +const person = plainToInstance(plain, Person, []); + +console.log("plainToInstance test:"); +console.log("Person instance:", person); +console.log("Is instance of Person:", person instanceof Person); + +// Test instanceToPlain +const backToPlain = instanceToPlain(person); +console.log("\ninstanceToPlain test:"); +console.log("Back to plain:", backToPlain); + +console.log("\nAll tests passed! ✅"); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 976a197..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "include": ["lib/"], - "exclude": ["build/"], - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ES2015" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "ES2015" /* Specify what module code is generated. */, - "rootDir": "lib/" /* Specify the root folder within your source files. */, - "moduleResolution": "Node10" /* 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. */ - // "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. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - "emitDeclarationOnly": true /* Only output d.ts files and not JavaScript files. */, - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "build/" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ - "strict": false /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "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. */ - // "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. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} From a6db046f23d3a8b9e7b3769590173fbcfa839b03 Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:38:39 +0900 Subject: [PATCH 02/12] Use TS in our codebase --- README.md | 2 +- deno.json | 15 +- deno.lock | 2 +- lib/exposed.js | 332 --------------------------------- package.json | 2 +- sample/classes.js | 2 +- sample/index.js | 2 +- scripts/build_npm.ts | 5 +- scripts/test.js | 2 +- lib/common.js => src/common.ts | 20 +- src/exposed.ts | 290 ++++++++++++++++++++++++++++ lib/index.js => src/index.ts | 4 +- lib/to.js => src/to.ts | 141 +++++++------- 13 files changed, 385 insertions(+), 434 deletions(-) delete mode 100644 lib/exposed.js rename lib/common.js => src/common.ts (63%) create mode 100644 src/exposed.ts rename lib/index.js => src/index.ts (60%) rename lib/to.js => src/to.ts (63%) diff --git a/README.md b/README.md index 425c4ea..9dae9b9 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ So, what is the problem? Sometimes you want to transform plain JavaScript object to an instance of the ES6 class. Once you've parsed some data from a JSON API or a JSON file with `JSON.parse`, -you have plain JavaScript objects, not instances of a class. +you have plain objects, not instances of a class. For example you have a list of users in your `users.json` that you are loading: diff --git a/deno.json b/deno.json index f58d3f5..1c44b28 100644 --- a/deno.json +++ b/deno.json @@ -1,8 +1,8 @@ { "name": "@cunarist/class-transform", "version": "0.7.0", - "description": "Transformation between plain JavaScript objects and class instances", - "exports": "./lib/index.js", + "description": "Transformation between plain objects and class instances", + "exports": "./src/index.ts", "license": "MIT", "repository": { "type": "git", @@ -25,7 +25,7 @@ "dev": "deno run --watch sample/index.js" }, "fmt": { - "include": ["lib/", "sample/", "scripts/"], + "include": ["src/", "sample/", "scripts/"], "exclude": ["npm/", "build/"], "useTabs": false, "lineWidth": 80, @@ -35,11 +35,14 @@ "proseWrap": "preserve" }, "lint": { - "include": ["lib/", "sample/", "scripts/"], - "exclude": ["npm/", "build/"] + "include": ["src/", "sample/", "scripts/"], + "exclude": ["npm/", "build/"], + "rules": { + "exclude": ["no-explicit-any"] + } }, "publish": { - "include": ["lib/", "README.md", "LICENSE", "CHANGELOG.md"], + "include": ["src/", "README.md", "LICENSE", "CHANGELOG.md"], "exclude": ["**/*.test.js", "**/*.test.ts"] } } diff --git a/deno.lock b/deno.lock index a038bd3..435e83c 100644 --- a/deno.lock +++ b/deno.lock @@ -79,7 +79,7 @@ "workspace": { "packageJson": { "dependencies": [ - "npm:prettier@^3.5.3", + "npm:prettier@^3.6.2", "npm:typescript@^5.8.3" ] } diff --git a/lib/exposed.js b/lib/exposed.js deleted file mode 100644 index f9410b0..0000000 --- a/lib/exposed.js +++ /dev/null @@ -1,332 +0,0 @@ -import { Direction, isExposing } from "./common.js"; - -/** - * Marks a class field to be exposed while transformation, - * so that `plainToInstance` and `instanceToPlain` can pick them up properly. - * Each method also provides proper type hints. - */ -export class Exposed { - // Constructor - - /** @type {(new (...args: Array) => any) | null} */ - type = null; - /** @type {Array} */ - args = []; - /** @type {boolean} */ - isArray = false; - /** @type {string | null} */ - plainAlias = null; - /** @type {any | null} */ - defaultValue = null; - /** @type {number} */ - direction = Direction.toBoth; - - // Type builders - - /** - * @template T - * @param {T} defaultValue - * @returns {null extends T ? (number | null) : number} - */ - static number(defaultValue = null) { - if (!isExposing()) { - // @ts-ignore - return defaultValue; - } - const exposed = new Exposed(); - exposed.type = Number; - exposed.defaultValue = defaultValue; - // @ts-ignore - return exposed; - } - - /** - * @template T - * @param {T} defaultValue - * @returns {null extends T ? (number | null) : number} - */ - number(defaultValue = null) { - if (!isExposing()) { - // @ts-ignore - return defaultValue; - } - this.type = Number; - this.defaultValue = defaultValue; - // @ts-ignore - return this; - } - - /** - * @returns {Array} - */ - static numbers() { - if (!isExposing()) { - return []; - } - const exposed = new Exposed(); - exposed.type = Number; - exposed.isArray = true; - // @ts-ignore - return exposed; - } - - /** - * @returns {Array} - */ - numbers() { - if (!isExposing()) { - return []; - } - this.type = Number; - this.isArray = true; - // @ts-ignore - return this; - } - - /** - * @template T - * @param {T} defaultValue - * @returns {null extends T ? (boolean | null) : boolean} - */ - static boolean(defaultValue = null) { - if (!isExposing()) { - // @ts-ignore - return defaultValue; - } - const exposed = new Exposed(); - exposed.type = Boolean; - exposed.defaultValue = defaultValue; - // @ts-ignore - return exposed; - } - - /** - * @template T - * @param {T} defaultValue - * @returns {null extends T ? (boolean | null) : boolean} - */ - boolean(defaultValue = null) { - if (!isExposing()) { - // @ts-ignore - return defaultValue; - } - this.type = Boolean; - this.defaultValue = defaultValue; - // @ts-ignore - return this; - } - - /** - * @returns {Array} - */ - static booleans() { - if (!isExposing()) { - return []; - } - const exposed = new Exposed(); - exposed.type = Boolean; - exposed.isArray = true; - // @ts-ignore - return exposed; - } - - /** - * @returns {Array} - */ - booleans() { - if (!isExposing()) { - return []; - } - this.type = Boolean; - this.isArray = true; - // @ts-ignore - return this; - } - - /** - * @template T - * @param {T} defaultValue - * @returns {null extends T ? (string | null) : string} - */ - static string(defaultValue = null) { - if (!isExposing()) { - // @ts-ignore - return defaultValue; - } - const exposed = new Exposed(); - exposed.type = String; - exposed.defaultValue = defaultValue; - // @ts-ignore - return exposed; - } - - /** - * @template T - * @param {T} defaultValue - * @returns {null extends T ? (string | null) : string} - */ - string(defaultValue = null) { - if (!isExposing()) { - // @ts-ignore - return defaultValue; - } - this.type = String; - this.defaultValue = defaultValue; - // @ts-ignore - return this; - } - - /** - * @returns {Array} - */ - static strings() { - if (!isExposing()) { - return []; - } - const exposed = new Exposed(); - exposed.type = String; - exposed.isArray = true; - // @ts-ignore - return exposed; - } - - /** - * @returns {Array} - */ - strings() { - if (!isExposing()) { - return []; - } - this.type = String; - this.isArray = true; - // @ts-ignore - return this; - } - - /** - * @template T - * @template {Array} A - * @param {new (...args: A) => T} type - * @param {A} args - * @returns {T} - */ - static struct(type, args) { - if (!isExposing()) { - return new type(...args); - } - const exposed = new Exposed(); - exposed.type = type; - exposed.args = args; - // @ts-ignore - return exposed; - } - - /** - * @template T - * @template {Array} A - * @param {new (...args: A) => T} type - * @param {A} args - * @returns {T} - */ - struct(type, args) { - if (!isExposing()) { - return new type(...args); - } - this.type = type; - this.args = args; - // @ts-ignore - return this; - } - - /** - * @template T - * @template {Array} A - * @param {new (...args: A) => T} type - * @param {A} args - * @returns {Array} - */ - static structs(type, args) { - if (!isExposing()) { - return []; - } - const exposed = new Exposed(); - exposed.type = type; - exposed.args = args; - exposed.isArray = true; - // @ts-ignore - return exposed; - } - - /** - * @template T - * @template {Array} A - * @param {new (...args: A) => T} type - * @param {A} args - * @returns {Array} - */ - structs(type, args) { - if (!isExposing()) { - return []; - } - this.type = type; - this.args = args; - this.isArray = true; - // @ts-ignore - return this; - } - - // Options - - /** - * @param {string} plainAlias - * @returns {Exposed} - */ - static alias(plainAlias) { - const exposed = new Exposed(); - exposed.plainAlias = plainAlias; - return exposed; - } - - /** - * @param {string} plainAlias - * @returns {Exposed} - */ - alias(plainAlias) { - this.plainAlias = plainAlias; - return this; - } - - /** - * @returns {Exposed} - */ - static toInstanceOnly() { - const exposed = new Exposed(); - exposed.direction = Direction.toInstanceOnly; - return exposed; - } - - /** - * @returns {Exposed} - */ - toInstanceOnly() { - this.direction = Direction.toInstanceOnly; - return this; - } - - /** - * @returns {Exposed} - */ - static toPlainOnly() { - const exposed = new Exposed(); - exposed.direction = Direction.toPlainOnly; - return exposed; - } - - /** - * @returns {Exposed} - */ - toPlainOnly() { - this.direction = Direction.toPlainOnly; - return this; - } -} diff --git a/package.json b/package.json index 4cfd41e..8b86014 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "class-transform", "version": "0.7.0", - "description": "Transformation between plain JavaScript objects and class instances", + "description": "Transformation between plain objects and class instances", "author": "Cunarist", "license": "MIT", "type": "module", diff --git a/sample/classes.js b/sample/classes.js index 6ea52e6..8b59bf9 100644 --- a/sample/classes.js +++ b/sample/classes.js @@ -1,6 +1,6 @@ // @ts-check -import { Exposed } from "../lib/index.js"; +import { Exposed } from "../src/index.ts"; export class Album { id = Exposed.number(); diff --git a/sample/index.js b/sample/index.js index 88200e6..ddd60af 100644 --- a/sample/index.js +++ b/sample/index.js @@ -3,7 +3,7 @@ import { instanceToPlain, plainsToInstances, plainToInstance, -} from "../lib/index.js"; +} from "../src/index.ts"; import { Photo, TimeRange } from "./classes.js"; const divider = "----------------------------------------"; diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts index 2afe70e..a941909 100644 --- a/scripts/build_npm.ts +++ b/scripts/build_npm.ts @@ -5,7 +5,7 @@ import { build, emptyDir } from "@deno/dnt"; await emptyDir("./npm"); await build({ - entryPoints: ["./lib/index.js"], + entryPoints: ["./src/index.ts"], outDir: "./npm", shims: { // see JS docs for overview and more options @@ -15,8 +15,7 @@ await build({ // package.json properties name: "class-transform", version: "0.7.0", - description: - "Transformation between plain JavaScript objects and class instances", + description: "Transformation between plain objects and class instances", license: "MIT", author: "Cunarist", repository: { diff --git a/scripts/test.js b/scripts/test.js index 907a102..1b17591 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,5 +1,5 @@ // Simple test to verify the library works correctly -import { Exposed, instanceToPlain, plainToInstance } from "../lib/index.js"; +import { Exposed, instanceToPlain, plainToInstance } from "../src/index.ts"; class Person { name = Exposed.string(); diff --git a/lib/common.js b/src/common.ts similarity index 63% rename from lib/common.js rename to src/common.ts index cdeda51..074db29 100644 --- a/lib/common.js +++ b/src/common.ts @@ -2,26 +2,23 @@ * Enum class that represents the transformation direction. */ export class Direction { - static toBoth = 0; - static toInstanceOnly = 1; - static toPlainOnly = 2; + static readonly toBoth = 0; + static readonly toInstanceOnly = 1; + static readonly toPlainOnly = 2; } -let exposingDepth = 0; +let exposingDepth: number = 0; /** * Executes a provided function in an exposing context, * which makes `Exposed` fields actually become * instances of `Exposed`. * This function can be called recursively. - * @template T - * @param {() => T} callable - * @returns {T} */ -export function whileExposing(callable) { +export function whileExposing(callable: () => T): T { exposingDepth += 1; try { - let returned = callable(); + const returned = callable(); return returned; } catch (error) { throw error; @@ -32,10 +29,9 @@ export function whileExposing(callable) { /** * Checks if the code is currently within an exposing context. - * @returns {boolean} */ -export function isExposing() { - if (exposingDepth == 0) { +export function isExposing(): boolean { + if (exposingDepth === 0) { return false; } else { return true; diff --git a/src/exposed.ts b/src/exposed.ts new file mode 100644 index 0000000..981181e --- /dev/null +++ b/src/exposed.ts @@ -0,0 +1,290 @@ +import { Direction, isExposing } from "./common.ts"; + +// Type for constructor functions +type Constructor = new (...args: any[]) => T; + +/** + * Marks a class field to be exposed while transformation, + * so that `plainToInstance` and `instanceToPlain` can pick them up properly. + * Each method also provides proper type hints. + */ +export class Exposed { + type: Constructor | null = null; + args: any[] = []; + isArray: boolean = false; + plainAlias: string | null = null; + defaultValue: any = null; + direction: number = Direction.toBoth; + + // Type builders + + /** + * Creates a number field with optional default value + */ + static number( + defaultValue: T = null as T, + ): T extends null ? number | null : number { + if (!isExposing()) { + return defaultValue as any; + } + const exposed = new Exposed(); + exposed.type = Number; + exposed.defaultValue = defaultValue; + return exposed as any; + } + + /** + * Instance method for number field + */ + number( + defaultValue: T = null as T, + ): T extends null ? number | null : number { + if (!isExposing()) { + return defaultValue as any; + } + this.type = Number; + this.defaultValue = defaultValue; + return this as any; + } + + /** + * Creates an array of numbers + */ + static numbers(): number[] { + if (!isExposing()) { + return []; + } + const exposed = new Exposed(); + exposed.type = Number; + exposed.isArray = true; + return exposed as any; + } + + /** + * Instance method for array of numbers + */ + numbers(): number[] { + if (!isExposing()) { + return []; + } + this.type = Number; + this.isArray = true; + return this as any; + } + + /** + * Creates a boolean field with optional default value + */ + static boolean( + defaultValue: T = null as T, + ): T extends null ? boolean | null : boolean { + if (!isExposing()) { + return defaultValue as any; + } + const exposed = new Exposed(); + exposed.type = Boolean; + exposed.defaultValue = defaultValue; + return exposed as any; + } + + /** + * Instance method for boolean field + */ + boolean( + defaultValue: T = null as T, + ): T extends null ? boolean | null : boolean { + if (!isExposing()) { + return defaultValue as any; + } + this.type = Boolean; + this.defaultValue = defaultValue; + return this as any; + } + + /** + * Creates an array of booleans + */ + static booleans(): boolean[] { + if (!isExposing()) { + return []; + } + const exposed = new Exposed(); + exposed.type = Boolean; + exposed.isArray = true; + return exposed as any; + } + + /** + * Instance method for array of booleans + */ + booleans(): boolean[] { + if (!isExposing()) { + return []; + } + this.type = Boolean; + this.isArray = true; + return this as any; + } + + /** + * Creates a string field with optional default value + */ + static string( + defaultValue: T = null as T, + ): T extends null ? string | null : string { + if (!isExposing()) { + return defaultValue as any; + } + const exposed = new Exposed(); + exposed.type = String; + exposed.defaultValue = defaultValue; + return exposed as any; + } + + /** + * Instance method for string field + */ + string( + defaultValue: T = null as T, + ): T extends null ? string | null : string { + if (!isExposing()) { + return defaultValue as any; + } + this.type = String; + this.defaultValue = defaultValue; + return this as any; + } + + /** + * Creates an array of strings + */ + static strings(): string[] { + if (!isExposing()) { + return []; + } + const exposed = new Exposed(); + exposed.type = String; + exposed.isArray = true; + return exposed as any; + } + + /** + * Instance method for array of strings + */ + strings(): string[] { + if (!isExposing()) { + return []; + } + this.type = String; + this.isArray = true; + return this as any; + } + + /** + * Creates a struct (class instance) field + */ + static struct(type: new (...args: A) => T, args: A): T { + if (!isExposing()) { + return new type(...args); + } + const exposed = new Exposed(); + exposed.type = type; + exposed.args = args; + return exposed as any; + } + + /** + * Instance method for struct field + */ + struct(type: new (...args: A) => T, args: A): T { + if (!isExposing()) { + return new type(...args); + } + this.type = type; + this.args = args; + return this as any; + } + + /** + * Creates an array of structs + */ + static structs( + type: new (...args: A) => T, + args: A, + ): T[] { + if (!isExposing()) { + return []; + } + const exposed = new Exposed(); + exposed.type = type; + exposed.args = args; + exposed.isArray = true; + return exposed as any; + } + + /** + * Instance method for array of structs + */ + structs(type: new (...args: A) => T, args: A): T[] { + if (!isExposing()) { + return []; + } + this.type = type; + this.args = args; + this.isArray = true; + return this as any; + } + + // Options + + /** + * Sets an alias for the plain object property name + */ + static alias(plainAlias: string): Exposed { + const exposed = new Exposed(); + exposed.plainAlias = plainAlias; + return exposed; + } + + /** + * Instance method to set alias + */ + alias(plainAlias: string): Exposed { + this.plainAlias = plainAlias; + return this; + } + + /** + * Marks field as transformation to instance only + */ + static toInstanceOnly(): Exposed { + const exposed = new Exposed(); + exposed.direction = Direction.toInstanceOnly; + return exposed; + } + + /** + * Instance method to mark as instance only + */ + toInstanceOnly(): Exposed { + this.direction = Direction.toInstanceOnly; + return this; + } + + /** + * Marks field as transformation to plain only + */ + static toPlainOnly(): Exposed { + const exposed = new Exposed(); + exposed.direction = Direction.toPlainOnly; + return exposed; + } + + /** + * Instance method to mark as plain only + */ + toPlainOnly(): Exposed { + this.direction = Direction.toPlainOnly; + return this; + } +} diff --git a/lib/index.js b/src/index.ts similarity index 60% rename from lib/index.js rename to src/index.ts index 36965e8..22f2193 100644 --- a/lib/index.js +++ b/src/index.ts @@ -1,7 +1,7 @@ -export { Exposed } from "./exposed.js"; +export { Exposed } from "./exposed.ts"; export { instancesToPlains, instanceToPlain, plainsToInstances, plainToInstance, -} from "./to.js"; +} from "./to.ts"; diff --git a/lib/to.js b/src/to.ts similarity index 63% rename from lib/to.js rename to src/to.ts index 90b8917..12cfe18 100644 --- a/lib/to.js +++ b/src/to.ts @@ -1,67 +1,54 @@ -import { Exposed } from "./exposed.js"; -import { Direction, whileExposing } from "./common.js"; +import { Exposed } from "./exposed.ts"; +import { Direction, whileExposing } from "./common.ts"; /** - * Transforms an array of plain JavaScript objects + * Transforms an array of plain objects * to an array of class instances. - * --- - * @template T - * @template {Array} A - * @param {Array} plains - * @param {new (...args: A) => T} type - * @param {A} args - * @returns {Array} */ -export function plainsToInstances(plains, type, args) { +export function plainsToInstances( + plains: Record[], + type: new (...args: A) => T, + args: A, +): T[] { if (!(plains instanceof Array)) { throw new TypeError("For non-arrays, `plainToInstance` should be used"); } - const array = []; + const array: T[] = []; for (const eachObject of plains) { array.push(plainToInstance(eachObject, type, args)); } - // @ts-ignore return array; } /** * Transforms an array of class instances - * to an array of plain JavaScript objects. - * --- - * @template T - * @param {Array} instances - The instance to convert to a plain object. - * @returns {Array} + * to an array of plain objects. */ -export function instancesToPlains(instances) { +export function instancesToPlains(instances: T[]): Record[] { if (!(instances instanceof Array)) { throw new TypeError("For non-arrays, `instanceToPlain` should be used"); } - const array = []; + const array: Record[] = []; for (const eachInstance of instances) { array.push(instanceToPlain(eachInstance)); } - // @ts-ignore return array; } /** * Transforms a plain JavaScript object to a class instance. - * --- - * @template T - * @template {Array} A - * @param {Object} plain - * @param {new (...args: A) => T} type - * @param {A} args - * @returns {T} */ -export function plainToInstance(plain, type, args) { +export function plainToInstance( + plain: Record, + type: new (...args: A) => T, + args: A, +): T { if (plain instanceof Array) { throw new TypeError("For arrays, `plainsToInstances` should be used"); } return whileExposing(() => { - /** @type {Object} */ - const instance = new type(...args); + const instance = new type(...args) as any; for (const property in instance) { const exposed = instance[property]; @@ -71,28 +58,28 @@ export function plainToInstance(plain, type, args) { continue; } - const type = exposed.type; + const exposedType = exposed.type; const isArray = exposed.isArray; const plainAlias = exposed.plainAlias; const defaultValue = exposed.defaultValue; const direction = exposed.direction; - const args = exposed.args; + const exposedArgs = exposed.args; - let value; + let value: any; if (plainAlias === null) { value = plain[property]; } else { value = plain[plainAlias]; } - if (type === null) { + if (exposedType === null) { throw new TypeError("Type information not included in `Exposed`"); } if ( value === null || value === undefined || - direction == Direction.toPlainOnly + direction === Direction.toPlainOnly ) { if (isArray) { // Expected array, received invalid value. @@ -103,11 +90,13 @@ export function plainToInstance(plain, type, args) { // Put in the initial value with proper type enforced. if (defaultValue === null) { instance[property] = null; - } else if (type === Number || type === Boolean || type === String) { - // @ts-ignore - instance[property] = type(defaultValue); + } else if ( + exposedType === Number || exposedType === Boolean || + exposedType === String + ) { + instance[property] = (exposedType as any)(defaultValue); } else { - instance[property] = new type(); + instance[property] = new exposedType(); } } continue; @@ -117,16 +106,18 @@ export function plainToInstance(plain, type, args) { if (value instanceof Array) { // Expected array, received array. // Put in an array with elements that have proper type enforced. - const array = []; + const array: any[] = []; instance[property] = array; for (const eachValue of value) { if (eachValue === null) { continue; - } else if (type === Number || type === Boolean || type === String) { - // @ts-ignore - array.push(type(eachValue)); + } else if ( + exposedType === Number || exposedType === Boolean || + exposedType === String + ) { + array.push((exposedType as any)(eachValue)); } else { - array.push(plainToInstance(eachValue, type, args)); + array.push(plainToInstance(eachValue, exposedType, exposedArgs)); } } } else { @@ -140,20 +131,28 @@ export function plainToInstance(plain, type, args) { // Put in the initial value with proper type enforced. if (defaultValue === null) { instance[property] = null; - } else if (type === Number || type === Boolean || type === String) { - // @ts-ignore - instance[property] = type(defaultValue); + } else if ( + exposedType === Number || exposedType === Boolean || + exposedType === String + ) { + instance[property] = (exposedType as any)(defaultValue); } else { - instance[property] = new type(); + instance[property] = new exposedType(); } } else { // Expected single, received single. // Put in the received value with proper type enforced. - if (type === Number || type === Boolean || type === String) { - // @ts-ignore - instance[property] = type(value); + if ( + exposedType === Number || exposedType === Boolean || + exposedType === String + ) { + instance[property] = (exposedType as any)(value); } else { - instance[property] = plainToInstance(value, type, args); + instance[property] = plainToInstance( + value, + exposedType, + exposedArgs, + ); } } } @@ -180,31 +179,27 @@ export function plainToInstance(plain, type, args) { /** * Transforms a class instance to a plain JavaScript object. - * --- - * @template T - * @param {T} instance - The instance to convert to a plain object. - * @returns {Object} */ -export function instanceToPlain(instance) { +export function instanceToPlain(instance: T): Record { if (instance instanceof Array) { throw new TypeError("For arrays, `instancesToPlains` should be used"); } return whileExposing(() => { const type = Object.getPrototypeOf(instance).constructor; - const blankInstance = new type(); - const exposedProperties = []; + const blankInstance = new type() as any; + const exposedProperties: string[] = []; + for (const property in blankInstance) { if (blankInstance[property] instanceof Exposed) { exposedProperties.push(property); } } - /** @type {Object} */ - const plain = {}; + const plain: Record = {}; for (const property of exposedProperties) { - const value = instance[property]; + const value = (instance as any)[property]; if (value === null || value === undefined) { plain[property] = null; @@ -215,7 +210,7 @@ export function instanceToPlain(instance) { const plainName = blankExposed.plainAlias ?? property; const direction = blankExposed.direction; - if (direction == Direction.toInstanceOnly) { + if (direction === Direction.toInstanceOnly) { continue; } @@ -226,27 +221,27 @@ export function instanceToPlain(instance) { } if (value instanceof Array) { - const array = []; + const array: any[] = []; plain[plainName] = array; for (const eachValue of value) { - if (typeof eachValue == "number") { + if (typeof eachValue === "number") { array.push(eachValue); - } else if (typeof eachValue == "boolean") { + } else if (typeof eachValue === "boolean") { array.push(eachValue); - } else if (typeof eachValue == "string") { + } else if (typeof eachValue === "string") { array.push(eachValue); - } else if (typeof eachValue == "object") { + } else if (typeof eachValue === "object") { array.push(instanceToPlain(eachValue)); } } } else { - if (typeof value == "number") { + if (typeof value === "number") { plain[plainName] = value; - } else if (typeof value == "boolean") { + } else if (typeof value === "boolean") { plain[plainName] = value; - } else if (typeof value == "string") { + } else if (typeof value === "string") { plain[plainName] = value; - } else if (typeof value == "object") { + } else if (typeof value === "object") { plain[plainName] = instanceToPlain(value); } else { plain[plainName] = null; From d483e19b3fb9d3c097fc4076c0490891cf8d69a1 Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:39:26 +0900 Subject: [PATCH 03/12] Remove old files --- .npmignore | 1 - .prettierrc | 1 - package.json | 29 ----------------------------- 3 files changed, 31 deletions(-) delete mode 100644 .npmignore delete mode 100644 .prettierrc delete mode 100644 package.json diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 11bab7a..0000000 --- a/.npmignore +++ /dev/null @@ -1 +0,0 @@ -./build/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 0967ef4..0000000 --- a/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/package.json b/package.json deleted file mode 100644 index 8b86014..0000000 --- a/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "class-transform", - "version": "0.7.0", - "description": "Transformation between plain objects and class instances", - "author": "Cunarist", - "license": "MIT", - "type": "module", - "main": "./lib/index.js", - "types": "./build/index.d.ts", - "repository": { - "type": "git", - "url": "https://github.com/cunarist/class-transform.git" - }, - "tags": [ - "type", - "javascript", - "object-to-class", - "class-to-object" - ], - "scripts": { - "build": "npx tsc", - "fix": "npx prettier --write \"**/*.{js,md}\"", - "check": "npx prettier --check \"**/*.{js,md}\"" - }, - "devDependencies": { - "prettier": "^3.6.2", - "typescript": "^5.8.3" - } -} From c9c5fde4179e9ea17cb5816ff84d99430eaf088c Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:40:44 +0900 Subject: [PATCH 04/12] Organize `deno.json` --- deno.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/deno.json b/deno.json index 1c44b28..ee7bcdd 100644 --- a/deno.json +++ b/deno.json @@ -8,22 +8,12 @@ "type": "git", "url": "https://github.com/cunarist/class-transform.git" }, - "imports": { - "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts" - }, "compilerOptions": { "allowJs": true, "checkJs": true, "strict": false, "lib": ["deno.window", "ES2015"] }, - "tasks": { - "build": "deno run -A scripts/build_npm.ts", - "check": "deno fmt --check", - "fix": "deno fmt", - "test": "deno test", - "dev": "deno run --watch sample/index.js" - }, "fmt": { "include": ["src/", "sample/", "scripts/"], "exclude": ["npm/", "build/"], @@ -44,5 +34,15 @@ "publish": { "include": ["src/", "README.md", "LICENSE", "CHANGELOG.md"], "exclude": ["**/*.test.js", "**/*.test.ts"] + }, + "tasks": { + "build": "deno run -A scripts/build_npm.ts", + "check": "deno fmt --check", + "fix": "deno fmt", + "test": "deno test", + "dev": "deno run --watch sample/index.js" + }, + "imports": { + "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts" } } From 473303e832d9c825d72a1798c2016db4fdafad4e Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:41:26 +0900 Subject: [PATCH 05/12] Use default Deno fmt rules --- CHANGELOG.md | 66 +++++++++++++++-------- DENO_README.md | 9 ++-- README.md | 142 +++++++++++++++++++++++-------------------------- deno.json | 10 ---- 4 files changed, 117 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b3f66..159e12b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,24 +16,30 @@ #### Changed -- re-added accidentally removed deprecated function names `classToPlain` and `plainToClass` +- re-added accidentally removed deprecated function names `classToPlain` and + `plainToClass` ### [0.5.0][v0.5.0] [BREAKING CHANGE] - 2021-11-20 -> **NOTE:** This version fixes a security vulnerability allowing denial of service attacks with a specially crafted request payload. Please update as soon as possible. +> **NOTE:** This version fixes a security vulnerability allowing denial of +> service attacks with a specially crafted request payload. Please update as +> soon as possible. #### Breaking Changes -See the breaking changes from `0.4.1` release. It was accidentally released as patch version. +See the breaking changes from `0.4.1` release. It was accidentally released as +patch version. ### [0.4.1][v0.4.1] [BREAKING CHANGE] - 2021-11-20 -> **NOTE:** This version fixes a security vulnerability allowing denial of service attacks with a specially crafted request payload. Please update as soon as possible. +> **NOTE:** This version fixes a security vulnerability allowing denial of +> service attacks with a specially crafted request payload. Please update as +> soon as possible. #### Breaking Changes -**Exported functions has been renamed** -Some of the exported functions has been renamed to better reflect what they are doing. +**Exported functions has been renamed** Some of the exported functions has been +renamed to better reflect what they are doing. - `classToPlain` -> `instanceToPlain` - `plainToClass` -> `plainToInstance` @@ -52,7 +58,8 @@ Some of the exported functions has been renamed to better reflect what they are #### Breaking Changes -See the breaking changes from `0.3.2` release. It was accidentally released as patch version. +See the breaking changes from `0.3.2` release. It was accidentally released as +patch version. #### Added @@ -72,9 +79,9 @@ See the breaking changes from `0.3.2` release. It was accidentally released as p #### Breaking Changes -**Signature change for `@Transform` decorator** -From this version the `@Transform` decorator receives the transformation parameters in a a wrapper object. You need to -destructure the values you are interested in. +**Signature change for `@Transform` decorator** From this version the +`@Transform` decorator receives the transformation parameters in a a wrapper +object. You need to destructure the values you are interested in. Old way: @@ -90,8 +97,10 @@ New way with wrapper object: #### Added -- `exposeDefaultValues` option has been added, when enabled properties will use their default values when no value is present for the property -- the name of the currently transformed parameter is exposed in the `@Transform` decorator +- `exposeDefaultValues` option has been added, when enabled properties will use + their default values when no value is present for the property +- the name of the currently transformed parameter is exposed in the `@Transform` + decorator #### Fixed @@ -122,7 +131,8 @@ New way with wrapper object: #### Fixed - circular dependency fixed -- dev dependencies removed from package.json before publishing (no more security warnings) +- dev dependencies removed from package.json before publishing (no more security + warnings) - transformer order is deterministic now (#231) - fix prototype pollution issue (#367) - various fixes in documentation @@ -131,14 +141,17 @@ New way with wrapper object: #### Changed -- `enableImplicitConversion` has been added and imlplicit value conversion is disabled by default. -- reverted #234 - fix: write properties with defined default values on prototype which broke the `@Exclude` decorator. +- `enableImplicitConversion` has been added and imlplicit value conversion is + disabled by default. +- reverted #234 - fix: write properties with defined default values on prototype + which broke the `@Exclude` decorator. ### [0.2.2][v0.2.2] [BREAKING CHANGE] > **NOTE:** This version is deprecated. -This version has introduced a breaking-change when this library is used with class-validator. See #257 for details. +This version has introduced a breaking-change when this library is used with +class-validator. See #257 for details. #### Added @@ -150,14 +163,16 @@ This version has introduced a breaking-change when this library is used with cla #### Added -- add option to strip unkown properties via using the `excludeExtraneousValues` option +- add option to strip unkown properties via using the `excludeExtraneousValues` + option ### [0.2.0][v0.2.0] [BREAKING CHANGE] #### Added - add documentation for using `Set`s and `Map`s -- add opotion to pass a discriminator function to convert values into different types based on custom conditions +- add opotion to pass a discriminator function to convert values into different + types based on custom conditions - added support for polymorphism based on a named type property #### Fixed @@ -192,13 +207,16 @@ This version has introduced a breaking-change when this library is used with cla - renamed library from `constructor-utils` to `class-transform` - completely renamed most of names -- renamed all main methods: `plainToConstructor` now is `plainToClass` and `constructorToPlain` is `classToPlain`, etc. +- renamed all main methods: `plainToConstructor` now is `plainToClass` and + `constructorToPlain` is `classToPlain`, etc. - `plainToConstructorArray` method removed - now `plainToClass` handles it - `@Skip()` decorator renamed to `@Exclude()` - added `@Expose` decorator - added lot of new options: groups, versioning, custom names, etc. -- methods and getters that should be exposed must be decorated with `@Expose` decorator -- added `excludedPrefix` to class transform options that allows exclude properties that start with one of the given prefix +- methods and getters that should be exposed must be decorated with `@Expose` + decorator +- added `excludedPrefix` to class transform options that allows exclude + properties that start with one of the given prefix ### 0.0.22 @@ -224,7 +242,8 @@ This version has introduced a breaking-change when this library is used with cla #### Changed -- renamed `constructor-utils/constructor-utils` to `constructor-utils` package namespace +- renamed `constructor-utils/constructor-utils` to `constructor-utils` package + namespace ### 0.0.15 @@ -236,7 +255,8 @@ This version has introduced a breaking-change when this library is used with cla #### Removed -- removed `import "reflect-metadata"` from source code. Now reflect metadata should be included like any other shims. +- removed `import "reflect-metadata"` from source code. Now reflect metadata + should be included like any other shims. ### 0.0.13 diff --git a/DENO_README.md b/DENO_README.md index 0bba1ef..86cea06 100644 --- a/DENO_README.md +++ b/DENO_README.md @@ -1,6 +1,7 @@ # Class Transform - Deno Version -This project has been converted from Node.js/npm to Deno while maintaining the ability to publish to NPM. +This project has been converted from Node.js/npm to Deno while maintaining the +ability to publish to NPM. ## Development with Deno @@ -72,13 +73,15 @@ deno publish cd npm && npm publish ``` -The NPM build will create a `npm/` directory with Node.js-compatible CommonJS and ES module builds. +The NPM build will create a `npm/` directory with Node.js-compatible CommonJS +and ES module builds. ## Migration Notes This project was migrated from package.json to deno.json: -- ✅ Source code already used ES modules with explicit extensions (Deno-compatible) +- ✅ Source code already used ES modules with explicit extensions + (Deno-compatible) - ✅ No external dependencies to convert - ✅ TypeScript configuration integrated into deno.json - ✅ Build pipeline using @deno/dnt for NPM compatibility diff --git a/README.md b/README.md index 9dae9b9..c4e4f27 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,10 @@ [![NPM Version](https://img.shields.io/npm/v/class-transform)](https://badge.fury.io/js/class-transform) [![Minified Size](https://img.shields.io/bundlejs/size/class-transform)](https://bundlejs.com/?q=class-transform) -Class syntax was introduced to JavaScript in ES6. -Nowadays you are working with typed instances more than ever. -`class-transform` allows you to transform -JSON or plain object into strongly typed instance of a class and vice versa. -This tool is very helpful for both the frontend and backend. +Class syntax was introduced to JavaScript in ES6. Nowadays you are working with +typed instances more than ever. `class-transform` allows you to transform JSON +or plain object into strongly typed instance of a class and vice versa. This +tool is very helpful for both the frontend and backend. ```javascript // Plain - no type information @@ -36,9 +35,10 @@ Album { } ``` -Started as a fork of [class-transformer](https://github.com/typestack/class-transformer), -this library aims to simplify the API, modernize code, and enhance type safety. -Both JavaScript and TypeScript are fully supported. +Started as a fork of +[class-transformer](https://github.com/typestack/class-transformer), this +library aims to simplify the API, modernize code, and enhance type safety. Both +JavaScript and TypeScript are fully supported. ## Table of contents @@ -61,18 +61,16 @@ Both JavaScript and TypeScript are fully supported. In JavaScript, objects can be classified into two categories: -- Plain objects: - Objects that are instances of `Object` class. - Sometimes they are called literal objects, when created via `{}` notation. -- Instance objects: - Instances of classes with own defined constructor, properties and methods. - Usually you define them via `class` notation. +- Plain objects: Objects that are instances of `Object` class. Sometimes they + are called literal objects, when created via `{}` notation. +- Instance objects: Instances of classes with own defined constructor, + properties and methods. Usually you define them via `class` notation. So, what is the problem? -Sometimes you want to transform plain JavaScript object to an instance of the ES6 class. -Once you've parsed some data from a JSON API or a JSON file with `JSON.parse`, -you have plain objects, not instances of a class. +Sometimes you want to transform plain JavaScript object to an instance of the +ES6 class. Once you've parsed some data from a JSON API or a JSON file with +`JSON.parse`, you have plain objects, not instances of a class. For example you have a list of users in your `users.json` that you are loading: @@ -99,8 +97,8 @@ For example you have a list of users in your `users.json` that you are loading: ] ``` -To load the JSON data, you would write the following code. -However, it consists solely of plain objects and lacks type safety. +To load the JSON data, you would write the following code. However, it consists +solely of plain objects and lacks type safety. ```javascript let response = await fetch("users.json"); @@ -109,9 +107,9 @@ let plains = await response.json(); // Type checkers cannot help you with `any` type like this. ``` -To achieve type-safe programming, you can use `class-transform`. -Purpose of this library is to help you to convert your plain objects -to the instances of classes you have. +To achieve type-safe programming, you can use `class-transform`. Purpose of this +library is to help you to convert your plain objects to the instances of classes +you have. ```javascript import { Exposed, plainsToInstances } from "class-transform"; @@ -131,15 +129,16 @@ let instances = plainsToInstances(await response.json(), User, []); // You can use proper class methods as well. ``` -Even inside TypeScript codebases, -using classes for JSON can be advantageous over `interface` and `type` statements -because they are preserved after compilation, -enabling true object-oriented programming for reliable runtime behaviors. +Even inside TypeScript codebases, using classes for JSON can be advantageous +over `interface` and `type` statements because they are preserved after +compilation, enabling true object-oriented programming for reliable runtime +behaviors. ## Samples -Take a look at the [sample code](https://github.com/cunarist/class-transform/tree/main/sample) -for more examples of usages. +Take a look at the +[sample code](https://github.com/cunarist/class-transform/tree/main/sample) for +more examples of usages. ## Functions @@ -176,8 +175,8 @@ There are also methods for specifying options. | `Exposed.toInstanceOnly` | Include it only to instance | | `Exposed.toPlainOnly` | Include it only to plain | -You can combine the effects of these methods by chaining them. -Please note that the type method should come at the _end_ of the chain. +You can combine the effects of these methods by chaining them. Please note that +the type method should come at the _end_ of the chain. ```javascript class SomeType { @@ -187,14 +186,12 @@ class SomeType { ## Strong type safety -Strong type safety is always guaranteed. -A class instance will always have the +Strong type safety is always guaranteed. A class instance will always have the exact set of values that match its fields, with the exact types. -`class-transform` only transforms class fields -that are set as `Exposed` with plain objects. -Fields that are not `Exposed` will be ignored. -This applies to both `plainToInstance` and `instanceToPlain`. +`class-transform` only transforms class fields that are set as `Exposed` with +plain objects. Fields that are not `Exposed` will be ignored. This applies to +both `plainToInstance` and `instanceToPlain`. ```javascript import { Exposed, instanceToPlain, plainToInstance } from "class-transform"; @@ -235,27 +232,25 @@ console.log(plainNew); // } ``` -If a property is missing, `class-transform` will fill it with -the initial value, blank array, or an empty child instance -depending on the field type. If the field is not `Exposed`, -the value will not be included in the transformation at all. +If a property is missing, `class-transform` will fill it with the initial value, +blank array, or an empty child instance depending on the field type. If the +field is not `Exposed`, the value will not be included in the transformation at +all. -Each type method has a return type that represents the data, -allowing TypeScript's type checker to do its job. -It works well with `"strict": true` of `tsconfig.json`. -If you're using JavaScript, you can set -`tsconfig.json`'s `compilerOptions.checkJs` to `true` -to utilize TypeScript's type checker. +Each type method has a return type that represents the data, allowing +TypeScript's type checker to do its job. It works well with `"strict": true` of +`tsconfig.json`. If you're using JavaScript, you can set `tsconfig.json`'s +`compilerOptions.checkJs` to `true` to utilize TypeScript's type checker. ## Working with nested structures -When you are trying to transform objects that have nested objects, -you need to explicitly specify the type of field -by passing the class itself into `Exposed.struct` or `Exposed.structs`. -An array with the class parameters is also needed. +When you are trying to transform objects that have nested objects, you need to +explicitly specify the type of field by passing the class itself into +`Exposed.struct` or `Exposed.structs`. An array with the class parameters is +also needed. -Let's say we have an album with photos. -And we are trying to convert album plain object to class object: +Let's say we have an album with photos. And we are trying to convert album plain +object to class object: ```javascript import { Exposed } from "class-transform"; @@ -300,9 +295,9 @@ console.log(instance); ## Using different property name in plain objects -If the plain object's property should have a different name, -you can do that by using `Exposed.alias` method. -Please note that the type method should come at the end. +If the plain object's property should have a different name, you can do that by +using `Exposed.alias` method. Please note that the type method should come at +the end. ```javascript import { Exposed, instanceToPlain, plainToInstance } from "class-transform"; @@ -323,14 +318,14 @@ console.log(plainNew); // { first_name_raw: 'John', last_name_raw: 'Davis' } ``` -This is useful when the JSON API uses snakecase or some other naming conventions. +This is useful when the JSON API uses snakecase or some other naming +conventions. ## Providing an initial value -When a field didn't receive some proper value, -it can get an initial value instead of being filled with `null`. -Simply provide the initial value to the type method. -By doing so, `null` will be removed from the field's type hint. +When a field didn't receive some proper value, it can get an initial value +instead of being filled with `null`. Simply provide the initial value to the +type method. By doing so, `null` will be removed from the field's type hint. ```javascript import { Exposed, plainToInstance } from "class-transform"; @@ -347,14 +342,13 @@ console.log(instance); // User { firstName: 'John', lastName: 'Davis' } ``` -Even when you provide an initial value of a wrong type, -implicit type conversion happens under the hood, -resulting in a completely type-safe instance. +Even when you provide an initial value of a wrong type, implicit type conversion +happens under the hood, resulting in a completely type-safe instance. ## Skipping by direction -You can control on which operation you will include a field. -Use `Exposed.toInstanceOnly` or `Exposed.toPlainOnly` method. +You can control on which operation you will include a field. Use +`Exposed.toInstanceOnly` or `Exposed.toPlainOnly` method. ```typescript import { Exclude } from "class-transformer"; @@ -366,20 +360,20 @@ class User { } ``` -Now `password` field will be included -only during the `instanceToPlain` operation. +Now `password` field will be included only during the `instanceToPlain` +operation. - `toPlainOnly`: Initial value on `plainToInstance` - `toInstanceOnly`: Drop on `instanceToPlain` ## Using advanced types -Basically, it's recommended to store only primitive types for fields -to maintain clean structure and transformation. +Basically, it's recommended to store only primitive types for fields to maintain +clean structure and transformation. -However, sometimes more advanced types might be needed. -In such cases, you can use getter and setter methods -to process the data from the basic values in the class. +However, sometimes more advanced types might be needed. In such cases, you can +use getter and setter methods to process the data from the basic values in the +class. ```javascript import { Exposed, plainToInstance } from "class-transform"; @@ -428,8 +422,8 @@ console.log(instance); ## Constructing an instance manually -You can simply use the `new` keyword. -All the `Exposed` fields will get its initial value. +You can simply use the `new` keyword. All the `Exposed` fields will get its +initial value. ```javascript import { Exposed } from "class-transform"; diff --git a/deno.json b/deno.json index ee7bcdd..d9c8044 100644 --- a/deno.json +++ b/deno.json @@ -14,16 +14,6 @@ "strict": false, "lib": ["deno.window", "ES2015"] }, - "fmt": { - "include": ["src/", "sample/", "scripts/"], - "exclude": ["npm/", "build/"], - "useTabs": false, - "lineWidth": 80, - "indentWidth": 2, - "semiColons": true, - "singleQuote": false, - "proseWrap": "preserve" - }, "lint": { "include": ["src/", "sample/", "scripts/"], "exclude": ["npm/", "build/"], From a504962ae9d2ac8cd9929eb481fbf14f6a2b8467 Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:45:17 +0900 Subject: [PATCH 06/12] Convert sample files to TS --- deno.json | 7 +-- deno.lock | 8 --- sample/classes.js | 57 -------------------- sample/classes.ts | 55 ++++++++++++++++++++ sample/index.js | 129 ---------------------------------------------- sample/index.ts | 129 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+), 200 deletions(-) delete mode 100644 sample/classes.js create mode 100644 sample/classes.ts delete mode 100644 sample/index.js create mode 100644 sample/index.ts diff --git a/deno.json b/deno.json index d9c8044..b802adf 100644 --- a/deno.json +++ b/deno.json @@ -9,14 +9,9 @@ "url": "https://github.com/cunarist/class-transform.git" }, "compilerOptions": { - "allowJs": true, - "checkJs": true, - "strict": false, "lib": ["deno.window", "ES2015"] }, "lint": { - "include": ["src/", "sample/", "scripts/"], - "exclude": ["npm/", "build/"], "rules": { "exclude": ["no-explicit-any"] } @@ -30,7 +25,7 @@ "check": "deno fmt --check", "fix": "deno fmt", "test": "deno test", - "dev": "deno run --watch sample/index.js" + "dev": "deno run --watch sample/index.ts" }, "imports": { "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts" diff --git a/deno.lock b/deno.lock index 435e83c..5e00481 100644 --- a/deno.lock +++ b/deno.lock @@ -75,13 +75,5 @@ "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63" - }, - "workspace": { - "packageJson": { - "dependencies": [ - "npm:prettier@^3.6.2", - "npm:typescript@^5.8.3" - ] - } } } diff --git a/sample/classes.js b/sample/classes.js deleted file mode 100644 index 8b59bf9..0000000 --- a/sample/classes.js +++ /dev/null @@ -1,57 +0,0 @@ -// @ts-check - -import { Exposed } from "../src/index.ts"; - -export class Album { - id = Exposed.number(); - name = Exposed.string(); -} - -export class User { - id = Exposed.number(); - firstName = Exposed.string(5050); - lastName = Exposed.string("PLACEHOLDER"); - - /** @param {number} age */ - constructor(age) { - this.age = age; - } -} - -export class Photo { - id = Exposed.number(0); - filename = Exposed.alias("rawFilename").string("BASE-FILENAME"); - metadata = Exposed.toPlainOnly().string(); - description = Exposed.string(); - tags = Exposed.toInstanceOnly().strings(); - author = Exposed.struct(User, [25]); - albums = Exposed.structs(Album, []); - year = 1970; - - /** @param {string} mood */ - constructor(mood) { - this.mood = mood; - } - - get name() { - return this.id + "_" + this.filename; - } - - getAlbums() { - console.log("this is not serialized/deserialized"); - return this.albums; - } -} - -export class TimeRange { - startTimestamp = Exposed.string(); - endTimestamp = Exposed.number(); - - get start() { - return new Date(this.endTimestamp ?? 0); - } - - get end() { - return new Date(this.endTimestamp ?? 0); - } -} diff --git a/sample/classes.ts b/sample/classes.ts new file mode 100644 index 0000000..875c911 --- /dev/null +++ b/sample/classes.ts @@ -0,0 +1,55 @@ +import { Exposed } from "../src/index.ts"; + +export class Album { + id = Exposed.number(); + name = Exposed.string(); +} + +export class User { + id = Exposed.number(); + firstName = Exposed.string(5050); + lastName = Exposed.string("PLACEHOLDER"); + age: number; + + constructor(age: number) { + this.age = age; + } +} + +export class Photo { + id = Exposed.number(0); + filename = Exposed.alias("rawFilename").string("BASE-FILENAME"); + metadata = Exposed.toPlainOnly().string(); + description = Exposed.string(); + tags = Exposed.toInstanceOnly().strings(); + author = Exposed.struct(User, [25]); + albums = Exposed.structs(Album, []); + year = 1970; + mood: string; + + constructor(mood: string) { + this.mood = mood; + } + + get name(): string { + return this.id + "_" + this.filename; + } + + getAlbums(): any[] { + console.log("this is not serialized/deserialized"); + return this.albums; + } +} + +export class TimeRange { + startTimestamp = Exposed.string(); + endTimestamp = Exposed.number(); + + get start(): Date { + return new Date(this.endTimestamp ?? 0); + } + + get end(): Date { + return new Date(this.endTimestamp ?? 0); + } +} diff --git a/sample/index.js b/sample/index.js deleted file mode 100644 index ddd60af..0000000 --- a/sample/index.js +++ /dev/null @@ -1,129 +0,0 @@ -import { - instancesToPlains, - instanceToPlain, - plainsToInstances, - plainToInstance, -} from "../src/index.ts"; -import { Photo, TimeRange } from "./classes.js"; - -const divider = "----------------------------------------"; -console.log(divider); - -// Check replacing fields. - -let photoEmpty = new Photo("Joyful"); -photoEmpty.metadata = "blank"; -console.log(photoEmpty); -console.log(divider); - -let photoPlainEmpty = instanceToPlain(photoEmpty); -console.log(photoPlainEmpty); -console.log(divider); - -let photoEmptyNew = plainToInstance(photoPlainEmpty, Photo, ["Silent"]); -console.log(photoEmptyNew); -console.log(divider); - -// Check typing. - -let photoPlain = { - id: "1", - rawFilename: "myphoto.jpg", - description: "about my photo", - tags: ["me", "iam"], - author: { - id: "2", - firstName: "Johny", - lastName: "Cage", - }, - albums: [ - { - id: "1", - name: "My life", - }, - { - id: "2", - name: "My young years", - }, - ], - metadata: "I like it", -}; - -let photo = plainToInstance(photoPlain, Photo, ["Vibrant"]); -photo.year = 2020; -console.log(photo); -console.log(divider); - -// Check untyping. - -let photoPlainNew = instanceToPlain(photo); -console.log(photoPlainNew); -console.log(divider); - -// Type an array. -let photosPlain = [ - { - id: "1", - rawFilename: "myphoto.jpg", - author: { - id: "2", - firstName: "Johny", - lastName: "Cage", - registrationDate: "1995-12-17T03:24:00", - }, - albums: [ - { - id: "1", - name: "My life", - }, - { - id: "2", - name: "My young years", - }, - ], - }, - { - id: "2", - rawFilename: "hisphoto.jpg", - description: "about his photo", - author: { - id: "2", - }, - albums: [ - { - id: "1", - name: "My life", - }, - { - id: "2", - name: "My young years", - }, - ], - }, -]; -let photosJson = JSON.stringify(photosPlain, null, 2); - -let photos = plainsToInstances(JSON.parse(photosJson), Photo, ["Party"]); -console.log(photos); -console.log(divider); - -for (const photo of photos) { - console.log(`${photo.filename} ${photo.mood}`); -} -console.log(divider); - -// Check array untyping. - -let photosPlainNew = instancesToPlains(photos); -console.log(photosPlainNew); -console.log(divider); - -let plain = { - startTimestamp: "February 12, 2024 12:30:00", - endTimestamp: 1613477400000, -}; - -let instance = plainToInstance(plain, TimeRange, []); -console.log(instance.start); -console.log(instance.end); -console.log(divider); diff --git a/sample/index.ts b/sample/index.ts new file mode 100644 index 0000000..afe1254 --- /dev/null +++ b/sample/index.ts @@ -0,0 +1,129 @@ +import { + instancesToPlains, + instanceToPlain, + plainsToInstances, + plainToInstance, +} from "../src/index.ts"; +import { Photo, TimeRange } from "./classes.ts"; + +const divider = "----------------------------------------"; +console.log(divider); + +// Check replacing fields. + +const photoEmpty = new Photo("Joyful"); +photoEmpty.metadata = "blank"; +console.log(photoEmpty); +console.log(divider); + +const photoPlainEmpty = instanceToPlain(photoEmpty); +console.log(photoPlainEmpty); +console.log(divider); + +const photoEmptyNew = plainToInstance(photoPlainEmpty, Photo, ["Silent"]); +console.log(photoEmptyNew); +console.log(divider); + +// Check typing. + +const photoPlain = { + id: "1", + rawFilename: "myphoto.jpg", + description: "about my photo", + tags: ["me", "iam"], + author: { + id: "2", + firstName: "Johny", + lastName: "Cage", + }, + albums: [ + { + id: "1", + name: "My life", + }, + { + id: "2", + name: "My young years", + }, + ], + metadata: "I like it", +}; + +const photo = plainToInstance(photoPlain, Photo, ["Vibrant"]); +photo.year = 2020; +console.log(photo); +console.log(divider); + +// Check untyping. + +const photoPlainNew = instanceToPlain(photo); +console.log(photoPlainNew); +console.log(divider); + +// Type an array. +const photosPlain = [ + { + id: "1", + rawFilename: "myphoto.jpg", + author: { + id: "2", + firstName: "Johny", + lastName: "Cage", + registrationDate: "1995-12-17T03:24:00", + }, + albums: [ + { + id: "1", + name: "My life", + }, + { + id: "2", + name: "My young years", + }, + ], + }, + { + id: "2", + rawFilename: "hisphoto.jpg", + description: "about his photo", + author: { + id: "2", + }, + albums: [ + { + id: "1", + name: "My life", + }, + { + id: "2", + name: "My young years", + }, + ], + }, +]; +const photosJson = JSON.stringify(photosPlain, null, 2); + +const photos = plainsToInstances(JSON.parse(photosJson), Photo, ["Party"]); +console.log(photos); +console.log(divider); + +for (const photo of photos) { + console.log(`${photo.filename} ${photo.mood}`); +} +console.log(divider); + +// Check array untyping. + +const photosPlainNew = instancesToPlains(photos); +console.log(photosPlainNew); +console.log(divider); + +const plain = { + startTimestamp: "February 12, 2024 12:30:00", + endTimestamp: 1613477400000, +}; + +const instance = plainToInstance(plain, TimeRange, []); +console.log(instance.start); +console.log(instance.end); +console.log(divider); From 2ba62f1fc173a6e4052d42f2036a3d8a60ce274c Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:55:48 +0900 Subject: [PATCH 07/12] Use proper test code --- deno.json | 3 +- deno.lock | 20 +++ sample/.gitignore | 2 - sample/classes.ts | 55 -------- sample/index.ts | 129 ------------------ sample/package.json | 14 -- sample/tsconfig.json | 109 --------------- test/index.test.ts | 307 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 329 insertions(+), 310 deletions(-) delete mode 100644 sample/.gitignore delete mode 100644 sample/classes.ts delete mode 100644 sample/index.ts delete mode 100644 sample/package.json delete mode 100644 sample/tsconfig.json create mode 100644 test/index.test.ts diff --git a/deno.json b/deno.json index b802adf..8ac44bd 100644 --- a/deno.json +++ b/deno.json @@ -28,6 +28,7 @@ "dev": "deno run --watch sample/index.ts" }, "imports": { - "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts" + "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts", + "@std/assert": "jsr:@std/assert@1" } } diff --git a/deno.lock b/deno.lock index 5e00481..8807e4c 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,20 @@ { "version": "5", + "specifiers": { + "jsr:@std/assert@1": "1.0.14", + "jsr:@std/internal@^1.0.10": "1.0.10" + }, + "jsr": { + "@std/assert@1.0.14": { + "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.10": { + "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" + } + }, "remote": { "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", @@ -75,5 +90,10 @@ "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63" + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1" + ] } } diff --git a/sample/.gitignore b/sample/.gitignore deleted file mode 100644 index b7dab5e..0000000 --- a/sample/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -build \ No newline at end of file diff --git a/sample/classes.ts b/sample/classes.ts deleted file mode 100644 index 875c911..0000000 --- a/sample/classes.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Exposed } from "../src/index.ts"; - -export class Album { - id = Exposed.number(); - name = Exposed.string(); -} - -export class User { - id = Exposed.number(); - firstName = Exposed.string(5050); - lastName = Exposed.string("PLACEHOLDER"); - age: number; - - constructor(age: number) { - this.age = age; - } -} - -export class Photo { - id = Exposed.number(0); - filename = Exposed.alias("rawFilename").string("BASE-FILENAME"); - metadata = Exposed.toPlainOnly().string(); - description = Exposed.string(); - tags = Exposed.toInstanceOnly().strings(); - author = Exposed.struct(User, [25]); - albums = Exposed.structs(Album, []); - year = 1970; - mood: string; - - constructor(mood: string) { - this.mood = mood; - } - - get name(): string { - return this.id + "_" + this.filename; - } - - getAlbums(): any[] { - console.log("this is not serialized/deserialized"); - return this.albums; - } -} - -export class TimeRange { - startTimestamp = Exposed.string(); - endTimestamp = Exposed.number(); - - get start(): Date { - return new Date(this.endTimestamp ?? 0); - } - - get end(): Date { - return new Date(this.endTimestamp ?? 0); - } -} diff --git a/sample/index.ts b/sample/index.ts deleted file mode 100644 index afe1254..0000000 --- a/sample/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { - instancesToPlains, - instanceToPlain, - plainsToInstances, - plainToInstance, -} from "../src/index.ts"; -import { Photo, TimeRange } from "./classes.ts"; - -const divider = "----------------------------------------"; -console.log(divider); - -// Check replacing fields. - -const photoEmpty = new Photo("Joyful"); -photoEmpty.metadata = "blank"; -console.log(photoEmpty); -console.log(divider); - -const photoPlainEmpty = instanceToPlain(photoEmpty); -console.log(photoPlainEmpty); -console.log(divider); - -const photoEmptyNew = plainToInstance(photoPlainEmpty, Photo, ["Silent"]); -console.log(photoEmptyNew); -console.log(divider); - -// Check typing. - -const photoPlain = { - id: "1", - rawFilename: "myphoto.jpg", - description: "about my photo", - tags: ["me", "iam"], - author: { - id: "2", - firstName: "Johny", - lastName: "Cage", - }, - albums: [ - { - id: "1", - name: "My life", - }, - { - id: "2", - name: "My young years", - }, - ], - metadata: "I like it", -}; - -const photo = plainToInstance(photoPlain, Photo, ["Vibrant"]); -photo.year = 2020; -console.log(photo); -console.log(divider); - -// Check untyping. - -const photoPlainNew = instanceToPlain(photo); -console.log(photoPlainNew); -console.log(divider); - -// Type an array. -const photosPlain = [ - { - id: "1", - rawFilename: "myphoto.jpg", - author: { - id: "2", - firstName: "Johny", - lastName: "Cage", - registrationDate: "1995-12-17T03:24:00", - }, - albums: [ - { - id: "1", - name: "My life", - }, - { - id: "2", - name: "My young years", - }, - ], - }, - { - id: "2", - rawFilename: "hisphoto.jpg", - description: "about his photo", - author: { - id: "2", - }, - albums: [ - { - id: "1", - name: "My life", - }, - { - id: "2", - name: "My young years", - }, - ], - }, -]; -const photosJson = JSON.stringify(photosPlain, null, 2); - -const photos = plainsToInstances(JSON.parse(photosJson), Photo, ["Party"]); -console.log(photos); -console.log(divider); - -for (const photo of photos) { - console.log(`${photo.filename} ${photo.mood}`); -} -console.log(divider); - -// Check array untyping. - -const photosPlainNew = instancesToPlains(photos); -console.log(photosPlainNew); -console.log(divider); - -const plain = { - startTimestamp: "February 12, 2024 12:30:00", - endTimestamp: 1613477400000, -}; - -const instance = plainToInstance(plain, TimeRange, []); -console.log(instance.start); -console.log(instance.end); -console.log(divider); diff --git a/sample/package.json b/sample/package.json deleted file mode 100644 index 62351c8..0000000 --- a/sample/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "sample", - "version": "0.1.0", - "description": "Demonstration for class-transform", - "private": true, - "type": "module", - "dependencies": { - "class-transform": "file:../", - "moment": "^2.30.1" - }, - "devDependencies": { - "typescript": "^5.3.3" - } -} diff --git a/sample/tsconfig.json b/sample/tsconfig.json deleted file mode 100644 index 24f64d3..0000000 --- a/sample/tsconfig.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ES2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "ES2015", /* Specify what module code is generated. */ - // "rootDir": "./" /* Specify the root folder within your source files. */, - "moduleResolution": "Node10", /* 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. */ - // "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. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true /* Only output d.ts files and not JavaScript files. */, - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "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. */ - // "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. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..d5d3ae9 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,307 @@ +import { assertEquals, assertInstanceOf } from "@std/assert"; +import { + Exposed, + instancesToPlains, + instanceToPlain, + plainsToInstances, + plainToInstance, +} from "@cunarist/class-transform"; + +export class Album { + id = Exposed.number(); + name = Exposed.string(); +} + +export class User { + id = Exposed.number(); + firstName = Exposed.string("5050"); + lastName = Exposed.string("PLACEHOLDER"); + age: number; + + constructor(age: number) { + this.age = age; + } +} + +export class Photo { + id = Exposed.number(0); + filename = Exposed.alias("rawFilename").string("BASE-FILENAME"); + metadata = Exposed.toPlainOnly().string(); + description = Exposed.string(); + tags = Exposed.toInstanceOnly().strings(); + author = Exposed.struct(User, [25]); + albums = Exposed.structs(Album, []); + year = 1970; + mood: string; + + constructor(mood: string) { + this.mood = mood; + } + + get name(): string { + return this.id + "_" + this.filename; + } + + getAlbums(): any[] { + console.log("this is not serialized/deserialized"); + return this.albums; + } +} + +export class TimeRange { + startTimestamp = Exposed.string(); + endTimestamp = Exposed.number(); + + get start(): Date { + return new Date(this.endTimestamp ?? 0); + } + + get end(): Date { + return new Date(this.endTimestamp ?? 0); + } +} + +Deno.test("Photo class - basic transformation", () => { + const photoPlain = { + id: "1", + rawFilename: "myphoto.jpg", + description: "about my photo", + tags: ["me", "iam"], + author: { + id: "2", + firstName: "Johny", + lastName: "Cage", + }, + albums: [ + { + id: "1", + name: "My life", + }, + { + id: "2", + name: "My young years", + }, + ], + metadata: "I like it", + }; + + const photo = plainToInstance(photoPlain, Photo, ["Vibrant"]); + + assertInstanceOf(photo, Photo); + assertEquals(photo.id, 1); // Should be converted to number + assertEquals(photo.filename, "myphoto.jpg"); + assertEquals(photo.description, "about my photo"); + assertEquals(photo.tags, ["me", "iam"]); + assertEquals(photo.metadata, null); // metadata is toPlainOnly, so not transformed from plain + assertEquals(photo.mood, "Vibrant"); + + assertInstanceOf(photo.author, User); + assertEquals(photo.author.id, 2); + assertEquals(photo.author.firstName, "Johny"); + assertEquals(photo.author.lastName, "Cage"); + + assertEquals(photo.albums.length, 2); + assertInstanceOf(photo.albums[0], Album); + assertEquals(photo.albums[0].id, 1); + assertEquals(photo.albums[0].name, "My life"); +}); + +Deno.test("Photo class - instance to plain transformation", () => { + const photo = new Photo("Joyful"); + photo.id = 123; + photo.filename = "test.jpg"; + photo.description = "test description"; + photo.metadata = "test metadata"; + photo.year = 2020; + + const plain = instanceToPlain(photo); + + assertEquals(plain.id, 123); + assertEquals(plain.rawFilename, "test.jpg"); // Should use alias + assertEquals(plain.description, "test description"); + assertEquals(plain.metadata, "test metadata"); + // year is not exposed, so not serialized + assertEquals(plain.year, undefined); + // mood should not be serialized as it's not exposed + assertEquals(plain.mood, undefined); +}); + +Deno.test("Photo class - roundtrip transformation", () => { + const originalPlain = { + id: "42", + rawFilename: "roundtrip.jpg", + description: "roundtrip test", + metadata: "roundtrip metadata", + author: { + id: "1", + firstName: "Test", + lastName: "User", + }, + albums: [ + { + id: "1", + name: "Test Album", + }, + ], + }; + + const photo = plainToInstance(originalPlain, Photo, ["Happy"]); + const backToPlain = instanceToPlain(photo); + + assertEquals(backToPlain.id, 42); + assertEquals(backToPlain.rawFilename, "roundtrip.jpg"); + assertEquals(backToPlain.description, "roundtrip test"); + assertEquals(backToPlain.metadata, null); // metadata is toPlainOnly, not transformed from plain + assertEquals(backToPlain.author.id, 1); + assertEquals(backToPlain.author.firstName, "Test"); + assertEquals(backToPlain.author.lastName, "User"); + assertEquals(backToPlain.albums[0].id, 1); + assertEquals(backToPlain.albums[0].name, "Test Album"); +}); + +Deno.test("Photo array - plainsToInstances", () => { + const photosPlain = [ + { + id: "1", + rawFilename: "photo1.jpg", + author: { + id: "2", + firstName: "Johny", + lastName: "Cage", + }, + albums: [ + { + id: "1", + name: "My life", + }, + ], + }, + { + id: "2", + rawFilename: "photo2.jpg", + description: "second photo", + author: { + id: "3", + firstName: "Jane", + lastName: "Doe", + }, + albums: [], + }, + ]; + + const photos = plainsToInstances(photosPlain, Photo, ["Party"]); + + assertEquals(photos.length, 2); + + assertInstanceOf(photos[0], Photo); + assertEquals(photos[0].id, 1); + assertEquals(photos[0].filename, "photo1.jpg"); + assertEquals(photos[0].mood, "Party"); + + assertInstanceOf(photos[1], Photo); + assertEquals(photos[1].id, 2); + assertEquals(photos[1].filename, "photo2.jpg"); + assertEquals(photos[1].description, "second photo"); + assertEquals(photos[1].mood, "Party"); +}); + +Deno.test("Photo array - instancesToPlains", () => { + const photo1 = new Photo("Calm"); + photo1.id = 1; + photo1.filename = "test1.jpg"; + + const photo2 = new Photo("Excited"); + photo2.id = 2; + photo2.filename = "test2.jpg"; + photo2.description = "second test"; + + const photos = [photo1, photo2]; + const plains = instancesToPlains(photos); + + assertEquals(plains.length, 2); + assertEquals(plains[0].id, 1); + assertEquals(plains[0].rawFilename, "test1.jpg"); + assertEquals(plains[1].id, 2); + assertEquals(plains[1].rawFilename, "test2.jpg"); + assertEquals(plains[1].description, "second test"); +}); + +Deno.test("TimeRange class - date transformation", () => { + const plain = { + startTimestamp: "February 12, 2024 12:30:00", + endTimestamp: 1613477400000, + }; + + const instance = plainToInstance(plain, TimeRange, []); + + assertInstanceOf(instance, TimeRange); + assertEquals(instance.startTimestamp, "February 12, 2024 12:30:00"); + assertEquals(instance.endTimestamp, 1613477400000); + + // Test computed properties + assertInstanceOf(instance.start, Date); + assertInstanceOf(instance.end, Date); + assertEquals(instance.end.getTime(), 1613477400000); +}); + +Deno.test("User class - default values", () => { + const userPlain = { + id: "5", + }; + + const user = plainToInstance(userPlain, User, [30]); + + assertInstanceOf(user, User); + assertEquals(user.id, 5); + assertEquals(user.firstName, "5050"); // Default value converted to string + assertEquals(user.lastName, "PLACEHOLDER"); // Default value + assertEquals(user.age, 30); // Constructor argument +}); + +Deno.test("Album class - simple transformation", () => { + const albumPlain = { + id: "10", + name: "Test Album", + }; + + const album = plainToInstance(albumPlain, Album, []); + + assertInstanceOf(album, Album); + assertEquals(album.id, 10); + assertEquals(album.name, "Test Album"); +}); + +Deno.test("Photo class - empty object with defaults", () => { + const photo = plainToInstance({}, Photo, ["Silent"]); + + assertInstanceOf(photo, Photo); + assertEquals(photo.id, 0); // Default value + assertEquals(photo.filename, "BASE-FILENAME"); // Default value + assertEquals(photo.mood, "Silent"); // Constructor argument + assertEquals(photo.year, 1970); // Class field default +}); + +Deno.test("Photo class - alias functionality", () => { + const photoPlain = { + rawFilename: "aliased.jpg", // Using the alias + }; + + const photo = plainToInstance(photoPlain, Photo, ["Test"]); + assertEquals(photo.filename, "aliased.jpg"); // Should map to filename property + + const backToPlain = instanceToPlain(photo); + assertEquals(backToPlain.rawFilename, "aliased.jpg"); // Should use alias in output + assertEquals(backToPlain.filename, undefined); // Original property name shouldn't appear +}); + +Deno.test("Photo class - getter methods work", () => { + const photo = new Photo("Test"); + photo.id = 123; + photo.filename = "test.jpg"; + + assertEquals(photo.name, "123_test.jpg"); // Test getter + + // Test method (should not be serialized) + const albums = photo.getAlbums(); + assertEquals(albums, photo.albums); +}); From c0d25b2e6aa83e16b6fb320fb8b38ffcb461654b Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:56:08 +0900 Subject: [PATCH 08/12] Ignore lock files from Git --- .gitignore | 3 ++ deno.lock | 99 ------------------------------------------------------ 2 files changed, 3 insertions(+), 99 deletions(-) delete mode 100644 deno.lock diff --git a/.gitignore b/.gitignore index 8ebae9b..b1ef735 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Custom +*.lock + # Log files logs *.log diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 8807e4c..0000000 --- a/deno.lock +++ /dev/null @@ -1,99 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@std/assert@1": "1.0.14", - "jsr:@std/internal@^1.0.10": "1.0.10" - }, - "jsr": { - "@std/assert@1.0.14": { - "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/internal@1.0.10": { - "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" - } - }, - "remote": { - "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", - "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", - "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", - "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", - "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", - "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", - "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", - "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", - "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", - "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", - "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", - "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", - "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", - "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", - "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", - "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", - "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", - "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", - "https://deno.land/std@0.181.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", - "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", - "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", - "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", - "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", - "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", - "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", - "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", - "https://deno.land/x/deno_cache@0.6.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", - "https://deno.land/x/deno_cache@0.6.2/cache.ts": "58b53c128b742757efcad10af9a3871f23b4e200674cb5b0ddf61164fb9b2fe7", - "https://deno.land/x/deno_cache@0.6.2/deno_dir.ts": "1ea355b8ba11c630d076b222b197cfc937dd81e5a4a260938997da99e8ff93a0", - "https://deno.land/x/deno_cache@0.6.2/deps.ts": "12cca94516cf2d3ed42fccd4b721ecd8060679253f077d83057511045b0081aa", - "https://deno.land/x/deno_cache@0.6.2/dirs.ts": "009c6f54e0b610914d6ce9f72f6f6ccfffd2d47a79a19061e0a9eb4253836069", - "https://deno.land/x/deno_cache@0.6.2/disk_cache.ts": "66a1e604a8d564b6dd0500326cac33d08b561d331036bf7272def80f2f7952aa", - "https://deno.land/x/deno_cache@0.6.2/file_fetcher.ts": "4f3e4a2c78a5ca1e4812099e5083f815a8525ab20d389b560b3517f6b1161dd6", - "https://deno.land/x/deno_cache@0.6.2/http_cache.ts": "407135eaf2802809ed373c230d57da7ef8dff923c4abf205410b9b99886491fd", - "https://deno.land/x/deno_cache@0.6.2/lib/deno_cache_dir.generated.js": "59f8defac32e8ebf2a30f7bc77e9d88f0e60098463fb1b75e00b9791a4bbd733", - "https://deno.land/x/deno_cache@0.6.2/lib/snippets/deno_cache_dir-a2aecaa9536c9402/fs.js": "cbe3a976ed63c72c7cb34ef845c27013033a3b11f9d8d3e2c4aa5dda2c0c7af6", - "https://deno.land/x/deno_cache@0.6.2/mod.ts": "b4004287e1c6123d7f07fe9b5b3e94ce6d990c4102949a89c527c68b19627867", - "https://deno.land/x/deno_cache@0.6.2/util.ts": "f3f5a0cfc60051f09162942fb0ee87a0e27b11a12aec4c22076e3006be4cc1e2", - "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", - "https://deno.land/x/dnt@0.40.0/lib/compiler.ts": "7f4447531581896348b8a379ab94730856b42ae50d99043f2468328360293cb1", - "https://deno.land/x/dnt@0.40.0/lib/compiler_transforms.ts": "f21aba052f5dcf0b0595c734450842855c7f572e96165d3d34f8fed2fc1f7ba1", - "https://deno.land/x/dnt@0.40.0/lib/mod.deps.ts": "8d6123c8e1162037e58aa8126686a03d1e2cffb250a8757bf715f80242097597", - "https://deno.land/x/dnt@0.40.0/lib/npm_ignore.ts": "57fbb7e7b935417d225eec586c6aa240288905eb095847d3f6a88e290209df4e", - "https://deno.land/x/dnt@0.40.0/lib/package_json.ts": "607b0a4f44acad071a4c8533b312a27d6671eac8e6a23625c8350ce29eadb2ba", - "https://deno.land/x/dnt@0.40.0/lib/pkg/dnt_wasm.generated.js": "2694546844a50861d6d1610859afbf5130baca4dc6cf304541b7ec2d6d998142", - "https://deno.land/x/dnt@0.40.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "aba69a019a6da6f084898a6c7b903b8b583bc0dbd82bfb338449cf0b5bce58fd", - "https://deno.land/x/dnt@0.40.0/lib/shims.ts": "39e5c141f0315c0faf30b479b53f92b9078d92e1fd67ee34cc60b701d8e68dab", - "https://deno.land/x/dnt@0.40.0/lib/test_runner/get_test_runner_code.ts": "4dc7a73a13b027341c0688df2b29a4ef102f287c126f134c33f69f0339b46968", - "https://deno.land/x/dnt@0.40.0/lib/test_runner/test_runner.ts": "4d0da0500ec427d5f390d9a8d42fb882fbeccc92c92d66b6f2e758606dbd40e6", - "https://deno.land/x/dnt@0.40.0/lib/transform.deps.ts": "2e159661e1c5c650de9a573babe0e319349fe493105157307ec2ad2f6a52c94e", - "https://deno.land/x/dnt@0.40.0/lib/types.ts": "b8e228b2fac44c2ae902fbb73b1689f6ab889915bd66486c8a85c0c24255f5fb", - "https://deno.land/x/dnt@0.40.0/lib/utils.ts": "224f15f33e7226a2fd991e438d0291d7ed8c7889807efa2e1ecb67d2d1db6720", - "https://deno.land/x/dnt@0.40.0/mod.ts": "ae1890fbe592e4797e7dd88c1e270f22b8334878e9bf187c4e11ae75746fe778", - "https://deno.land/x/dnt@0.40.0/transform.ts": "f68743a14cf9bf53bfc9c81073871d69d447a7f9e3453e0447ca2fb78926bb1d", - "https://deno.land/x/ts_morph@20.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", - "https://deno.land/x/ts_morph@20.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", - "https://deno.land/x/ts_morph@20.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", - "https://deno.land/x/ts_morph@20.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", - "https://deno.land/x/ts_morph@20.0.0/common/ts_morph_common.js": "2325f94f61dc5f3f98a1dab366dc93048d11b1433d718b10cfc6ee5a1cfebe8f", - "https://deno.land/x/ts_morph@20.0.0/common/typescript.js": "b9edf0a451685d13e0467a7ed4351d112b74bd1e256b915a2b941054e31c1736", - "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed", - "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63" - }, - "workspace": { - "dependencies": [ - "jsr:@std/assert@1" - ] - } -} From 0a4a08deb53617c846be95942cd2bd085ab86425 Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 14:59:07 +0900 Subject: [PATCH 09/12] Rename GH Actions files --- ...nuous_integration.yml => code_quality.yml} | 0 .github/workflows/user_installation.yml | 26 ------------------- 2 files changed, 26 deletions(-) rename .github/workflows/{continuous_integration.yml => code_quality.yml} (100%) delete mode 100644 .github/workflows/user_installation.yml diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/code_quality.yml similarity index 100% rename from .github/workflows/continuous_integration.yml rename to .github/workflows/code_quality.yml diff --git a/.github/workflows/user_installation.yml b/.github/workflows/user_installation.yml deleted file mode 100644 index af0d366..0000000 --- a/.github/workflows/user_installation.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: user-installation - -on: - workflow_dispatch: - schedule: - - cron: "*/10 * * * *" - -jobs: - build: - name: build - runs-on: ubuntu-latest - strategy: - matrix: - node-version: ["16", "18", "20"] - fail-fast: false - defaults: - run: - working-directory: ./sample - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm uninstall class-transform - - run: npm install class-transform - - run: node index.js From 4540c8f7840fef0295d0aaa33afb542cbf3e139d Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 15:02:12 +0900 Subject: [PATCH 10/12] Check code quality in CI --- .github/workflows/code_quality.yml | 35 ++++++------------------------ 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 88615af..b9d642f 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -3,33 +3,12 @@ name: continuous-integration on: [push, pull_request] jobs: - lint: - name: lint + check: + name: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: 20 - - run: npm install - - run: npm run check - run: - name: run - runs-on: ubuntu-latest - strategy: - matrix: - node-version: ["16", "18", "20"] - fail-fast: false - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npx tsc - - run: npm install - working-directory: ./sample - - run: npx tsc - working-directory: ./sample - - run: node index.js - working-directory: ./sample + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + - run: deno test + - run: deno lint + - run: deno fmt --check From 3648fe3f73d981aaf6881dba85637123b95f6ab2 Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 15:03:19 +0900 Subject: [PATCH 11/12] Change the job name --- .github/workflows/code_quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index b9d642f..6004617 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: check: - name: test + name: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 587b7d05fb7541db81c7581b5cfa98edbcdeb72f Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Fri, 3 Oct 2025 15:07:15 +0900 Subject: [PATCH 12/12] Remove a badge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index c4e4f27..f3da07a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # `class-transform` -[![Continuous Integration](https://github.com/cunarist/class-transform/workflows/CI/badge.svg)](https://github.com/cunarist/class-transform/actions/workflows/continuous_integration.yml) [![NPM Version](https://img.shields.io/npm/v/class-transform)](https://badge.fury.io/js/class-transform) [![Minified Size](https://img.shields.io/bundlejs/size/class-transform)](https://bundlejs.com/?q=class-transform)