@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 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,2 @@
1
+ export declare const MarkedSymbol: unique symbol;
2
+ export type MarkedSymbolType = typeof MarkedSymbol;
@@ -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"}
@@ -0,0 +1,3 @@
1
+ export { MarkedSymbol } from "./constants.js";
2
+ export { createMask } from "./utils.js";
3
+ export { IMask, MaskInfer, MaskTree, MaskProvider } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { MarkedSymbol } from "./constants.js";
2
+ export { createMask } from "./utils.js";
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
@@ -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"}
@@ -0,0 +1,2 @@
1
+ import { MaskBuilderFunc, IMask } from "./types.js";
2
+ export declare function createMask<ObjectT, FieldTupleT extends readonly any[]>(maskBuilderFn: MaskBuilderFunc<ObjectT, FieldTupleT>): IMask<ObjectT, FieldTupleT>;
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
+ }