typemold 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 +43 -0
- package/README.md +235 -0
- package/dist/cjs/decorators.js +284 -0
- package/dist/cjs/index.js +74 -0
- package/dist/cjs/mapper.js +153 -0
- package/dist/cjs/nestjs/index.js +12 -0
- package/dist/cjs/nestjs/mapper.module.js +103 -0
- package/dist/cjs/nestjs/mapper.service.js +161 -0
- package/dist/cjs/registry.js +179 -0
- package/dist/cjs/types.js +17 -0
- package/dist/cjs/utils.js +136 -0
- package/dist/esm/decorators.js +274 -0
- package/dist/esm/index.js +51 -0
- package/dist/esm/mapper.js +149 -0
- package/dist/esm/nestjs/index.js +6 -0
- package/dist/esm/nestjs/mapper.module.js +100 -0
- package/dist/esm/nestjs/mapper.service.js +125 -0
- package/dist/esm/registry.js +175 -0
- package/dist/esm/types.js +14 -0
- package/dist/esm/utils.js +127 -0
- package/dist/types/decorators.d.ts +206 -0
- package/dist/types/decorators.d.ts.map +1 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/mapper.d.ts +93 -0
- package/dist/types/mapper.d.ts.map +1 -0
- package/dist/types/nestjs/index.d.ts +7 -0
- package/dist/types/nestjs/index.d.ts.map +1 -0
- package/dist/types/nestjs/mapper.module.d.ts +89 -0
- package/dist/types/nestjs/mapper.module.d.ts.map +1 -0
- package/dist/types/nestjs/mapper.service.d.ts +80 -0
- package/dist/types/nestjs/mapper.service.d.ts.map +1 -0
- package/dist/types/registry.d.ts +60 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/types.d.ts +120 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils.d.ts +30 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chetan Prakash Joshi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
MIT License
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2026 Chetan Joshi
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# typemold
|
|
2
|
+
|
|
3
|
+
A **lightweight**, **high-performance** object mapper for NestJS with runtime field projection.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/typemold)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- ⚡ **High Performance** - Compiled mappers cached after first use (no runtime reflection)
|
|
11
|
+
- 🎯 **Runtime Field Projection** - Pick/omit fields without creating multiple DTOs
|
|
12
|
+
- 📦 **Lightweight** - < 5KB bundle, zero production dependencies
|
|
13
|
+
- 🏷️ **Field Groups** - Define reusable field sets with decorators
|
|
14
|
+
- 🔧 **NestJS Integration** - Full module support with DI
|
|
15
|
+
- ✅ **TypeScript First** - Full strict mode support
|
|
16
|
+
- 🔄 **Hybrid Validation** - Optional class-validator integration
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install typemold
|
|
22
|
+
|
|
23
|
+
# Peer dependency (already in NestJS projects)
|
|
24
|
+
npm install reflect-metadata
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Define Your DTO
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { AutoMap, MapFrom, FieldGroup } from "typemold";
|
|
33
|
+
|
|
34
|
+
class UserDto {
|
|
35
|
+
@AutoMap()
|
|
36
|
+
username: string;
|
|
37
|
+
|
|
38
|
+
@MapFrom("profile.avatar")
|
|
39
|
+
avatarUrl: string;
|
|
40
|
+
|
|
41
|
+
@MapFrom((src) => src.age >= 18)
|
|
42
|
+
isAdult: boolean;
|
|
43
|
+
|
|
44
|
+
@AutoMap()
|
|
45
|
+
email: string;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Map Objects
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Mapper } from "typemold";
|
|
53
|
+
|
|
54
|
+
// Basic mapping
|
|
55
|
+
const userDto = Mapper.map(userEntity, UserDto);
|
|
56
|
+
|
|
57
|
+
// Array mapping
|
|
58
|
+
const userDtos = Mapper.mapArray(users, UserDto);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Runtime Field Projection ⭐
|
|
62
|
+
|
|
63
|
+
**The killer feature** - reuse a single DTO across multiple endpoints:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Full user profile
|
|
67
|
+
Mapper.map(user, UserDto);
|
|
68
|
+
// Result: { username, avatarUrl, isAdult, email }
|
|
69
|
+
|
|
70
|
+
// Only username and avatar (shorthand)
|
|
71
|
+
Mapper.pick(user, UserDto, ["username", "avatarUrl"]);
|
|
72
|
+
// Result: { username, avatarUrl }
|
|
73
|
+
|
|
74
|
+
// Exclude sensitive fields
|
|
75
|
+
Mapper.omit(user, UserDto, ["email"]);
|
|
76
|
+
// Result: { username, avatarUrl, isAdult }
|
|
77
|
+
|
|
78
|
+
// Using options object
|
|
79
|
+
Mapper.map(user, UserDto, { pick: ["username", "avatarUrl"] });
|
|
80
|
+
Mapper.map(user, UserDto, { omit: ["email"] });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Field Groups
|
|
84
|
+
|
|
85
|
+
Define reusable field sets:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
class UserDto {
|
|
89
|
+
@FieldGroup("minimal", "public")
|
|
90
|
+
@AutoMap()
|
|
91
|
+
username: string;
|
|
92
|
+
|
|
93
|
+
@FieldGroup("minimal", "public")
|
|
94
|
+
@MapFrom("profile.avatar")
|
|
95
|
+
avatar: string;
|
|
96
|
+
|
|
97
|
+
@FieldGroup("public", "full")
|
|
98
|
+
@AutoMap()
|
|
99
|
+
bio: string;
|
|
100
|
+
|
|
101
|
+
@FieldGroup("full")
|
|
102
|
+
@AutoMap()
|
|
103
|
+
email: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Use field groups
|
|
107
|
+
Mapper.group(user, UserDto, "minimal"); // { username, avatar }
|
|
108
|
+
Mapper.group(user, UserDto, "public"); // { username, avatar, bio }
|
|
109
|
+
Mapper.group(user, UserDto, "full"); // { bio, email }
|
|
110
|
+
|
|
111
|
+
// Or via options
|
|
112
|
+
Mapper.map(user, UserDto, { group: "minimal" });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Decorators
|
|
116
|
+
|
|
117
|
+
| Decorator | Description | Example |
|
|
118
|
+
| ------------------------- | ---------------------------- | ------------------------------------------------ |
|
|
119
|
+
| `@AutoMap()` | Maps property with same name | `@AutoMap() name: string` |
|
|
120
|
+
| `@MapFrom(path)` | Maps from nested path | `@MapFrom('profile.avatar') avatar: string` |
|
|
121
|
+
| `@MapFrom(fn)` | Custom transform | `@MapFrom(src => src.age > 18) isAdult: boolean` |
|
|
122
|
+
| `@FieldGroup(...groups)` | Assigns to field groups | `@FieldGroup('minimal', 'public')` |
|
|
123
|
+
| `@Ignore()` | Skips property | `@Ignore() internalId: string` |
|
|
124
|
+
| `@NestedType(() => Type)` | Nested object mapping | `@NestedType(() => AddressDto)` |
|
|
125
|
+
|
|
126
|
+
## NestJS Integration
|
|
127
|
+
|
|
128
|
+
### Basic Setup
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { Module } from "@nestjs/common";
|
|
132
|
+
import { MapperModule } from "typemold";
|
|
133
|
+
|
|
134
|
+
@Module({
|
|
135
|
+
imports: [MapperModule.forRoot()], // Global by default
|
|
136
|
+
})
|
|
137
|
+
export class AppModule {}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### With Validation
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
@Module({
|
|
144
|
+
imports: [
|
|
145
|
+
MapperModule.forRoot({
|
|
146
|
+
enableValidation: true, // Uses class-validator if installed
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
})
|
|
150
|
+
export class AppModule {}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Async Configuration
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
@Module({
|
|
157
|
+
imports: [
|
|
158
|
+
MapperModule.forRootAsync({
|
|
159
|
+
imports: [ConfigModule],
|
|
160
|
+
useFactory: (config: ConfigService) => ({
|
|
161
|
+
enableValidation: config.get("ENABLE_VALIDATION"),
|
|
162
|
+
}),
|
|
163
|
+
inject: [ConfigService],
|
|
164
|
+
}),
|
|
165
|
+
],
|
|
166
|
+
})
|
|
167
|
+
export class AppModule {}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Using MapperService
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { Injectable } from "@nestjs/common";
|
|
174
|
+
import { MapperService } from "typemold";
|
|
175
|
+
|
|
176
|
+
@Injectable()
|
|
177
|
+
export class UserService {
|
|
178
|
+
constructor(private readonly mapper: MapperService) {}
|
|
179
|
+
|
|
180
|
+
async getUser(id: string): Promise<UserDto> {
|
|
181
|
+
const user = await this.userRepo.findOne(id);
|
|
182
|
+
return this.mapper.map(user, UserDto);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getUserMinimal(id: string): Promise<Partial<UserDto>> {
|
|
186
|
+
const user = await this.userRepo.findOne(id);
|
|
187
|
+
return this.mapper.group(user, UserDto, "minimal");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getPostAuthor(
|
|
191
|
+
postId: string
|
|
192
|
+
): Promise<Pick<UserDto, "username" | "avatar">> {
|
|
193
|
+
const user = await this.getPostUser(postId);
|
|
194
|
+
return this.mapper.pick(user, UserDto, ["username", "avatar"]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Performance
|
|
200
|
+
|
|
201
|
+
Thanks to compiled & cached mappers, performance is near-identical to hand-written mapping code:
|
|
202
|
+
|
|
203
|
+
| Operation | typemold | @automapper/nestjs | Manual |
|
|
204
|
+
| ------------ | --------------------- | ------------------ | -------- |
|
|
205
|
+
| Single map | ~0.002ms | ~0.05ms | ~0.001ms |
|
|
206
|
+
| Array (1000) | ~1.5ms | ~40ms | ~1ms |
|
|
207
|
+
| Memory | O(1) cache | O(n) profiles | None |
|
|
208
|
+
|
|
209
|
+
## API Reference
|
|
210
|
+
|
|
211
|
+
### Mapper (Static)
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
Mapper.map(source, TargetDto, options?)
|
|
215
|
+
Mapper.mapArray(sources, TargetDto, options?)
|
|
216
|
+
Mapper.pick(source, TargetDto, ['field1', 'field2'])
|
|
217
|
+
Mapper.omit(source, TargetDto, ['field1'])
|
|
218
|
+
Mapper.group(source, TargetDto, 'groupName')
|
|
219
|
+
Mapper.createMapper(TargetDto, options?) // Returns reusable function
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### MapOptions
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
interface MapOptions<T> {
|
|
226
|
+
pick?: (keyof T)[]; // Include only these fields
|
|
227
|
+
omit?: (keyof T)[]; // Exclude these fields
|
|
228
|
+
group?: string; // Use predefined field group
|
|
229
|
+
extras?: Record<string, unknown>; // Extra context for transforms
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT © [Sevirial](https://sevirial.com)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @sevirial/nest-mapper - Decorators
|
|
4
|
+
* Property decorators for defining mapping configurations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Groups = void 0;
|
|
8
|
+
exports.MapFrom = MapFrom;
|
|
9
|
+
exports.createMapping = createMapping;
|
|
10
|
+
exports.AutoMap = AutoMap;
|
|
11
|
+
exports.FieldGroup = FieldGroup;
|
|
12
|
+
exports.createFieldGroups = createFieldGroups;
|
|
13
|
+
exports.Ignore = Ignore;
|
|
14
|
+
exports.NestedType = NestedType;
|
|
15
|
+
require("reflect-metadata");
|
|
16
|
+
const types_1 = require("./types");
|
|
17
|
+
/**
|
|
18
|
+
* Maps a property from a source path or using a transform function.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Direct property mapping
|
|
22
|
+
* class UserDto {
|
|
23
|
+
* @MapFrom('firstName')
|
|
24
|
+
* name: string;
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Nested path mapping
|
|
29
|
+
* class UserDto {
|
|
30
|
+
* @MapFrom('profile.avatar')
|
|
31
|
+
* avatarUrl: string;
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Transform function with typed source
|
|
36
|
+
* class UserDto {
|
|
37
|
+
* @MapFrom<User>((src) => src.age >= 18) // ← IntelliSense for src!
|
|
38
|
+
* isAdult: boolean;
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
41
|
+
function MapFrom(sourcePathOrTransform) {
|
|
42
|
+
return (target, propertyKey) => {
|
|
43
|
+
const key = String(propertyKey);
|
|
44
|
+
const existingMappings = Reflect.getMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, target.constructor) || new Map();
|
|
45
|
+
const existingConfig = existingMappings.get(key) || createDefaultConfig(key);
|
|
46
|
+
if (typeof sourcePathOrTransform === "function") {
|
|
47
|
+
existingConfig.source = sourcePathOrTransform;
|
|
48
|
+
existingConfig.isTransform = true;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
existingConfig.source = sourcePathOrTransform;
|
|
52
|
+
existingConfig.isTransform = false;
|
|
53
|
+
}
|
|
54
|
+
existingMappings.set(key, existingConfig);
|
|
55
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, existingMappings, target.constructor);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Creates a type-safe mapping configuration with full IntelliSense support.
|
|
60
|
+
* Use this builder pattern when you need autocomplete for source paths.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* interface User {
|
|
64
|
+
* username: string;
|
|
65
|
+
* profile: { avatar: string; bio: string };
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* // Option 1: Define mappings with full autocomplete
|
|
69
|
+
* const toUserDto = createMapping<User, UserDto>({
|
|
70
|
+
* avatar: 'profile.avatar', // ✨ Autocomplete for paths!
|
|
71
|
+
* bio: src => src.profile.bio, // ✨ Autocomplete for transforms!
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* // Usage
|
|
75
|
+
* const dto = toUserDto(userEntity);
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Option 2: Use with Mapper
|
|
79
|
+
* const dto = Mapper.mapWith(user, toUserDto);
|
|
80
|
+
*/
|
|
81
|
+
function createMapping(mappings) {
|
|
82
|
+
return (source) => {
|
|
83
|
+
const result = {};
|
|
84
|
+
for (const [targetKey, sourcePathOrFn] of Object.entries(mappings)) {
|
|
85
|
+
if (typeof sourcePathOrFn === "function") {
|
|
86
|
+
result[targetKey] = sourcePathOrFn(source);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
result[targetKey] = getNestedValueTyped(source, sourcePathOrFn);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Helper to get nested value with type safety
|
|
97
|
+
*/
|
|
98
|
+
function getNestedValueTyped(obj, path) {
|
|
99
|
+
if (obj == null)
|
|
100
|
+
return undefined;
|
|
101
|
+
const segments = path.split(".");
|
|
102
|
+
let current = obj;
|
|
103
|
+
for (const segment of segments) {
|
|
104
|
+
if (current == null || typeof current !== "object")
|
|
105
|
+
return undefined;
|
|
106
|
+
current = current[segment];
|
|
107
|
+
}
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Automatically maps a property with the same name from source.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* class UserDto {
|
|
115
|
+
* @AutoMap()
|
|
116
|
+
* username: string; // Maps from source.username
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
function AutoMap() {
|
|
120
|
+
return (target, propertyKey) => {
|
|
121
|
+
const key = String(propertyKey);
|
|
122
|
+
const existingMappings = Reflect.getMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, target.constructor) || new Map();
|
|
123
|
+
const existingConfig = existingMappings.get(key) || createDefaultConfig(key);
|
|
124
|
+
existingConfig.source = key; // Same name mapping
|
|
125
|
+
existingConfig.isTransform = false;
|
|
126
|
+
existingMappings.set(key, existingConfig);
|
|
127
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, existingMappings, target.constructor);
|
|
128
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.AUTO_MAP, true, target.constructor, key);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Assigns a property to one or more field groups for runtime projection.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // Basic usage with strings
|
|
136
|
+
* class UserDto {
|
|
137
|
+
* @FieldGroup('minimal', 'public')
|
|
138
|
+
* @AutoMap()
|
|
139
|
+
* username: string;
|
|
140
|
+
* }
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* // Type-safe usage with const object (recommended for autocomplete!)
|
|
144
|
+
* const Groups = createFieldGroups('minimal', 'public', 'full');
|
|
145
|
+
*
|
|
146
|
+
* class UserDto {
|
|
147
|
+
* @FieldGroup(Groups.minimal, Groups.public) // ✨ Autocomplete!
|
|
148
|
+
* @AutoMap()
|
|
149
|
+
* username: string;
|
|
150
|
+
* }
|
|
151
|
+
*/
|
|
152
|
+
function FieldGroup(...groups) {
|
|
153
|
+
return (target, propertyKey) => {
|
|
154
|
+
const key = String(propertyKey);
|
|
155
|
+
const existingMappings = Reflect.getMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, target.constructor) || new Map();
|
|
156
|
+
const existingConfig = existingMappings.get(key) || createDefaultConfig(key);
|
|
157
|
+
existingConfig.groups = [...new Set([...existingConfig.groups, ...groups])];
|
|
158
|
+
existingMappings.set(key, existingConfig);
|
|
159
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, existingMappings, target.constructor);
|
|
160
|
+
// Also store in field groups map for quick lookup
|
|
161
|
+
const fieldGroups = Reflect.getMetadata(types_1.METADATA_KEYS.FIELD_GROUPS, target.constructor) || new Map();
|
|
162
|
+
for (const group of groups) {
|
|
163
|
+
const groupSet = fieldGroups.get(group) || new Set();
|
|
164
|
+
groupSet.add(key);
|
|
165
|
+
fieldGroups.set(group, groupSet);
|
|
166
|
+
}
|
|
167
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.FIELD_GROUPS, fieldGroups, target.constructor);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Creates a type-safe field groups object with autocomplete support.
|
|
172
|
+
* Use this to define your groups once and get IntelliSense everywhere!
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* // Define groups once
|
|
176
|
+
* export const UserGroups = createFieldGroups('minimal', 'public', 'full');
|
|
177
|
+
*
|
|
178
|
+
* class UserDto {
|
|
179
|
+
* @FieldGroup(UserGroups.minimal, UserGroups.public) // ✨ Autocomplete!
|
|
180
|
+
* @AutoMap()
|
|
181
|
+
* username: string;
|
|
182
|
+
*
|
|
183
|
+
* @FieldGroup(UserGroups.full)
|
|
184
|
+
* @AutoMap()
|
|
185
|
+
* email: string;
|
|
186
|
+
* }
|
|
187
|
+
*
|
|
188
|
+
* // Usage with type-safety
|
|
189
|
+
* Mapper.map(user, UserDto, { group: UserGroups.minimal }); // ✨ Autocomplete!
|
|
190
|
+
*/
|
|
191
|
+
function createFieldGroups(...groups) {
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const group of groups) {
|
|
194
|
+
result[group] = group;
|
|
195
|
+
}
|
|
196
|
+
return Object.freeze(result);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Built-in field groups with autocomplete - use these directly!
|
|
200
|
+
* No need to define your own groups for common use cases.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* class UserDto {
|
|
204
|
+
* @FieldGroup(Groups.MINIMAL, Groups.PUBLIC) // ✨ Autocomplete!
|
|
205
|
+
* @AutoMap()
|
|
206
|
+
* username: string;
|
|
207
|
+
*
|
|
208
|
+
* @FieldGroup(Groups.DETAILED)
|
|
209
|
+
* @AutoMap()
|
|
210
|
+
* email: string;
|
|
211
|
+
* }
|
|
212
|
+
*
|
|
213
|
+
* // Usage
|
|
214
|
+
* Mapper.map(user, UserDto, { group: Groups.MINIMAL }); // ✨ Autocomplete!
|
|
215
|
+
*/
|
|
216
|
+
exports.Groups = Object.freeze({
|
|
217
|
+
/** Minimal fields - just the essentials (e.g., id, name) */
|
|
218
|
+
MINIMAL: "minimal",
|
|
219
|
+
/** Summary fields - brief overview */
|
|
220
|
+
SUMMARY: "summary",
|
|
221
|
+
/** Public fields - safe to expose publicly */
|
|
222
|
+
PUBLIC: "public",
|
|
223
|
+
/** Private fields - internal use only */
|
|
224
|
+
PRIVATE: "private",
|
|
225
|
+
/** Detailed fields - comprehensive info */
|
|
226
|
+
DETAILED: "detailed",
|
|
227
|
+
/** Full fields - everything */
|
|
228
|
+
FULL: "full",
|
|
229
|
+
/** List view fields - for table/list displays */
|
|
230
|
+
LIST: "list",
|
|
231
|
+
/** Detail view fields - for detail pages */
|
|
232
|
+
DETAIL: "detail",
|
|
233
|
+
/** Admin fields - administrative data */
|
|
234
|
+
ADMIN: "admin",
|
|
235
|
+
/** API response fields */
|
|
236
|
+
API: "api",
|
|
237
|
+
});
|
|
238
|
+
/**
|
|
239
|
+
* Ignores a property during mapping.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* class UserDto {
|
|
243
|
+
* @Ignore()
|
|
244
|
+
* internalId: string; // Will not be mapped
|
|
245
|
+
* }
|
|
246
|
+
*/
|
|
247
|
+
function Ignore() {
|
|
248
|
+
return (target, propertyKey) => {
|
|
249
|
+
const key = String(propertyKey);
|
|
250
|
+
const existingMappings = Reflect.getMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, target.constructor) || new Map();
|
|
251
|
+
const existingConfig = existingMappings.get(key) || createDefaultConfig(key);
|
|
252
|
+
existingConfig.ignore = true;
|
|
253
|
+
existingMappings.set(key, existingConfig);
|
|
254
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.PROPERTY_MAPPINGS, existingMappings, target.constructor);
|
|
255
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.IGNORE, true, target.constructor, key);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Specifies the type for nested object mapping.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* class UserDto {
|
|
263
|
+
* @NestedType(() => AddressDto)
|
|
264
|
+
* @MapFrom('address')
|
|
265
|
+
* address: AddressDto;
|
|
266
|
+
* }
|
|
267
|
+
*/
|
|
268
|
+
function NestedType(typeFactory) {
|
|
269
|
+
return (target, propertyKey) => {
|
|
270
|
+
Reflect.defineMetadata(types_1.METADATA_KEYS.NESTED_TYPE, typeFactory, target.constructor, String(propertyKey));
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Creates a default property mapping config
|
|
275
|
+
*/
|
|
276
|
+
function createDefaultConfig(targetKey) {
|
|
277
|
+
return {
|
|
278
|
+
targetKey,
|
|
279
|
+
source: targetKey,
|
|
280
|
+
isTransform: false,
|
|
281
|
+
groups: [],
|
|
282
|
+
ignore: false,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* typemold
|
|
4
|
+
* A lightweight, high-performance object mapper for TypeScript and Node.js
|
|
5
|
+
*
|
|
6
|
+
* @author Chetan Joshi
|
|
7
|
+
* @license MIT
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Basic mapping
|
|
11
|
+
* import { Mapper, MapFrom, AutoMap } from 'typemold';
|
|
12
|
+
*
|
|
13
|
+
* class UserDto {
|
|
14
|
+
* @AutoMap()
|
|
15
|
+
* username: string;
|
|
16
|
+
*
|
|
17
|
+
* @MapFrom('profile.avatar')
|
|
18
|
+
* avatar: string;
|
|
19
|
+
*
|
|
20
|
+
* @MapFrom((src) => src.age >= 18)
|
|
21
|
+
* isAdult: boolean;
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* const userDto = Mapper.map(userEntity, UserDto);
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Runtime field projection
|
|
28
|
+
* const minimal = Mapper.map(user, UserDto, { pick: ['username', 'avatar'] });
|
|
29
|
+
* const safe = Mapper.map(user, UserDto, { omit: ['email', 'password'] });
|
|
30
|
+
* const public = Mapper.map(user, UserDto, { group: 'public' });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // NestJS integration (optional)
|
|
34
|
+
* import { MapperModule, MapperService } from 'typemold';
|
|
35
|
+
*
|
|
36
|
+
* @Module({
|
|
37
|
+
* imports: [MapperModule.forRoot()],
|
|
38
|
+
* })
|
|
39
|
+
* export class AppModule {}
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.MAPPER_OPTIONS = exports.MapperService = exports.MapperModule = exports.isClassInstance = exports.isPlainObject = exports.omitKeys = exports.pickKeys = exports.getNestedValue = exports.MapperFactory = exports.MappingRegistry = exports.METADATA_KEYS = exports.NestedType = exports.Ignore = exports.Groups = exports.createFieldGroups = exports.FieldGroup = exports.AutoMap = exports.createMapping = exports.MapFrom = exports.Mapper = void 0;
|
|
43
|
+
// Core Mapper
|
|
44
|
+
var mapper_1 = require("./mapper");
|
|
45
|
+
Object.defineProperty(exports, "Mapper", { enumerable: true, get: function () { return mapper_1.Mapper; } });
|
|
46
|
+
// Decorators
|
|
47
|
+
var decorators_1 = require("./decorators");
|
|
48
|
+
Object.defineProperty(exports, "MapFrom", { enumerable: true, get: function () { return decorators_1.MapFrom; } });
|
|
49
|
+
Object.defineProperty(exports, "createMapping", { enumerable: true, get: function () { return decorators_1.createMapping; } });
|
|
50
|
+
Object.defineProperty(exports, "AutoMap", { enumerable: true, get: function () { return decorators_1.AutoMap; } });
|
|
51
|
+
Object.defineProperty(exports, "FieldGroup", { enumerable: true, get: function () { return decorators_1.FieldGroup; } });
|
|
52
|
+
Object.defineProperty(exports, "createFieldGroups", { enumerable: true, get: function () { return decorators_1.createFieldGroups; } });
|
|
53
|
+
Object.defineProperty(exports, "Groups", { enumerable: true, get: function () { return decorators_1.Groups; } });
|
|
54
|
+
Object.defineProperty(exports, "Ignore", { enumerable: true, get: function () { return decorators_1.Ignore; } });
|
|
55
|
+
Object.defineProperty(exports, "NestedType", { enumerable: true, get: function () { return decorators_1.NestedType; } });
|
|
56
|
+
// Types
|
|
57
|
+
var types_1 = require("./types");
|
|
58
|
+
Object.defineProperty(exports, "METADATA_KEYS", { enumerable: true, get: function () { return types_1.METADATA_KEYS; } });
|
|
59
|
+
// Registry (for advanced usage)
|
|
60
|
+
var registry_1 = require("./registry");
|
|
61
|
+
Object.defineProperty(exports, "MappingRegistry", { enumerable: true, get: function () { return registry_1.MappingRegistry; } });
|
|
62
|
+
Object.defineProperty(exports, "MapperFactory", { enumerable: true, get: function () { return registry_1.MapperFactory; } });
|
|
63
|
+
// Utilities
|
|
64
|
+
var utils_1 = require("./utils");
|
|
65
|
+
Object.defineProperty(exports, "getNestedValue", { enumerable: true, get: function () { return utils_1.getNestedValue; } });
|
|
66
|
+
Object.defineProperty(exports, "pickKeys", { enumerable: true, get: function () { return utils_1.pickKeys; } });
|
|
67
|
+
Object.defineProperty(exports, "omitKeys", { enumerable: true, get: function () { return utils_1.omitKeys; } });
|
|
68
|
+
Object.defineProperty(exports, "isPlainObject", { enumerable: true, get: function () { return utils_1.isPlainObject; } });
|
|
69
|
+
Object.defineProperty(exports, "isClassInstance", { enumerable: true, get: function () { return utils_1.isClassInstance; } });
|
|
70
|
+
// NestJS Integration
|
|
71
|
+
var nestjs_1 = require("./nestjs");
|
|
72
|
+
Object.defineProperty(exports, "MapperModule", { enumerable: true, get: function () { return nestjs_1.MapperModule; } });
|
|
73
|
+
Object.defineProperty(exports, "MapperService", { enumerable: true, get: function () { return nestjs_1.MapperService; } });
|
|
74
|
+
Object.defineProperty(exports, "MAPPER_OPTIONS", { enumerable: true, get: function () { return nestjs_1.MAPPER_OPTIONS; } });
|