@so0shka/ts-mask 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +89 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +41 -0
- package/dist/utils.js.map +1 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 So0shka
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# TypeScript Mask Library
|
|
2
|
+
|
|
3
|
+
A simple library to define and infer types for object fields using "masks" in TypeScript. This library allows you to specify which fields of an object should be selected, while automatically inferring the corresponding types.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install via npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install ts-mask
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Comprehensive example:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createMask, MaskInfer } from "ts-mask";
|
|
19
|
+
|
|
20
|
+
interface IDocument {
|
|
21
|
+
id: number;
|
|
22
|
+
number: string;
|
|
23
|
+
author: {
|
|
24
|
+
id: number;
|
|
25
|
+
fullname: string;
|
|
26
|
+
};
|
|
27
|
+
items: {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const productMask = createMask((p: MaskProvider<IDocument>) => [
|
|
34
|
+
p.id,
|
|
35
|
+
p.author,
|
|
36
|
+
p.items.mask((i) => [i.name]),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
productMask.toObject();
|
|
40
|
+
|
|
41
|
+
// {
|
|
42
|
+
// id: Symbol(MARKED),
|
|
43
|
+
// author: Symbol(MARKED),
|
|
44
|
+
// items: {
|
|
45
|
+
// name: Symbol(MARKED),
|
|
46
|
+
// }
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
type PartialProduct = MaskInfer<typeof productMask>;
|
|
50
|
+
// type PartialProduct = {
|
|
51
|
+
// id: number;
|
|
52
|
+
// author: {
|
|
53
|
+
// id: number;
|
|
54
|
+
// fullname: string;
|
|
55
|
+
// };
|
|
56
|
+
// items: {
|
|
57
|
+
// name: string;
|
|
58
|
+
// }[];
|
|
59
|
+
// };
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### createMask function
|
|
65
|
+
|
|
66
|
+
Creates a mask for a given object type. It accepts a builder function that defines which fields should be included in the mask.
|
|
67
|
+
|
|
68
|
+
The created mask has a `toObject` method that returns the object representation of the mask, where each field is marked with a `markedSymbol`.
|
|
69
|
+
|
|
70
|
+
#### Example:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
const mask = createMask((p: MaskProvider<MyObject>) => [p.field1, p.field2]);
|
|
74
|
+
mask.toObject();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### MaskInfer type
|
|
78
|
+
|
|
79
|
+
This utility type is used to infer the partial type of the object based on the mask created by `createMask`.
|
|
80
|
+
|
|
81
|
+
#### Example:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
type MyObjectPartial = MaskInfer<typeof mask>;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MarkedSymbol = Symbol("MARKED");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,YAAY,GAAkB,MAAM,CAAC,QAAQ,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { MarkedSymbolType } from "./constants.js";
|
|
2
|
+
type Prettify<T> = {
|
|
3
|
+
[K in keyof T]: T[K];
|
|
4
|
+
} & {};
|
|
5
|
+
type NonFunctionPropertyNames<T> = {
|
|
6
|
+
[K in keyof T]: T[K] extends Function ? never : K;
|
|
7
|
+
}[keyof T];
|
|
8
|
+
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
|
|
9
|
+
type ValidKeys<ObjectT> = Extract<keyof NonFunctionProperties<ObjectT>, string | number>;
|
|
10
|
+
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
11
|
+
export type MaskTree = {
|
|
12
|
+
[key: string]: MaskTree | MarkedSymbolType;
|
|
13
|
+
};
|
|
14
|
+
export interface IMask<ObjectT, FieldTupleT extends readonly MaskAnyTerm<ObjectT>[]> {
|
|
15
|
+
toObject: () => MaskTree;
|
|
16
|
+
}
|
|
17
|
+
export type MaskBuilderFunc<ItemT, FieldTupleT extends readonly any[]> = (provider: MaskProvider<ItemT>) => FieldTupleT;
|
|
18
|
+
export declare class MaskPlainTerm<ObjectT, KeyT extends ValidKeys<ObjectT>> {
|
|
19
|
+
readonly key: KeyT;
|
|
20
|
+
constructor(key: KeyT);
|
|
21
|
+
}
|
|
22
|
+
export declare class MaskCompositeTerm<ObjectT, KeyT extends ValidKeys<ObjectT>, ItemT> {
|
|
23
|
+
readonly key: KeyT;
|
|
24
|
+
constructor(key: KeyT);
|
|
25
|
+
mask<FieldTupleT extends readonly any[]>(builderFn: MaskBuilderFunc<ItemT, FieldTupleT>): MaskCompiledTerm<ObjectT, KeyT, MaskDef<ItemT, FieldTupleT>>;
|
|
26
|
+
}
|
|
27
|
+
export type MaskProvider<ObjectT> = {
|
|
28
|
+
[KeyT in ValidKeys<ObjectT>]: ObjectT[KeyT] extends readonly (infer ItemT)[] ? MaskCompositeTerm<ObjectT, KeyT, ItemT> : ObjectT[KeyT] extends object ? MaskCompositeTerm<ObjectT, KeyT, ObjectT[KeyT]> : MaskPlainTerm<ObjectT, KeyT>;
|
|
29
|
+
};
|
|
30
|
+
export declare class MaskDef<ObjectT, FieldTupleT extends readonly MaskAnyTerm<ObjectT>[]> {
|
|
31
|
+
}
|
|
32
|
+
export declare class MaskCompiledTerm<ObjectT, KeyT extends ValidKeys<ObjectT>, MaskDefT extends MaskDef<ObjectT[KeyT], any>> {
|
|
33
|
+
readonly key: KeyT;
|
|
34
|
+
constructor(key: KeyT);
|
|
35
|
+
}
|
|
36
|
+
export type MaskAnyTerm<ObjectT> = MaskPlainTerm<ObjectT, any> | MaskCompositeTerm<ObjectT, any, any> | MaskCompiledTerm<ObjectT, any, any>;
|
|
37
|
+
export type MaskDefInfer<T> = T extends MaskDef<infer ObjectT, infer FieldTupleT> ? FieldTupleT extends readonly MaskAnyTerm<ObjectT>[] ? Prettify<FieldsTupleToObject<ObjectT, FieldTupleT>> : never : never;
|
|
38
|
+
type MaskAnyTermToObject<TermT extends MaskAnyTerm<any>> = TermT extends MaskPlainTerm<infer ObjectT, infer KeyT> | MaskCompositeTerm<infer ObjectT, infer KeyT, any> ? {
|
|
39
|
+
[K in KeyT]: ObjectT[K];
|
|
40
|
+
} : TermT extends MaskCompiledTerm<infer ObjectT, infer KeyT, infer MaskDefT> ? ObjectT[KeyT] extends readonly any[] ? {
|
|
41
|
+
[K in KeyT]: MaskDefInfer<MaskDefT>[];
|
|
42
|
+
} : {
|
|
43
|
+
[K in KeyT]: MaskDefInfer<MaskDefT>;
|
|
44
|
+
} : never;
|
|
45
|
+
type FieldsTupleToObject<ObjectT, TupleT extends readonly MaskAnyTerm<ObjectT>[]> = UnionToIntersection<MaskAnyTermToObject<TupleT[number]>>;
|
|
46
|
+
export type MaskInfer<T> = T extends IMask<infer ObjectT, infer FieldTupleT> ? MaskDefInfer<MaskDef<ObjectT, FieldTupleT>> : never;
|
|
47
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class MaskPlainTerm {
|
|
2
|
+
constructor(key) {
|
|
3
|
+
this.key = key;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export class MaskCompositeTerm {
|
|
7
|
+
constructor(key) {
|
|
8
|
+
this.key = key;
|
|
9
|
+
}
|
|
10
|
+
mask(builderFn) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class MaskDef {
|
|
15
|
+
}
|
|
16
|
+
export class MaskCompiledTerm {
|
|
17
|
+
constructor(key) {
|
|
18
|
+
this.key = key;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAsCA,MAAa,aAAa;IACI;IAA5B,YAA4B,GAAS;QAAT,QAAG,GAAH,GAAG,CAAM;IAAG,CAAC;CAC1C;AAFD,sCAEC;AAED,MAAa,iBAAiB;IAKA;IAA5B,YAA4B,GAAS;QAAT,QAAG,GAAH,GAAG,CAAM;IAAG,CAAC;IAEzC,IAAI,CACF,SAA8C;QAE9C,OAAO,IAAW,CAAC;IACrB,CAAC;CACF;AAZD,8CAYC;AAUD,MAAa,OAAO;CAGhB;AAHJ,0BAGI;AAEJ,MAAa,gBAAgB;IAKC;IAA5B,YAA4B,GAAS;QAAT,QAAG,GAAH,GAAG,CAAM;IAAG,CAAC;CAC1C;AAND,4CAMC"}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MarkedSymbol } from "./constants.js";
|
|
2
|
+
class MaskBranch {
|
|
3
|
+
constructor(key, subtree) {
|
|
4
|
+
this.key = key;
|
|
5
|
+
this.subtree = subtree;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function createMask(maskBuilderFn) {
|
|
9
|
+
const proxy = createMaskProxy();
|
|
10
|
+
// Trick: public types are designed to trick the user and make inference work
|
|
11
|
+
const pickedTerms = maskBuilderFn(proxy);
|
|
12
|
+
const branchSubtree = compileBranchSubtree(pickedTerms);
|
|
13
|
+
return {
|
|
14
|
+
toObject() {
|
|
15
|
+
return branchSubtree;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function compileBranchSubtree(pickedTerms) {
|
|
20
|
+
const subtree = {};
|
|
21
|
+
for (const field of pickedTerms) {
|
|
22
|
+
subtree[field.key] =
|
|
23
|
+
field instanceof MaskBranch ? field.subtree : MarkedSymbol;
|
|
24
|
+
}
|
|
25
|
+
return subtree;
|
|
26
|
+
}
|
|
27
|
+
function createMaskProxy() {
|
|
28
|
+
return new Proxy(new Object(), {
|
|
29
|
+
get(_, propName) {
|
|
30
|
+
return {
|
|
31
|
+
key: propName,
|
|
32
|
+
mask: (maskBuilderFn) => {
|
|
33
|
+
const proxy = createMaskProxy();
|
|
34
|
+
const pickedTerms = maskBuilderFn(proxy);
|
|
35
|
+
const branchSubtree = compileBranchSubtree(pickedTerms);
|
|
36
|
+
return new MaskBranch(propName, branchSubtree);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;AAiBA,gCAiBC;AAlCD,2CAA2C;AAU3C,MAAM,UAAU;IAEL;IACA;IAFT,YACS,GAAoB,EACpB,OAAiB;QADjB,QAAG,GAAH,GAAG,CAAiB;QACpB,YAAO,GAAP,OAAO,CAAU;IACvB,CAAC;CACL;AAED,SAAgB,UAAU,CACxB,aAAoD;IAEpD,MAAM,KAAK,GAAG,eAAe,EAAW,CAAC;IAEzC,6EAA6E;IAC7E,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAEtC,CAAC;IAEF,MAAM,aAAa,GAAG,oBAAoB,CAAC,WAAkB,CAAC,CAAC;IAE/D,OAAO;QACL,QAAQ;YACN,OAAO,aAAa,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,WAA0C;IACtE,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;YAChB,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAY,CAAC;IAC/D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,IAAI,KAAK,CAAC,IAAI,MAAM,EAAE,EAAE;QAC7B,GAAG,CAAC,CAAC,EAAE,QAAgB;YACrB,OAAO;gBACL,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,CAAC,aAAwC,EAAE,EAAE;oBACjD,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;oBAChC,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;oBACzC,MAAM,aAAa,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;oBAExD,OAAO,IAAI,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;gBACjD,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAA0B,CAAC;AAC9B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@so0shka/ts-mask",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A TypeScript library for creating object field masks and inferring types from them",
|
|
5
|
+
"homepage": "https://github.com/So0shka/ts-mask#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/So0shka/ts-mask/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/So0shka/ts-mask.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "So0shka",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/**/*"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.2.0",
|
|
27
|
+
"tsx": "^4.21.0",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
}
|
|
30
|
+
}
|