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.
Files changed (39) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +235 -0
  3. package/dist/cjs/decorators.js +284 -0
  4. package/dist/cjs/index.js +74 -0
  5. package/dist/cjs/mapper.js +153 -0
  6. package/dist/cjs/nestjs/index.js +12 -0
  7. package/dist/cjs/nestjs/mapper.module.js +103 -0
  8. package/dist/cjs/nestjs/mapper.service.js +161 -0
  9. package/dist/cjs/registry.js +179 -0
  10. package/dist/cjs/types.js +17 -0
  11. package/dist/cjs/utils.js +136 -0
  12. package/dist/esm/decorators.js +274 -0
  13. package/dist/esm/index.js +51 -0
  14. package/dist/esm/mapper.js +149 -0
  15. package/dist/esm/nestjs/index.js +6 -0
  16. package/dist/esm/nestjs/mapper.module.js +100 -0
  17. package/dist/esm/nestjs/mapper.service.js +125 -0
  18. package/dist/esm/registry.js +175 -0
  19. package/dist/esm/types.js +14 -0
  20. package/dist/esm/utils.js +127 -0
  21. package/dist/types/decorators.d.ts +206 -0
  22. package/dist/types/decorators.d.ts.map +1 -0
  23. package/dist/types/index.d.ts +46 -0
  24. package/dist/types/index.d.ts.map +1 -0
  25. package/dist/types/mapper.d.ts +93 -0
  26. package/dist/types/mapper.d.ts.map +1 -0
  27. package/dist/types/nestjs/index.d.ts +7 -0
  28. package/dist/types/nestjs/index.d.ts.map +1 -0
  29. package/dist/types/nestjs/mapper.module.d.ts +89 -0
  30. package/dist/types/nestjs/mapper.module.d.ts.map +1 -0
  31. package/dist/types/nestjs/mapper.service.d.ts +80 -0
  32. package/dist/types/nestjs/mapper.service.d.ts.map +1 -0
  33. package/dist/types/registry.d.ts +60 -0
  34. package/dist/types/registry.d.ts.map +1 -0
  35. package/dist/types/types.d.ts +120 -0
  36. package/dist/types/types.d.ts.map +1 -0
  37. package/dist/types/utils.d.ts +30 -0
  38. package/dist/types/utils.d.ts.map +1 -0
  39. package/package.json +92 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @sevirial/nest-mapper - NestJS MapperModule
3
+ * Dynamic module for NestJS integration
4
+ */
5
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
6
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
7
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
8
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ var MapperModule_1;
12
+ import { Module, } from "@nestjs/common";
13
+ import { MapperService, MAPPER_OPTIONS, } from "./mapper.service";
14
+ /**
15
+ * NestJS Module for @sevirial/nest-mapper
16
+ *
17
+ * @example
18
+ * // Basic usage (global by default)
19
+ * @Module({
20
+ * imports: [MapperModule.forRoot()],
21
+ * })
22
+ * export class AppModule {}
23
+ *
24
+ * @example
25
+ * // With options
26
+ * @Module({
27
+ * imports: [
28
+ * MapperModule.forRoot({
29
+ * enableValidation: true,
30
+ * converters: [myDateConverter],
31
+ * }),
32
+ * ],
33
+ * })
34
+ * export class AppModule {}
35
+ *
36
+ * @example
37
+ * // Async configuration
38
+ * @Module({
39
+ * imports: [
40
+ * MapperModule.forRootAsync({
41
+ * imports: [ConfigModule],
42
+ * useFactory: (config: ConfigService) => ({
43
+ * enableValidation: config.get('ENABLE_VALIDATION'),
44
+ * }),
45
+ * inject: [ConfigService],
46
+ * }),
47
+ * ],
48
+ * })
49
+ * export class AppModule {}
50
+ */
51
+ let MapperModule = MapperModule_1 = class MapperModule {
52
+ /**
53
+ * Configure the mapper module with static options
54
+ */
55
+ static forRoot(options) {
56
+ const isGlobal = options?.isGlobal ?? true;
57
+ const optionsProvider = {
58
+ provide: MAPPER_OPTIONS,
59
+ useValue: options || {},
60
+ };
61
+ return {
62
+ module: MapperModule_1,
63
+ global: isGlobal,
64
+ providers: [optionsProvider, MapperService],
65
+ exports: [MapperService],
66
+ };
67
+ }
68
+ /**
69
+ * Configure the mapper module with async options (factory pattern)
70
+ */
71
+ static forRootAsync(options) {
72
+ const isGlobal = options.isGlobal ?? true;
73
+ const asyncOptionsProvider = {
74
+ provide: MAPPER_OPTIONS,
75
+ useFactory: options.useFactory,
76
+ inject: options.inject || [],
77
+ };
78
+ return {
79
+ module: MapperModule_1,
80
+ global: isGlobal,
81
+ imports: options.imports || [],
82
+ providers: [asyncOptionsProvider, MapperService],
83
+ exports: [MapperService],
84
+ };
85
+ }
86
+ /**
87
+ * For feature modules that need the mapper (when not using isGlobal)
88
+ */
89
+ static forFeature() {
90
+ return {
91
+ module: MapperModule_1,
92
+ providers: [MapperService],
93
+ exports: [MapperService],
94
+ };
95
+ }
96
+ };
97
+ MapperModule = MapperModule_1 = __decorate([
98
+ Module({})
99
+ ], MapperModule);
100
+ export { MapperModule };
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @sevirial/nest-mapper - NestJS MapperService
3
+ * Injectable service for NestJS dependency injection
4
+ */
5
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
6
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
7
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
8
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ var __metadata = (this && this.__metadata) || function (k, v) {
12
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
13
+ };
14
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
15
+ return function (target, key) { decorator(target, key, paramIndex); }
16
+ };
17
+ import { Injectable, Inject, Optional } from "@nestjs/common";
18
+ import { Mapper } from "../mapper";
19
+ /**
20
+ * Injection token for MapperModule options
21
+ */
22
+ export const MAPPER_OPTIONS = Symbol("MAPPER_OPTIONS");
23
+ /**
24
+ * Injectable mapper service for NestJS
25
+ *
26
+ * @example
27
+ * @Injectable()
28
+ * export class UserService {
29
+ * constructor(private readonly mapper: MapperService) {}
30
+ *
31
+ * async getUser(id: string): Promise<UserDto> {
32
+ * const user = await this.userRepo.findOne(id);
33
+ * return this.mapper.map(user, UserDto);
34
+ * }
35
+ * }
36
+ */
37
+ let MapperService = class MapperService {
38
+ constructor(options) {
39
+ this.validator = null;
40
+ this.options = options || {};
41
+ // Register custom converters if provided
42
+ if (this.options.converters) {
43
+ for (const converter of this.options.converters) {
44
+ Mapper.registerConverter(converter);
45
+ }
46
+ }
47
+ // Set global context if extras provided
48
+ if (this.options.globalExtras) {
49
+ Mapper.setGlobalContext({ extras: this.options.globalExtras });
50
+ }
51
+ // Try to load class-validator if validation is enabled
52
+ if (this.options.enableValidation) {
53
+ this.initializeValidator();
54
+ }
55
+ }
56
+ /**
57
+ * Lazily loads class-validator for hybrid integration
58
+ */
59
+ async initializeValidator() {
60
+ try {
61
+ this.validator = await import("class-validator");
62
+ }
63
+ catch {
64
+ console.warn("[@sevirial/nest-mapper] class-validator not found. Validation will be skipped.");
65
+ }
66
+ }
67
+ /**
68
+ * Maps a source object to a target DTO
69
+ */
70
+ map(source, targetType, options) {
71
+ return Mapper.map(source, targetType, options);
72
+ }
73
+ /**
74
+ * Maps an array of source objects to target DTOs
75
+ */
76
+ mapArray(sources, targetType, options) {
77
+ return Mapper.mapArray(sources, targetType, options);
78
+ }
79
+ /**
80
+ * Maps and validates the result using class-validator (if enabled)
81
+ *
82
+ * @throws ValidationError[] if validation fails
83
+ */
84
+ async mapAndValidate(source, targetType, options) {
85
+ const result = this.map(source, targetType, options);
86
+ if (this.validator && this.options.enableValidation) {
87
+ const errors = await this.validator.validate(result);
88
+ if (errors.length > 0) {
89
+ throw errors;
90
+ }
91
+ }
92
+ return result;
93
+ }
94
+ /**
95
+ * Pick specific fields (shorthand)
96
+ */
97
+ pick(source, targetType, fields) {
98
+ return Mapper.pick(source, targetType, fields);
99
+ }
100
+ /**
101
+ * Omit specific fields (shorthand)
102
+ */
103
+ omit(source, targetType, fields) {
104
+ return Mapper.omit(source, targetType, fields);
105
+ }
106
+ /**
107
+ * Use a field group (shorthand)
108
+ */
109
+ group(source, targetType, groupName) {
110
+ return Mapper.group(source, targetType, groupName);
111
+ }
112
+ /**
113
+ * Creates a reusable mapper function
114
+ */
115
+ createMapper(targetType, options) {
116
+ return Mapper.createMapper(targetType, options);
117
+ }
118
+ };
119
+ MapperService = __decorate([
120
+ Injectable(),
121
+ __param(0, Optional()),
122
+ __param(0, Inject(MAPPER_OPTIONS)),
123
+ __metadata("design:paramtypes", [Object])
124
+ ], MapperService);
125
+ export { MapperService };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @sevirial/nest-mapper - Mapping Registry
3
+ * Singleton registry for storing and caching mapping configurations
4
+ */
5
+ import "reflect-metadata";
6
+ import { METADATA_KEYS, } from "./types";
7
+ import { getNestedValue } from "./utils";
8
+ /**
9
+ * Global mapping registry - singleton pattern for performance
10
+ */
11
+ class MappingRegistryClass {
12
+ constructor() {
13
+ this.registry = new Map();
14
+ this.compiledMappers = new WeakMap();
15
+ }
16
+ /**
17
+ * Gets or creates a registry entry for a target DTO class
18
+ */
19
+ getEntry(targetType) {
20
+ let entry = this.registry.get(targetType);
21
+ if (!entry) {
22
+ entry = this.createEntryFromMetadata(targetType);
23
+ this.registry.set(targetType, entry);
24
+ }
25
+ return entry;
26
+ }
27
+ /**
28
+ * Creates a mapping entry by reading decorator metadata
29
+ */
30
+ createEntryFromMetadata(targetType) {
31
+ const propertyMappings = Reflect.getMetadata(METADATA_KEYS.PROPERTY_MAPPINGS, targetType) ||
32
+ new Map();
33
+ const fieldGroups = Reflect.getMetadata(METADATA_KEYS.FIELD_GROUPS, targetType) || new Map();
34
+ return {
35
+ targetType,
36
+ propertyConfigs: propertyMappings,
37
+ fieldGroups,
38
+ };
39
+ }
40
+ /**
41
+ * Gets a compiled mapper for the given target type and options signature
42
+ */
43
+ getCompiledMapper(targetType, optionsKey = "default") {
44
+ const typeMappers = this.compiledMappers.get(targetType);
45
+ return typeMappers?.get(optionsKey);
46
+ }
47
+ /**
48
+ * Stores a compiled mapper for reuse
49
+ */
50
+ setCompiledMapper(targetType, optionsKey, mapper) {
51
+ let typeMappers = this.compiledMappers.get(targetType);
52
+ if (!typeMappers) {
53
+ typeMappers = new Map();
54
+ this.compiledMappers.set(targetType, typeMappers);
55
+ }
56
+ typeMappers.set(optionsKey, mapper);
57
+ }
58
+ /**
59
+ * Clears all cached mappers (useful for testing)
60
+ */
61
+ clearCache() {
62
+ this.registry.clear();
63
+ }
64
+ /**
65
+ * Generates a cache key for mapping options
66
+ */
67
+ getOptionsKey(options) {
68
+ if (!options)
69
+ return "default";
70
+ if (options.pick)
71
+ return `pick:${options.pick.sort().join(",")}`;
72
+ if (options.omit)
73
+ return `omit:${options.omit.sort().join(",")}`;
74
+ if (options.group)
75
+ return `group:${options.group}`;
76
+ return "default";
77
+ }
78
+ }
79
+ /**
80
+ * Singleton instance
81
+ */
82
+ export const MappingRegistry = new MappingRegistryClass();
83
+ /**
84
+ * Mapper Factory - Creates optimized mapping functions
85
+ */
86
+ export class MapperFactory {
87
+ /**
88
+ * Creates a compiled mapper for the given target type with optional field projection
89
+ */
90
+ static createMapper(targetType, options) {
91
+ const entry = MappingRegistry.getEntry(targetType);
92
+ const optionsKey = MappingRegistry.getOptionsKey(options);
93
+ // Check cache first
94
+ let compiledMapper = MappingRegistry.getCompiledMapper(targetType, optionsKey);
95
+ if (compiledMapper) {
96
+ return compiledMapper;
97
+ }
98
+ // Determine which properties to include
99
+ const propertiesToMap = this.getPropertiesToMap(entry, options);
100
+ // Build the compiled mapper
101
+ compiledMapper = this.buildMapper(targetType, entry, propertiesToMap);
102
+ // Cache for reuse
103
+ MappingRegistry.setCompiledMapper(targetType, optionsKey, compiledMapper);
104
+ return compiledMapper;
105
+ }
106
+ /**
107
+ * Determines which properties to map based on options
108
+ */
109
+ static getPropertiesToMap(entry, options) {
110
+ const allConfigs = Array.from(entry.propertyConfigs.values()).filter((config) => !config.ignore);
111
+ if (!options) {
112
+ return allConfigs;
113
+ }
114
+ // Field group selection
115
+ if (options.group) {
116
+ const groupFields = entry.fieldGroups.get(options.group);
117
+ if (groupFields) {
118
+ return allConfigs.filter((config) => groupFields.has(config.targetKey));
119
+ }
120
+ return []; // Group not found, return empty
121
+ }
122
+ // Pick specific fields
123
+ if (options.pick && options.pick.length > 0) {
124
+ const pickSet = new Set(options.pick.map(String));
125
+ return allConfigs.filter((config) => pickSet.has(config.targetKey));
126
+ }
127
+ // Omit specific fields
128
+ if (options.omit && options.omit.length > 0) {
129
+ const omitSet = new Set(options.omit.map(String));
130
+ return allConfigs.filter((config) => !omitSet.has(config.targetKey));
131
+ }
132
+ return allConfigs;
133
+ }
134
+ /**
135
+ * Builds an optimized mapping function
136
+ */
137
+ static buildMapper(targetType, entry, properties) {
138
+ // Separate transform functions from path mappings for optimization
139
+ const pathMappings = [];
140
+ const transformMappings = [];
141
+ for (const prop of properties) {
142
+ if (prop.isTransform && typeof prop.source === "function") {
143
+ transformMappings.push({
144
+ target: prop.targetKey,
145
+ transform: prop.source,
146
+ });
147
+ }
148
+ else {
149
+ pathMappings.push({
150
+ target: prop.targetKey,
151
+ source: prop.source,
152
+ });
153
+ }
154
+ }
155
+ // Return optimized mapper function
156
+ return (source, context) => {
157
+ if (source == null) {
158
+ return null;
159
+ }
160
+ // Create new instance or plain object based on target type
161
+ const result = new targetType();
162
+ // Apply path mappings (optimized)
163
+ for (let i = 0; i < pathMappings.length; i++) {
164
+ const mapping = pathMappings[i];
165
+ result[mapping.target] = getNestedValue(source, mapping.source);
166
+ }
167
+ // Apply transform mappings
168
+ for (let i = 0; i < transformMappings.length; i++) {
169
+ const mapping = transformMappings[i];
170
+ result[mapping.target] = mapping.transform(source, context);
171
+ }
172
+ return result;
173
+ };
174
+ }
175
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @sevirial/nest-mapper - Type Definitions
3
+ * Core types for the high-performance object mapper
4
+ */
5
+ /**
6
+ * Metadata key constants
7
+ */
8
+ export const METADATA_KEYS = {
9
+ PROPERTY_MAPPINGS: Symbol("sevirial:property-mappings"),
10
+ FIELD_GROUPS: Symbol("sevirial:field-groups"),
11
+ AUTO_MAP: Symbol("sevirial:auto-map"),
12
+ IGNORE: Symbol("sevirial:ignore"),
13
+ NESTED_TYPE: Symbol("sevirial:nested-type"),
14
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @sevirial/nest-mapper - Utility Functions
3
+ * Helper functions for the mapping engine
4
+ */
5
+ /**
6
+ * Gets a nested value from an object using dot notation path.
7
+ * Optimized for performance with caching of path segments.
8
+ *
9
+ * @example
10
+ * getNestedValue({ profile: { avatar: 'url' } }, 'profile.avatar')
11
+ * // Returns: 'url'
12
+ */
13
+ const pathCache = new Map();
14
+ // Dangerous property names for prototype pollution
15
+ const BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
16
+ export function getNestedValue(obj, path) {
17
+ if (obj == null)
18
+ return undefined;
19
+ // Simple path - check for prototype pollution
20
+ if (!path.includes(".")) {
21
+ if (BLOCKED_KEYS.has(path))
22
+ return undefined;
23
+ return obj[path];
24
+ }
25
+ let segments = pathCache.get(path);
26
+ if (!segments) {
27
+ segments = path.split(".");
28
+ pathCache.set(path, segments);
29
+ }
30
+ let current = obj;
31
+ for (let i = 0; i < segments.length; i++) {
32
+ // Block prototype pollution attempts
33
+ if (BLOCKED_KEYS.has(segments[i]))
34
+ return undefined;
35
+ if (current == null || typeof current !== "object")
36
+ return undefined;
37
+ current = current[segments[i]];
38
+ }
39
+ return current;
40
+ }
41
+ /**
42
+ * Checks if a value is a plain object (not an array, Date, etc.)
43
+ */
44
+ export function isPlainObject(value) {
45
+ if (value === null || typeof value !== "object")
46
+ return false;
47
+ const proto = Object.getPrototypeOf(value);
48
+ return proto === Object.prototype || proto === null;
49
+ }
50
+ /**
51
+ * Checks if a value is a class instance (not a plain object)
52
+ */
53
+ export function isClassInstance(value) {
54
+ if (value === null || typeof value !== "object")
55
+ return false;
56
+ const proto = Object.getPrototypeOf(value);
57
+ return proto !== Object.prototype && proto !== null;
58
+ }
59
+ /**
60
+ * Creates a shallow clone of an object with only specified keys
61
+ */
62
+ export function pickKeys(obj, keys) {
63
+ const result = {};
64
+ for (const key of keys) {
65
+ if (key in obj) {
66
+ result[key] = obj[key];
67
+ }
68
+ }
69
+ return result;
70
+ }
71
+ /**
72
+ * Creates a shallow clone of an object without specified keys
73
+ */
74
+ export function omitKeys(obj, keys) {
75
+ const keySet = new Set(keys);
76
+ const result = {};
77
+ for (const key in obj) {
78
+ if (Object.prototype.hasOwnProperty.call(obj, key) && !keySet.has(key)) {
79
+ result[key] = obj[key];
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+ /**
85
+ * Gets all property keys including inherited ones
86
+ */
87
+ export function getAllPropertyKeys(target) {
88
+ const keys = new Set();
89
+ let current = target;
90
+ while (current && current !== Object.prototype) {
91
+ for (const key of Object.getOwnPropertyNames(current)) {
92
+ if (key !== "constructor") {
93
+ keys.add(key);
94
+ }
95
+ }
96
+ current = Object.getPrototypeOf(current);
97
+ }
98
+ return Array.from(keys);
99
+ }
100
+ /**
101
+ * Deep clones an object (for handling circular references)
102
+ */
103
+ export function deepClone(obj, visited = new WeakMap()) {
104
+ if (obj === null || typeof obj !== "object")
105
+ return obj;
106
+ const objAsObject = obj;
107
+ if (visited.has(objAsObject))
108
+ return visited.get(objAsObject);
109
+ if (Array.isArray(obj)) {
110
+ const arrClone = [];
111
+ visited.set(objAsObject, arrClone);
112
+ for (let i = 0; i < obj.length; i++) {
113
+ arrClone[i] = deepClone(obj[i], visited);
114
+ }
115
+ return arrClone;
116
+ }
117
+ if (obj instanceof Date)
118
+ return new Date(obj.getTime());
119
+ if (obj instanceof RegExp)
120
+ return new RegExp(obj.source, obj.flags);
121
+ const clone = Object.create(Object.getPrototypeOf(obj));
122
+ visited.set(objAsObject, clone);
123
+ for (const key of Object.keys(obj)) {
124
+ clone[key] = deepClone(obj[key], visited);
125
+ }
126
+ return clone;
127
+ }