Gathering detailed insights and metrics for ts-keys-turn
Gathering detailed insights and metrics for ts-keys-turn
Gathering detailed insights and metrics for ts-keys-turn
Gathering detailed insights and metrics for ts-keys-turn
A TypeScript custom transformer which enables to obtain keys of given type
npm install ts-keys-turn
Typescript
Module System
Node Version
NPM Version
TypeScript (98.56%)
JavaScript (1.44%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
220 Commits
1 Branches
1 Contributors
Updated on Jun 05, 2023
Latest Version
0.5.10
Package Id
ts-keys-turn@0.5.10
Unpacked Size
24.05 kB
Size
8.20 kB
File Count
8
NPM Version
8.1.0
Node Version
16.12.0
Published on
Jun 16, 2023
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
5
A TypeScript custom transformer which enables to obtain keys of given type.
TypeScript >= 2.4.1
Object.keys
and Object.getOwnPropertyNames
methods return string[]
type instead of (keyof obj)[]
expected by the naming. The reason why this is done is that the types of objects in typescript are covariant and may implicitly contain supersets of other types. Therefore, they can lead to the fact that in runtime - when calling Object.keys
- we can get as a result keys that are not covered by typescript types. For example consider this one that lead to runtime error while typescript thinks that everything is fine.
This package presents safe workaround.
The package contains function keyTransform
for transformation Object.getOwnPropertyNames<typeof o>(o)
expression to array of keys in result file and overriden type for ObjectConstructor.getOwnPropertyNames
that returns Array<keyof T>
for safe transformation cases, whenever possible, else - string[]
. For example:
1let ab = { a: 1, b: 1 } as const 2let abc = { a: 1, b: 1, c: 1 } as const 3ab = abc 4let ks = Object.getOwnPropertyNames<typeof ab>(ab) // ("a" | "b")[]
will be converted (in compile time) to:
1var ab = { a: 1, b: 1 }; 2var abc = { a: 1, b: 1, c: 1 }; 3ab = abc; 4var ks_2 = ["a", "b"]; // ["a", "b"] is matches with type
instead of:
1var ab = { a: 1, b: 1 }; 2var abc = { a: 1, b: 1, c: 1 }; 3ab = abc; 4let keys = Object.getOwnPropertyNames(ab) // <- ['a', 'b', 'c'] <- missmatch with typescript
There are several limitations for security and transparency reasons. The getOwnPropertyNames
method return (keyof typeof obj)[]
instead of string[]
and makes appropriate transformation only when the following rules are followed:
Generic type should be explicitly specified in the calling signature. It's kind of a way to choose exactly how to handle the construction during development:
1let ae = { a: 1, b: 1 }
2let strs = Object.getOwnPropertyNames(ae) // string[]
3let keys = Object.getOwnPropertyNames<typeof ae>(ae) // (keyof AE)[]
The type should contains just required fields to avoid the discrepancy of the list of fields with the runtime:
with optional fields:
1type A = { a: 1, b?: 1 } 2let ae: A = { a: 1, b: 1 } 3let ks = Object.getOwnPropertyNames<A>(ae) // string[]
with required fields:
1type A = { a: 1, b: 1 } 2let ae: A = { a: 1, b: 1 } 3let ks = Object.getOwnPropertyNames<A>(ae) // (keyof AE)[]
The type should not be union for the same reason:
1type U = { a: 1 } | { a: 1, b: 1 } 2let aa: U = { a: 1, b: 1 } 3let k = Object.getOwnPropertyNames<U>(aa) // string[]
Properly using the package consists of the three following steps (both of them required!):
Installation:
npm i -D Sanshain/ts-keys-turn
or
npm i -D ts-keys-turn
Addition of the package to the include section of your tscofig.json
(if node_modules
didn't...):
"include": [
// ...
"node_modules/ts-keys-turn"
]
Tuning custom transformer which is used to compile the keys
function correctly: look up point "How to use the custom transformer":
Unfortunately, TypeScript itself does not currently provide any way to use custom transformers by tsconfig.json (See https://github.com/Microsoft/TypeScript/issues/14419) and requires using itself API. The followings are the usage examples of the API with the transformer for the most common cases:
See examples/webpack for detail.
1// webpack.config.js 2const keysTransformer = require('ts-transformer-keys/transformer').default; 3 4module.exports = { 5 // ... 6 module: { 7 rules: [ 8 { 9 test: /\.ts$/, 10 loader: 'ts-loader', // or 'awesome-typescript-loader' 11 options: { 12 // make sure not to set `transpileOnly: true` here, otherwise it will not work 13 getCustomTransformers: program => ({ 14 before: [ 15 keysTransformer(program) 16 ] 17 }) 18 } 19 } 20 ] 21 } 22}; 23
See examples/rollup for detail.
1// rollup.config.js 2import resolve from 'rollup-plugin-node-resolve'; 3import typescript from 'rollup-plugin-typescript2'; 4import keysTransformer from 'ts-transformer-keys/transformer'; 5 6export default { 7 // ... 8 plugins: [ 9 resolve(), 10 typescript({ transformers: [service => ({ 11 before: [ keysTransformer(service.getProgram()) ], 12 after: [] 13 })] }) 14 ] 15};
See examples/ttypescript for detail. See ttypescript's README for how to use this with module bundlers such as webpack or Rollup.
1// tsconfig.json 2{ 3 "compilerOptions": { 4 // ... 5 "plugins": [ 6 { "transform": "ts-transformer-keys/transformer" } 7 ] 8 }, 9 // ... 10}
See examples/ts-jest for details. In order to use this transformer with ts-jest, you need to add a wrapper around it like this:
1// ts-jest-keys-transformer.js 2const keysTransformer = require('ts-transformer-keys/transformer').default; 3const name = 'my-key-transformer'; 4const version = 1; 5const factory = (cs) => (ctx) => keysTransformer(cs.program)(ctx); 6// For ts-jest 26 use: 7// const factory = (cs) => (ctx) => keysTransformer(cs.tsCompiler.program)(ctx); 8 9module.exports = { name, version, factory };
And add it in jest.config.js
like this:
1 globals: { 2 'ts-jest': { 3 // relative path to the ts-jest-keys-transformer.js file 4 astTransformers: { before: ['src/react/ts-jest-keys-transformer.js'] }, 5 }, 6 },
Note: ts-jest 26.4.2 does not work with this transformer (fixed in ts-jest 26.4.3). Also, for versions smaller than 26.2, you need to provide the transformer in an array instead, like this: astTransformers: { before: ['src/react/ts-jest-keys-transformer.js'] }
See test for detail.
You can try it with $ npm test
.
1const ts = require('typescript'); 2const keysTransformer = require('ts-transformer-keys/transformer').default; 3 4const program = ts.createProgram([/* your files to compile */], { 5 strict: true, 6 noEmitOnError: true, 7 target: ts.ScriptTarget.ES5 8}); 9 10const transformers = { 11 before: [keysTransformer(program)], 12 after: [] 13}; 14const { emitSkipped, diagnostics } = program.emit(undefined, undefined, undefined, false, transformers); 15 16if (emitSkipped) { 17 throw new Error(diagnostics.map(diagnostic => diagnostic.messageText).join('\n')); 18}
As a result, the TypeScript code shown here is compiled into the following JavaScript.
1"use strict"; 2Object.defineProperty(exports, "__esModule", { value: true }); 3var ts_transformer_keys_1 = require("ts-transformer-keys"); 4var keysOfProps = ["id", "name", "age"]; 5console.log(keysOfProps); // ['id', 'name', 'age']
Object.keys
?This package by default is configurated to use Object.getOwnPropertyNames
signature for keys transformation instead of Object.keys
. Why?
The difference among the methods is that Object.keys
, unlike Object.getOwnPropertyNames
, returns only enumerated properties. It is important to note that typescript itself cannot control the enumerability of properties, since javascript in runtime allows you to change it for those of them that do not have the configurable: false
clause set (that is, all properties for which it is not explicitly set). Therefore ts cann't detect the p-roperty state. However, using these properties inside source ts code even outside the enumeration supposes that the fields will still be explicitly described in types, rather than not at all.
Object.keys
more preferred?Despite the ways to make non-enumerable fields in runtime (via object.create
or modification descriptor from getPropertyDescriptor
), many developers prefer not to use this feature to make the code more obvious.
Therefore, for them there is no difference, except that the keys
consists of a less number of letters. For such cases, it may be reasonably to use keys
method for transformations and at all.
keys
for transformataion requires the following steps:install the package npm i -D ts-keys-turn
specify path for keys.d.ts
instead of node_modules/ts-keys-compiler
at include option of your tsconfig.json
:
1"include": [ 2 "node_modules/ts-keys-compiler/sources/keys.d.ts" 3],
pass keys
as methodName option to transform function (look up how to use the custom transformer if missed):
1keysTransform(program, {methodName: 'keys'})
On the tested hardware, for 40 files with 1600 lines of ts code (i.e. 64 thousand lines of code, respectively), and 5 corresponding constructs for transformation in each file, the difference in compilation speed did not exceed 10% (~1.550 sec vs ~1.700 sec). But even if it has weight, you can use the transformer only for production mode like this:
1typescript({ 2 transformers: production ? [service => { 3 const program = service.getProgram() 4 return { 5 before: program ? [keysTransform(program)] : [], 6 after: [] 7 } 8 }] : [] 9})
MIT
No vulnerabilities found.
No security vulnerabilities found.