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/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml new file mode 100644 index 0000000..6004617 --- /dev/null +++ b/.github/workflows/code_quality.yml @@ -0,0 +1,14 @@ +name: continuous-integration + +on: [push, pull_request] + +jobs: + check: + name: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + - run: deno test + - run: deno lint + - run: deno fmt --check diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml deleted file mode 100644 index 88615af..0000000 --- a/.github/workflows/continuous_integration.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: continuous-integration - -on: [push, pull_request] - -jobs: - lint: - name: lint - 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 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 diff --git a/.gitignore b/.gitignore index bd76370..b1ef735 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Custom +*.lock + # Log files logs *.log @@ -16,6 +19,9 @@ coverage/** # Dependency directories node_modules +# NPM build output (from deno task build) +npm/ + # MacOS related files *.DS_Store .AppleDouble 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/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 new file mode 100644 index 0000000..86cea06 --- /dev/null +++ b/DENO_README.md @@ -0,0 +1,88 @@ +# 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..f3da07a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # `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) -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 +34,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 +60,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 JavaScript 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 +96,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 +106,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 +128,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 @@ -163,7 +161,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` | `[]` | @@ -176,8 +174,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,17 +185,15 @@ 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, plainToInstance, instanceToPlain } from "class-transform"; +import { Exposed, instanceToPlain, plainToInstance } from "class-transform"; class User { id = Exposed.number(); // number | null @@ -235,27 +231,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,12 +294,12 @@ 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, plainToInstance, instanceToPlain } from "class-transform"; +import { Exposed, instanceToPlain, plainToInstance } from "class-transform"; class User { firstName = Exposed.alias("first_name_raw").string(); @@ -323,14 +317,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 +341,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 +359,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 +421,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 new file mode 100644 index 0000000..8ac44bd --- /dev/null +++ b/deno.json @@ -0,0 +1,34 @@ +{ + "name": "@cunarist/class-transform", + "version": "0.7.0", + "description": "Transformation between plain objects and class instances", + "exports": "./src/index.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/cunarist/class-transform.git" + }, + "compilerOptions": { + "lib": ["deno.window", "ES2015"] + }, + "lint": { + "rules": { + "exclude": ["no-explicit-any"] + } + }, + "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.ts" + }, + "imports": { + "@deno/dnt": "https://deno.land/x/dnt@0.40.0/mod.ts", + "@std/assert": "jsr:@std/assert@1" + } +} 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 deleted file mode 100644 index 4cfd41e..0000000 --- a/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "class-transform", - "version": "0.7.0", - "description": "Transformation between plain JavaScript 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" - } -} 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.js b/sample/classes.js deleted file mode 100644 index 8e00bd1..0000000 --- a/sample/classes.js +++ /dev/null @@ -1,57 +0,0 @@ -// @ts-check - -import { Exposed } from "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"); - - /** @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/index.js b/sample/index.js deleted file mode 100644 index 4aad3dd..0000000 --- a/sample/index.js +++ /dev/null @@ -1,129 +0,0 @@ -import { - plainToInstance, - plainsToInstances, - instanceToPlain, - instancesToPlains, -} from "class-transform"; -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/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 ffde148..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/scripts/build_npm.ts b/scripts/build_npm.ts new file mode 100644 index 0000000..a941909 --- /dev/null +++ b/scripts/build_npm.ts @@ -0,0 +1,56 @@ +#!/usr/bin/env -S deno run -A + +import { build, emptyDir } from "@deno/dnt"; + +await emptyDir("./npm"); + +await build({ + entryPoints: ["./src/index.ts"], + 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 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..1b17591 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,22 @@ +// Simple test to verify the library works correctly +import { Exposed, instanceToPlain, plainToInstance } from "../src/index.ts"; + +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/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 8ef9cd2..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 { - plainsToInstances, instancesToPlains, - plainToInstance, instanceToPlain, -} from "./to.js"; + plainsToInstances, + plainToInstance, +} 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 949c165..12cfe18 100644 --- a/lib/to.js +++ b/src/to.ts @@ -1,67 +1,54 @@ -import { Exposed } from "./exposed.js"; -import { whileExposing, Direction } 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; 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); +}); 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. */ - } -}