typeorm-dto-generator 1.0.1 → 1.0.3

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/dist/index.js CHANGED
@@ -1,772 +1,837 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { Node, Project } from 'ts-morph';
4
- export class TORMDTOGenerator {
5
- config;
6
- project;
7
- constructor(config = {}) {
8
- this.config = this.createConfig(config);
9
- this.project = new Project({
10
- tsConfigFilePath: this.config.apiTsConfigPath
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.ts
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ TORMDTOGenerator: () => TORMDTOGenerator
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+ var import_fs = __toESM(require("fs"));
36
+ var import_path = __toESM(require("path"));
37
+ var import_ts_morph = require("ts-morph");
38
+ var TORMDTOGenerator = class {
39
+ config;
40
+ project;
41
+ sourceInfoByClassName = /* @__PURE__ */ new Map();
42
+ constructor(config) {
43
+ this.config = this.createConfig(config);
44
+ this.project = new import_ts_morph.Project({
45
+ skipAddingFilesFromTsConfig: true
46
+ });
47
+ }
48
+ async run() {
49
+ this.loadEntitySourceInfo();
50
+ const metas = await this.createEntityMetas();
51
+ import_fs.default.rmSync(this.config.dtoOutputDir, {
52
+ recursive: true,
53
+ force: true
54
+ });
55
+ import_fs.default.mkdirSync(this.config.dtoOutputDir, {
56
+ recursive: true
57
+ });
58
+ this.ensureCommonTypesFile(metas);
59
+ this.validateCommonTypeCoverage(metas);
60
+ for (const meta of metas) {
61
+ this.writeFile(
62
+ import_path.default.join(this.config.dtoOutputDir, meta.dtoFileName),
63
+ this.createDTOContent(meta, metas)
64
+ );
65
+ }
66
+ this.writeFile(
67
+ import_path.default.join(this.config.dtoOutputDir, `index.ts`),
68
+ `${this.createDTOIndexContent(metas)}
69
+ `
70
+ );
71
+ this.writeFile(
72
+ this.config.mapperOutputFile,
73
+ this.createGeneratedMapperContent(metas)
74
+ );
75
+ this.log(`Generated DTO files: ${metas.length}`);
76
+ this.log(`Generated DTO directory: ${this.config.dtoOutputDir}`);
77
+ this.log(`Generated mapper file: ${this.config.mapperOutputFile}`);
78
+ return {
79
+ metas,
80
+ dtoOutputDir: this.config.dtoOutputDir,
81
+ mapperOutputFile: this.config.mapperOutputFile
82
+ };
83
+ }
84
+ log(message) {
85
+ if (this.config.debug)
86
+ console.log(message);
87
+ }
88
+ createConfig(config) {
89
+ if (!config.datasource)
90
+ throw new Error("datasource is required");
91
+ const root = process.cwd();
92
+ const dtoOutputDir = config.dtoOutputDir || import_path.default.join(root, "dto");
93
+ const mapperOutputFile = config.mapperOutputFile || import_path.default.join(root, "dto.mapper.ts");
94
+ const sharedDTOImportPath = config.sharedDTOImportPath || import_path.default.relative(import_path.default.parse(mapperOutputFile).dir, dtoOutputDir).replaceAll("\\", "/");
95
+ return {
96
+ debug: config.debug || false,
97
+ dtoOutputDir,
98
+ mapperOutputFile,
99
+ sharedDTOImportPath: sharedDTOImportPath.startsWith(".") ? sharedDTOImportPath : `./${sharedDTOImportPath}`,
100
+ includeExtensionOnImports: config.includeExtensionOnImports ?? true,
101
+ excludePropertyNames: config.excludePropertyNames ?? [
102
+ "password",
103
+ "passwordHash",
104
+ "secret",
105
+ "refreshToken",
106
+ "accessToken",
107
+ "keyHash",
108
+ "token",
109
+ "otp",
110
+ "salt"
111
+ ],
112
+ customScalarTypeMap: config.customScalarTypeMap ?? {
113
+ BigInt: "string",
114
+ bigint: "string",
115
+ Buffer: "string",
116
+ Decimal: "string"
117
+ },
118
+ ...config
119
+ };
120
+ }
121
+ writeFile(filePath, content) {
122
+ import_fs.default.mkdirSync(import_path.default.dirname(filePath), {
123
+ recursive: true
124
+ });
125
+ import_fs.default.writeFileSync(filePath, content);
126
+ }
127
+ toKebabCase(value) {
128
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
129
+ }
130
+ unique(items) {
131
+ return [...new Set(items)];
132
+ }
133
+ createRelativeJSImportPath(fromFile, targetFile) {
134
+ const relativePath = import_path.default.relative(import_path.default.dirname(fromFile), targetFile).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts)$/, this.config.includeExtensionOnImports ? ".ts" : "");
135
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
136
+ }
137
+ getCommonTypesFilePath() {
138
+ return import_path.default.join(this.config.dtoOutputDir, "common.dto.ts");
139
+ }
140
+ getCommonTypesImportSpecifier(dtoFilePath) {
141
+ return this.createRelativeJSImportPath(dtoFilePath, this.getCommonTypesFilePath());
142
+ }
143
+ getDatasourceEntities() {
144
+ const entities = this.config.datasource.options.entities ?? [];
145
+ if (Array.isArray(entities)) {
146
+ return entities;
147
+ }
148
+ return Object.values(entities);
149
+ }
150
+ normalizeEntityPattern(pattern) {
151
+ return pattern.replace(/\\/g, "/");
152
+ }
153
+ isSourceFilePath(filePath) {
154
+ return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(filePath);
155
+ }
156
+ isEntitySourceFilePath(filePath) {
157
+ return /\.entity\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(filePath);
158
+ }
159
+ normalizeAbsolutePath(filePath) {
160
+ return import_path.default.resolve(filePath).replace(/\\/g, "/").toLowerCase();
161
+ }
162
+ isIgnoredSourcePath(filePath) {
163
+ const normalized = this.normalizeAbsolutePath(filePath);
164
+ const dtoOutputDir = this.normalizeAbsolutePath(this.config.dtoOutputDir);
165
+ const mapperOutputFile = this.normalizeAbsolutePath(this.config.mapperOutputFile);
166
+ const ignoredSegments = [
167
+ "/node_modules/",
168
+ "/.git/",
169
+ "/.next/",
170
+ "/dist/",
171
+ "/build/",
172
+ "/coverage/"
173
+ ];
174
+ if (ignoredSegments.some((segment) => normalized.includes(segment))) {
175
+ return true;
176
+ }
177
+ if (normalized === dtoOutputDir || normalized.startsWith(`${dtoOutputDir}/`)) {
178
+ return true;
179
+ }
180
+ return normalized === mapperOutputFile;
181
+ }
182
+ discoverProjectSourcePaths() {
183
+ const result = [];
184
+ const walk = (directory) => {
185
+ if (!import_fs.default.existsSync(directory)) {
186
+ return;
187
+ }
188
+ for (const entry of import_fs.default.readdirSync(directory, { withFileTypes: true })) {
189
+ const fullPath = import_path.default.join(directory, entry.name);
190
+ const normalized = fullPath.replace(/\\/g, "/");
191
+ if (this.isIgnoredSourcePath(normalized)) {
192
+ continue;
193
+ }
194
+ if (entry.isDirectory()) {
195
+ walk(fullPath);
196
+ continue;
197
+ }
198
+ if (entry.isFile() && this.isSourceFilePath(fullPath)) {
199
+ result.push(fullPath);
200
+ }
201
+ }
202
+ };
203
+ walk(process.cwd());
204
+ return result;
205
+ }
206
+ addEntityOptionSourceFiles() {
207
+ const sourceFiles = [];
208
+ for (const entity of this.getDatasourceEntities()) {
209
+ if (typeof entity !== "string") {
210
+ continue;
211
+ }
212
+ sourceFiles.push(...this.project.addSourceFilesAtPaths(this.normalizeEntityPattern(entity)));
213
+ }
214
+ return sourceFiles;
215
+ }
216
+ addDiscoveredSourceFiles() {
217
+ const paths = this.discoverProjectSourcePaths();
218
+ return paths.map((filePath) => {
219
+ return this.project.addSourceFileAtPathIfExists(filePath);
220
+ }).filter((sourceFile) => {
221
+ return Boolean(sourceFile);
222
+ });
223
+ }
224
+ loadEntitySourceInfo() {
225
+ const fromEntityOptions = this.addEntityOptionSourceFiles();
226
+ this.addDiscoveredSourceFiles();
227
+ const candidateFiles = fromEntityOptions.length ? fromEntityOptions : this.project.getSourceFiles();
228
+ for (const sourceFile of candidateFiles) {
229
+ for (const classDeclaration of sourceFile.getClasses()) {
230
+ if (!this.isEntityClass(classDeclaration)) {
231
+ continue;
232
+ }
233
+ this.sourceInfoByClassName.set(classDeclaration.getNameOrThrow(), {
234
+ filePath: sourceFile.getFilePath(),
235
+ enumTypeByProperty: this.collectEnumTypesByProperty(classDeclaration),
236
+ typeByProperty: this.collectTypesByProperty(classDeclaration)
11
237
  });
12
- }
13
- run() {
14
- const entitySourceFiles = this.project.addSourceFilesAtPaths(this.config.entityGlob);
15
- const metas = this.createEntityMetas(entitySourceFiles);
16
- fs.rmSync(this.config.dtoOutputDir, {
17
- recursive: true,
18
- force: true
19
- });
20
- fs.mkdirSync(this.config.dtoOutputDir, {
21
- recursive: true
22
- });
23
- this.ensureCommonTypesFile(metas);
24
- this.validateCommonTypeCoverage(metas);
25
- for (const meta of metas) {
26
- this.writeFile(path.join(this.config.dtoOutputDir, meta.dtoFileName), this.createDTOContent(meta, metas));
27
- }
28
- this.writeFile(path.join(this.config.dtoOutputDir, 'index.ts'), `${this.createDTOIndexContent(metas)}\n`);
29
- this.writeFile(this.config.mapperOutputFile, this.createGeneratedMapperContent(metas));
30
- console.log(`Generated DTO files: ${metas.length}`);
31
- console.log(`Generated DTO directory: ${this.config.dtoOutputDir}`);
32
- console.log(`Generated mapper file: ${this.config.mapperOutputFile}`);
33
- return {
34
- metas,
35
- dtoOutputDir: this.config.dtoOutputDir,
36
- mapperOutputFile: this.config.mapperOutputFile
37
- };
38
- }
39
- createConfig(config) {
40
- const root = process.cwd();
41
- const resolvedConfig = {
42
- apiTsConfigPath: path.join(root, 'packages/api/tsconfig.json'),
43
- entityGlob: path.join(root, 'packages/api/src/databases/livingkostpro/entities/**/*.ts'),
44
- dtoOutputDir: path.join(root, 'packages/lib/src/dto/generated'),
45
- mapperOutputFile: path.join(root, 'packages/api/src/common/dto/generated-dto.mapper.ts'),
46
- sharedDTOImportPath: '@livingkostpro/lib/dto',
47
- includeGetters: false,
48
- excludePropertyNames: [
49
- 'password',
50
- 'passwordHash',
51
- 'secret',
52
- 'refreshToken',
53
- 'accessToken',
54
- 'keyHash',
55
- 'token',
56
- 'otp',
57
- 'salt'
58
- ],
59
- columnDecorators: [
60
- 'Column',
61
- 'PrimaryColumn',
62
- 'PrimaryGeneratedColumn',
63
- 'CreateDateColumn',
64
- 'UpdateDateColumn',
65
- 'DeleteDateColumn',
66
- 'VersionColumn',
67
- 'RelationId',
68
- 'DatetimeUTCTimestampColumn',
69
- 'DatetimeColumn',
70
- 'DatetimeNullColumn',
71
- 'BooleanFalseColumn',
72
- 'DecimalColumn',
73
- 'CurrencyColumn',
74
- 'SortOrderColumn',
75
- 'DescriptionNullColumn',
76
- 'DescriptionColumn',
77
- 'NameColumn',
78
- 'NameNullColumn',
79
- 'CodeColumn',
80
- 'JSONNullColumn',
81
- 'UUIDNullColumn'
82
- ],
83
- relationDecorators: [
84
- 'OneToOne',
85
- 'OneToMany',
86
- 'ManyToOne',
87
- 'ManyToMany'
88
- ],
89
- arrayRelationDecorators: [
90
- 'OneToMany',
91
- 'ManyToMany'
92
- ],
93
- customScalarTypeMap: {
94
- BigInt: 'string',
95
- bigint: 'string',
96
- Buffer: 'string',
97
- Decimal: 'string'
98
- },
99
- ...config
100
- };
101
- return resolvedConfig;
102
- }
103
- writeFile(filePath, content) {
104
- fs.mkdirSync(path.dirname(filePath), {
105
- recursive: true
106
- });
107
- fs.writeFileSync(filePath, content);
108
- }
109
- toKebabCase(value) {
110
- return value
111
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
112
- .replace(/[\s_]+/g, '-')
113
- .toLowerCase();
114
- }
115
- unique(items) {
116
- return [...new Set(items)];
117
- }
118
- cleanTypeText(value) {
119
- return value
120
- .replace(/import\(".*?"\)\./g, '')
121
- .replace(/\s+/g, ' ')
122
- .trim();
123
- }
124
- createRelativeJSImportPath(fromFile, targetFile) {
125
- const relativePath = path
126
- .relative(path.dirname(fromFile), targetFile)
127
- .replace(/\\/g, '/')
128
- .replace(/\.(ts|tsx|mts|cts)$/, '.ts');
129
- return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
130
- }
131
- getCommonTypesFilePath() {
132
- return path.join(this.config.dtoOutputDir, 'common.dto.ts');
133
- }
134
- getCommonTypesImportSpecifier(dtoFilePath) {
135
- return this.createRelativeJSImportPath(dtoFilePath, this.getCommonTypesFilePath());
136
- }
137
- getDecorator(property, names) {
138
- return property.getDecorators().find((decorator) => {
139
- return names.includes(decorator.getName());
140
- });
141
- }
142
- hasDecorator(property, names) {
143
- return Boolean(this.getDecorator(property, names));
144
- }
145
- isEntityClass(classDeclaration) {
146
- return classDeclaration.getDecorators().some((decorator) => {
147
- return decorator.getName() === 'Entity';
148
- });
149
- }
150
- getDecoratorArgumentsText(decorator) {
151
- return decorator?.getArguments().map((argument) => argument.getText()) ?? [];
152
- }
153
- getDecoratorObjectLiteral(decorator) {
154
- const argument = decorator?.getArguments().find((item) => {
155
- return Node.isObjectLiteralExpression(item);
156
- });
157
- return argument && Node.isObjectLiteralExpression(argument) ? argument : null;
158
- }
159
- getObjectLiteralProperty(objectLiteral, propertyName) {
160
- const property = objectLiteral?.getProperty(propertyName);
161
- return property && Node.isPropertyAssignment(property) ? property : null;
162
- }
163
- getPropertyAssignmentInitializerText(property) {
164
- return property?.getInitializer()?.getText() ?? null;
165
- }
166
- hasDecoratorOption(property, decoratorNames, optionName, optionValue) {
167
- const decorator = this.getDecorator(property, decoratorNames);
168
- const objectLiteral = this.getDecoratorObjectLiteral(decorator);
169
- const option = this.getObjectLiteralProperty(objectLiteral, optionName);
170
- const initializerText = this.getPropertyAssignmentInitializerText(option);
171
- if (initializerText) {
172
- return initializerText === optionValue;
173
- }
174
- const argumentsText = this.getDecoratorArgumentsText(decorator);
175
- const pattern = new RegExp(`${optionName}\\s*:\\s*${optionValue}`);
176
- return argumentsText.some((text) => {
177
- return pattern.test(text);
178
- });
179
- }
180
- hasSelectFalse(property) {
181
- return this.hasDecoratorOption(property, this.config.columnDecorators, 'select', 'false');
182
- }
183
- isExcludedProperty(property) {
184
- return this.config.excludePropertyNames.includes(property.getName()) || this.hasSelectFalse(property);
185
- }
186
- isColumnProperty(property) {
187
- if (this.isExcludedProperty(property)) {
188
- return false;
189
- }
190
- if (this.hasDecorator(property, this.config.relationDecorators)) {
191
- return false;
192
- }
193
- return this.hasDecorator(property, this.config.columnDecorators);
194
- }
195
- isRelationProperty(property) {
196
- if (this.isExcludedProperty(property)) {
197
- return false;
198
- }
199
- return this.hasDecorator(property, this.config.relationDecorators);
200
- }
201
- collectProperties(classDeclaration, visitedClassNames = new Set()) {
202
- const className = classDeclaration.getName();
203
- if (className && visitedClassNames.has(className)) {
204
- return [];
205
- }
206
- if (className) {
207
- visitedClassNames.add(className);
208
- }
209
- const baseClass = this.getBaseClass(classDeclaration);
210
- const baseProperties = baseClass ? this.collectProperties(baseClass, visitedClassNames) : [];
211
- const ownProperties = classDeclaration.getProperties();
212
- return [...baseProperties, ...ownProperties];
213
- }
214
- getBaseClass(classDeclaration) {
215
- const resolvedBaseClass = classDeclaration.getBaseClass();
216
- if (resolvedBaseClass) {
217
- return resolvedBaseClass;
218
- }
219
- const extendsExpression = classDeclaration.getExtends()?.getExpression().getText();
220
- if (!extendsExpression) {
221
- return null;
222
- }
223
- return this.findClassDeclarationByName(extendsExpression);
224
- }
225
- findClassDeclarationByName(className) {
226
- for (const sourceFile of this.project.getSourceFiles()) {
227
- const classDeclaration = sourceFile.getClass(className);
228
- if (classDeclaration) {
229
- return classDeclaration;
230
- }
231
- }
238
+ }
239
+ }
240
+ }
241
+ isEntityClass(classDeclaration) {
242
+ return classDeclaration.getDecorators().some((decorator) => {
243
+ return decorator.getName() === "Entity";
244
+ });
245
+ }
246
+ getDecoratorObjectLiteral(property) {
247
+ for (const decorator of property.getDecorators()) {
248
+ const objectLiteral = decorator.getArguments().find((argument) => {
249
+ return import_ts_morph.Node.isObjectLiteralExpression(argument);
250
+ });
251
+ if (objectLiteral && import_ts_morph.Node.isObjectLiteralExpression(objectLiteral)) {
252
+ return objectLiteral;
253
+ }
254
+ }
255
+ return null;
256
+ }
257
+ getColumnEnumInitializer(property) {
258
+ var _a;
259
+ const objectLiteral = this.getDecoratorObjectLiteral(property);
260
+ const enumProperty = objectLiteral == null ? void 0 : objectLiteral.getProperty("enum");
261
+ if (!enumProperty || !import_ts_morph.Node.isPropertyAssignment(enumProperty)) {
262
+ return null;
263
+ }
264
+ return ((_a = enumProperty.getInitializer()) == null ? void 0 : _a.getText()) ?? null;
265
+ }
266
+ getEnumTypeNameFromProperty(property) {
267
+ var _a;
268
+ const typeText = (_a = property.getTypeNode()) == null ? void 0 : _a.getText();
269
+ if (typeText && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(typeText)) {
270
+ return typeText;
271
+ }
272
+ const enumInitializer = this.getColumnEnumInitializer(property);
273
+ if (enumInitializer && /^[A-Za-z_$][A-Za-z0-9_$.]*$/.test(enumInitializer)) {
274
+ return enumInitializer.split(".").pop() ?? enumInitializer;
275
+ }
276
+ return null;
277
+ }
278
+ collectEnumTypesByProperty(classDeclaration) {
279
+ const enumTypeByProperty = /* @__PURE__ */ new Map();
280
+ for (const property of this.collectProperties(classDeclaration)) {
281
+ const enumTypeName = this.getEnumTypeNameFromProperty(property);
282
+ if (enumTypeName) {
283
+ enumTypeByProperty.set(property.getName(), enumTypeName);
284
+ }
285
+ }
286
+ return enumTypeByProperty;
287
+ }
288
+ cleanTypeText(value) {
289
+ return value.replace(/import\(".*?"\)\./g, "").replace(/\s+/g, " ").trim();
290
+ }
291
+ collectTypesByProperty(classDeclaration) {
292
+ var _a;
293
+ const typeByProperty = /* @__PURE__ */ new Map();
294
+ for (const property of this.collectProperties(classDeclaration)) {
295
+ const typeText = (_a = property.getTypeNode()) == null ? void 0 : _a.getText();
296
+ if (typeText) {
297
+ typeByProperty.set(property.getName(), this.cleanTypeText(typeText));
298
+ }
299
+ }
300
+ return typeByProperty;
301
+ }
302
+ collectProperties(classDeclaration, visitedClassNames = /* @__PURE__ */ new Set()) {
303
+ const className = classDeclaration.getName();
304
+ if (className && visitedClassNames.has(className)) {
305
+ return [];
306
+ }
307
+ if (className) {
308
+ visitedClassNames.add(className);
309
+ }
310
+ const baseClass = classDeclaration.getBaseClass();
311
+ const baseProperties = baseClass ? this.collectProperties(baseClass, visitedClassNames) : [];
312
+ return [...baseProperties, ...classDeclaration.getProperties()];
313
+ }
314
+ getClassName(target, fallback) {
315
+ return typeof target === "function" ? target.name : fallback;
316
+ }
317
+ async ensureDatasourceMetadata() {
318
+ if (this.config.datasource.entityMetadatas.length) {
319
+ return;
320
+ }
321
+ const datasource = this.config.datasource;
322
+ if (typeof datasource.buildMetadatas !== "function") {
323
+ throw new Error("DTOGeneratorService config error: datasource cannot build entity metadata.");
324
+ }
325
+ await datasource.buildMetadatas();
326
+ }
327
+ isExcludedColumn(column) {
328
+ return this.config.excludePropertyNames.includes(column.propertyName) || column.isSelect === false || Boolean(column.relationMetadata);
329
+ }
330
+ isDateColumn(column) {
331
+ return column.type === Date || [
332
+ "date",
333
+ "datetime",
334
+ "datetime2",
335
+ "timestamp",
336
+ "timestamp with time zone",
337
+ "timestamp without time zone",
338
+ "time",
339
+ "time with time zone",
340
+ "time without time zone"
341
+ ].includes(String(column.type));
342
+ }
343
+ isJsonColumn(column) {
344
+ return ["json", "jsonb", "simple-json"].includes(String(column.type));
345
+ }
346
+ resolveEnumTypeName(entityName, column) {
347
+ var _a;
348
+ const enumTypeName = (_a = this.sourceInfoByClassName.get(entityName)) == null ? void 0 : _a.enumTypeByProperty.get(column.propertyName);
349
+ if (enumTypeName) {
350
+ return enumTypeName;
351
+ }
352
+ if (column.enumName) {
353
+ return column.enumName;
354
+ }
355
+ return `${entityName}${column.propertyName.charAt(0).toUpperCase()}${column.propertyName.slice(1)}`;
356
+ }
357
+ resolveSourcePropertyType(entityName, propertyName) {
358
+ var _a;
359
+ const sourceType = (_a = this.sourceInfoByClassName.get(entityName)) == null ? void 0 : _a.typeByProperty.get(propertyName);
360
+ if (!sourceType) {
361
+ return null;
362
+ }
363
+ return sourceType.split("|").map((item) => item.trim()).map((item) => item === "Date" ? "string" : item).join(" | ");
364
+ }
365
+ resolveColumnType(entityName, column) {
366
+ var _a;
367
+ if ((_a = column.enum) == null ? void 0 : _a.length) {
368
+ return this.resolveEnumTypeName(entityName, column);
369
+ }
370
+ const sourceType = this.resolveSourcePropertyType(entityName, column.propertyName);
371
+ if (sourceType) {
372
+ return sourceType;
373
+ }
374
+ const typeText = typeof column.type === "function" ? column.type.name : String(column.type);
375
+ if (this.config.customScalarTypeMap[typeText]) {
376
+ return this.config.customScalarTypeMap[typeText];
377
+ }
378
+ if (this.isDateColumn(column)) {
379
+ return "string";
380
+ }
381
+ if (column.type === String || [
382
+ "char",
383
+ "varchar",
384
+ "nvarchar",
385
+ "text",
386
+ "tinytext",
387
+ "mediumtext",
388
+ "longtext",
389
+ "uuid",
390
+ "simple-enum"
391
+ ].includes(typeText)) {
392
+ return "string";
393
+ }
394
+ if (column.type === Number || [
395
+ "int",
396
+ "integer",
397
+ "tinyint",
398
+ "smallint",
399
+ "mediumint",
400
+ "bigint",
401
+ "float",
402
+ "double",
403
+ "double precision",
404
+ "real",
405
+ "decimal",
406
+ "numeric"
407
+ ].includes(typeText)) {
408
+ return "number";
409
+ }
410
+ if (column.type === Boolean || ["bool", "boolean"].includes(typeText)) {
411
+ return "boolean";
412
+ }
413
+ if (this.isJsonColumn(column)) {
414
+ return "any";
415
+ }
416
+ if (typeText === "object") {
417
+ return "Record<string, unknown>";
418
+ }
419
+ return typeText;
420
+ }
421
+ appendNullType(typeText) {
422
+ if (typeText.split("|").map((item) => item.trim()).includes("null")) {
423
+ return typeText;
424
+ }
425
+ return `${typeText} | null`;
426
+ }
427
+ columnToMeta(entityName, column) {
428
+ const resolvedType = this.resolveColumnType(entityName, column);
429
+ return {
430
+ name: column.propertyName,
431
+ optional: false,
432
+ type: column.isNullable ? this.appendNullType(resolvedType) : resolvedType,
433
+ isDate: this.isDateColumn(column),
434
+ mapAsNumber: resolvedType === "number",
435
+ enumValues: column.enum ? [...column.enum] : void 0
436
+ };
437
+ }
438
+ relationToMeta(relation) {
439
+ const targetClassName = this.getClassName(relation.inverseEntityMetadata.target, relation.inverseEntityMetadata.name);
440
+ return {
441
+ name: relation.propertyName,
442
+ targetClassName,
443
+ targetDTOName: `${targetClassName}DTO`,
444
+ isArray: relation.isOneToMany || relation.isManyToMany,
445
+ nullable: relation.isNullable
446
+ };
447
+ }
448
+ async createEntityMetas() {
449
+ await this.ensureDatasourceMetadata();
450
+ const metas = this.config.datasource.entityMetadatas.map((metadata) => {
451
+ var _a;
452
+ const className = this.getClassName(metadata.target, metadata.name);
453
+ const moduleName = this.toKebabCase(className);
454
+ const columns = metadata.columns.filter((column) => !this.isExcludedColumn(column)).map((column) => this.columnToMeta(className, column));
455
+ const relations = metadata.relations.map((relation) => this.relationToMeta(relation));
456
+ return {
457
+ className,
458
+ dtoName: `${className}DTO`,
459
+ moduleName,
460
+ dtoFileName: `${moduleName}.dto.ts`,
461
+ mapperName: `parse${className}DTO`,
462
+ filePath: (_a = this.sourceInfoByClassName.get(className)) == null ? void 0 : _a.filePath,
463
+ columns,
464
+ relations
465
+ };
466
+ });
467
+ return metas.sort((a, b) => {
468
+ return a.className.localeCompare(b.className);
469
+ });
470
+ }
471
+ findMetaByClassName(metas, className) {
472
+ return metas.find((meta) => {
473
+ return meta.className === className;
474
+ });
475
+ }
476
+ getValidRelations(meta, metas) {
477
+ return meta.relations.filter((relation) => {
478
+ return Boolean(this.findMetaByClassName(metas, relation.targetClassName));
479
+ });
480
+ }
481
+ createDTOContent(meta, metas) {
482
+ var _a;
483
+ const dtoFilePath = import_path.default.join(this.config.dtoOutputDir, meta.dtoFileName);
484
+ const validRelations = this.getValidRelations(meta, metas);
485
+ const relationImports = this.unique(
486
+ validRelations.filter((relation) => relation.targetClassName !== meta.className).map((relation) => relation.targetClassName)
487
+ ).map((targetClassName) => {
488
+ const targetMeta = this.findMetaByClassName(metas, targetClassName);
489
+ if (!targetMeta) {
232
490
  return null;
233
- }
234
- isNullishTypeText(typeText) {
235
- return typeText === 'null' || typeText === 'undefined';
236
- }
237
- isNullableTypeText(typeText) {
238
- return typeText.split('|').map((item) => item.trim()).includes('null');
239
- }
240
- getPropertyTypeText(property) {
241
- return this.cleanTypeText(property.getTypeNode()?.getText() ?? property.getType().getText(property));
242
- }
243
- getAccessorTypeText(accessor) {
244
- return this.cleanTypeText(accessor.getReturnTypeNode()?.getText() ?? accessor.getReturnType().getText(accessor));
245
- }
246
- isDateColumn(property) {
247
- const typeText = this.getPropertyTypeText(property);
248
- return typeText === 'Date' || typeText.includes('Date |') || typeText.includes('| Date');
249
- }
250
- isNullableColumn(property) {
251
- const typeText = this.getPropertyTypeText(property);
252
- return this.isNullableTypeText(typeText) || this.hasDecoratorOption(property, this.config.columnDecorators, 'nullable', 'true');
253
- }
254
- resolveEnumType(type) {
255
- const symbol = type.getSymbol() ?? type.getAliasSymbol();
256
- const declarations = symbol?.getDeclarations() ?? [];
257
- const enumDeclaration = declarations.find((declaration) => {
258
- return Node.isEnumDeclaration(declaration);
259
- });
260
- if (!enumDeclaration || !Node.isEnumDeclaration(enumDeclaration)) {
261
- return null;
262
- }
263
- return enumDeclaration.getName();
264
- }
265
- resolveEnumTypeFromProperty(property) {
266
- const typeText = this.getPropertyTypeText(property);
267
- const normalizedTypeText = this.unwrapNullishType(typeText);
268
- const enumByType = this.findEnumDeclarationByName(normalizedTypeText);
269
- if (enumByType) {
270
- return enumByType.getName();
271
- }
272
- const columnDecorator = this.getDecorator(property, this.config.columnDecorators);
273
- const objectLiteral = this.getDecoratorObjectLiteral(columnDecorator);
274
- const enumProperty = this.getObjectLiteralProperty(objectLiteral, 'enum');
275
- const enumInitializerText = this.getPropertyAssignmentInitializerText(enumProperty);
276
- if (!enumInitializerText) {
277
- return null;
278
- }
279
- const enumName = enumInitializerText.split('.').pop() ?? enumInitializerText;
280
- const enumDeclaration = this.findEnumDeclarationByName(enumName);
281
- if (!enumDeclaration) {
282
- return null;
283
- }
284
- return enumDeclaration.getName();
285
- }
286
- findEnumDeclarationByName(enumName) {
287
- for (const sourceFile of this.project.getSourceFiles()) {
288
- const enumDeclaration = sourceFile.getEnum(enumName);
289
- if (enumDeclaration) {
290
- return enumDeclaration;
291
- }
292
- }
293
- return null;
294
- }
295
- unwrapNullishType(typeText) {
296
- return typeText
297
- .split('|')
298
- .map((item) => item.trim())
299
- .filter((item) => item !== 'null' && item !== 'undefined')
300
- .join(' | ');
301
- }
302
- resolveSingleColumnType(type, fallback) {
303
- const typeText = this.cleanTypeText(fallback);
304
- if (this.isNullishTypeText(typeText)) {
305
- return typeText;
306
- }
307
- if (this.config.customScalarTypeMap[typeText]) {
308
- return this.config.customScalarTypeMap[typeText];
309
- }
310
- if (typeText === 'Date') {
311
- return 'string';
312
- }
313
- if (type.isString()) {
314
- return 'string';
315
- }
316
- if (type.isNumber()) {
317
- return 'number';
318
- }
319
- if (type.isBoolean()) {
320
- return 'boolean';
321
- }
322
- if (type.isStringLiteral()) {
323
- return JSON.stringify(type.getLiteralValue());
324
- }
325
- if (type.isNumberLiteral()) {
326
- return String(type.getLiteralValue());
327
- }
328
- if (typeText === 'true' || typeText === 'false') {
329
- return typeText;
330
- }
331
- const enumType = this.resolveEnumType(type);
332
- if (enumType) {
333
- return enumType;
334
- }
335
- if (typeText.includes('Date')) {
336
- return typeText.replace(/\bDate\b/g, 'string');
337
- }
338
- if (typeText.endsWith('[]')) {
339
- return 'unknown[]';
340
- }
341
- if (typeText.startsWith('Array<')) {
342
- return 'unknown[]';
343
- }
344
- if (typeText === 'object') {
345
- return 'Record<string, unknown>';
346
- }
347
- return typeText;
348
- }
349
- resolveColumnType(property) {
350
- const enumType = this.resolveEnumTypeFromProperty(property);
351
- if (enumType) {
352
- return enumType;
353
- }
354
- const typeNodeText = this.getPropertyTypeText(property);
355
- const type = property.getType();
356
- if (typeNodeText.includes('|')) {
357
- const result = typeNodeText.split('|').map((item) => {
358
- return this.resolveSingleColumnType(type, item.trim());
359
- });
360
- return this.unique(result).join(' | ');
361
- }
362
- return this.resolveSingleColumnType(type, typeNodeText);
363
- }
364
- resolveAccessorType(accessor) {
365
- const typeNodeText = this.getAccessorTypeText(accessor);
366
- const type = accessor.getReturnType();
367
- if (typeNodeText.includes('|')) {
368
- const result = typeNodeText.split('|').map((item) => {
369
- return this.resolveSingleColumnType(type, item.trim());
370
- });
371
- return this.unique(result).join(' | ');
372
- }
373
- return this.resolveSingleColumnType(type, typeNodeText);
374
- }
375
- appendNullType(typeText) {
376
- if (typeText.split('|').map((item) => item.trim()).includes('null')) {
377
- return typeText;
378
- }
379
- return `${typeText} | null`;
380
- }
381
- propertyToColumnMeta(property) {
382
- const resolvedType = this.resolveColumnType(property);
383
- const nullable = this.isNullableColumn(property);
384
- return {
385
- name: property.getName(),
386
- optional: property.hasQuestionToken(),
387
- type: nullable ? this.appendNullType(resolvedType) : resolvedType,
388
- isDate: this.isDateColumn(property)
389
- };
390
- }
391
- accessorToColumnMeta(accessor) {
392
- const resolvedType = this.resolveAccessorType(accessor);
393
- return {
394
- name: accessor.getName(),
395
- optional: false,
396
- type: resolvedType,
397
- isDate: this.getAccessorTypeText(accessor).includes('Date')
398
- };
399
- }
400
- isExcludedGetter(accessor) {
401
- return this.config.excludePropertyNames.includes(accessor.getName());
402
- }
403
- collectGetAccessors(classDeclaration, visitedClassNames = new Set()) {
404
- const className = classDeclaration.getName();
405
- if (className && visitedClassNames.has(className)) {
406
- return [];
407
- }
408
- if (className) {
409
- visitedClassNames.add(className);
410
- }
411
- const baseClass = this.getBaseClass(classDeclaration);
412
- const baseGetters = baseClass ? this.collectGetAccessors(baseClass, visitedClassNames) : [];
413
- const ownGetters = classDeclaration.getGetAccessors();
414
- return [...baseGetters, ...ownGetters];
415
- }
416
- unwrapRelationType(typeText) {
417
- return this.cleanTypeText(typeText)
418
- .replace(/^Promise<(.+)>$/, '$1')
419
- .replace(/^Array<(.+)>$/, '$1')
420
- .replace(/\[\]$/, '')
421
- .split('|')
422
- .map((item) => item.trim())
423
- .filter((item) => item !== 'null' && item !== 'undefined')
424
- .join(' | ');
425
- }
426
- getRelationTargetClassName(property) {
427
- const decorator = this.getDecorator(property, this.config.relationDecorators);
428
- const firstArgument = decorator?.getArguments()[0]?.getText() ?? '';
429
- const arrowMatch = firstArgument.match(/=>\s*([A-Za-z0-9_]+)/);
430
- const returnMatch = firstArgument.match(/return\s+([A-Za-z0-9_]+)/);
431
- if (arrowMatch?.[1]) {
432
- return arrowMatch[1];
433
- }
434
- if (returnMatch?.[1]) {
435
- return returnMatch[1];
436
- }
437
- return this.unwrapRelationType(this.getPropertyTypeText(property));
438
- }
439
- isArrayRelation(property) {
440
- const decorator = this.getDecorator(property, this.config.relationDecorators);
441
- const decoratorName = decorator?.getName() ?? '';
442
- const typeText = this.getPropertyTypeText(property);
443
- return this.config.arrayRelationDecorators.includes(decoratorName)
444
- || typeText.includes('[]')
445
- || typeText.startsWith('Array<')
446
- || typeText.startsWith('Promise<Array<')
447
- || typeText.includes('Promise<') && typeText.includes('[]>');
448
- }
449
- isNullableRelation(property) {
450
- const typeText = this.getPropertyTypeText(property);
451
- return this.isNullableTypeText(typeText) || this.hasDecoratorOption(property, this.config.relationDecorators, 'nullable', 'true');
452
- }
453
- propertyToRelationMeta(property) {
454
- const targetClassName = this.getRelationTargetClassName(property);
455
- return {
456
- name: property.getName(),
457
- targetClassName,
458
- targetDTOName: `${targetClassName}DTO`,
459
- isArray: this.isArrayRelation(property),
460
- nullable: this.isNullableRelation(property)
461
- };
462
- }
463
- createEntityMetas(entitySourceFiles) {
464
- const metas = [];
465
- for (const sourceFile of entitySourceFiles) {
466
- for (const classDeclaration of sourceFile.getClasses()) {
467
- if (!this.isEntityClass(classDeclaration)) {
468
- continue;
469
- }
470
- const className = classDeclaration.getNameOrThrow();
471
- const moduleName = this.toKebabCase(className);
472
- const properties = this.collectProperties(classDeclaration);
473
- const columns = properties
474
- .filter((property) => this.isColumnProperty(property))
475
- .map((property) => this.propertyToColumnMeta(property));
476
- const relations = properties
477
- .filter((property) => this.isRelationProperty(property))
478
- .map((property) => this.propertyToRelationMeta(property));
479
- const takenNames = new Set([
480
- ...columns.map((column) => column.name),
481
- ...relations.map((relation) => relation.name)
482
- ]);
483
- const getterColumns = this.config.includeGetters
484
- ? this.collectGetAccessors(classDeclaration)
485
- .filter((getter) => getter.getParameters().length === 0)
486
- .filter((getter) => !this.isExcludedGetter(getter))
487
- .filter((getter) => !takenNames.has(getter.getName()))
488
- .map((getter) => this.accessorToColumnMeta(getter))
489
- : [];
490
- metas.push({
491
- className,
492
- dtoName: `${className}DTO`,
493
- moduleName,
494
- filePath: classDeclaration.getSourceFile().getFilePath(),
495
- sourceFilePath: sourceFile.getFilePath(),
496
- dtoFileName: `${moduleName}.dto.ts`,
497
- mapperName: `parse${className}DTO`,
498
- columns: [...columns, ...getterColumns],
499
- relations
500
- });
501
- }
502
- }
503
- return metas.sort((a, b) => {
504
- return a.className.localeCompare(b.className);
505
- });
506
- }
507
- findMetaByClassName(metas, className) {
508
- return metas.find((meta) => {
509
- return meta.className === className;
510
- });
511
- }
512
- getValidRelations(meta, metas) {
513
- return meta.relations.filter((relation) => {
514
- return Boolean(this.findMetaByClassName(metas, relation.targetClassName));
515
- });
516
- }
517
- createDTOContent(meta, metas) {
518
- const dtoFilePath = path.join(this.config.dtoOutputDir, meta.dtoFileName);
519
- const validRelations = this.getValidRelations(meta, metas);
520
- const relationImports = this.unique(validRelations
521
- .filter((relation) => {
522
- return relation.targetClassName !== meta.className;
523
- })
524
- .map((relation) => {
525
- return relation.targetClassName;
526
- })).map((targetClassName) => {
527
- const targetMeta = this.findMetaByClassName(metas, targetClassName);
528
- if (!targetMeta) {
529
- return null;
530
- }
531
- const targetPath = path.join(this.config.dtoOutputDir, targetMeta.dtoFileName);
532
- return [targetMeta.dtoName, this.createRelativeJSImportPath(dtoFilePath, targetPath)];
533
- }).filter((item) => {
534
- return item !== null;
535
- });
536
- const relationTypeNames = new Set(validRelations.map((relation) => relation.targetDTOName));
537
- const externalTypeImports = this.collectExternalTypeImports(meta, dtoFilePath, relationTypeNames);
538
- const importMap = new Map();
539
- for (const [typeName, moduleSpecifier] of [...relationImports, ...externalTypeImports]) {
540
- if (!importMap.has(moduleSpecifier)) {
541
- importMap.set(moduleSpecifier, new Set());
542
- }
543
- importMap.get(moduleSpecifier)?.add(typeName);
544
- }
545
- const imports = [...importMap.entries()]
546
- .sort((a, b) => {
547
- return a[0].localeCompare(b[0]);
548
- })
549
- .map(([moduleSpecifier, typeNames]) => {
550
- return `import type { ${[...typeNames].sort((a, b) => a.localeCompare(b)).join(', ')} } from '${moduleSpecifier}'`;
551
- });
552
- const columnLines = meta.columns.map((column) => {
553
- const optional = column.optional ? '?' : '';
554
- return ` ${column.name}${optional}: ${column.type}`;
555
- });
556
- const relationLines = validRelations.map((relation) => {
557
- const type = relation.isArray
558
- ? `${relation.targetDTOName}[]`
559
- : `${relation.targetDTOName}${relation.nullable ? ' | null' : ''}`;
560
- return ` ${relation.name}?: ${type}`;
561
- });
562
- const importBlock = imports.length ? `${imports.join('\n')}\n\n` : '';
563
- const body = [...columnLines, ...relationLines].join('\n');
564
- return `${importBlock}export type ${meta.dtoName} = {\n${body}\n}\n`;
565
- }
566
- collectExternalTypeImports(meta, dtoFilePath, relationTypeNames) {
567
- const symbols = this.collectExternalTypeSymbols(meta, relationTypeNames);
568
- const importSpecifier = this.getCommonTypesImportSpecifier(dtoFilePath);
569
- return [...symbols].map((symbol) => {
570
- return [symbol, importSpecifier];
571
- });
572
- }
573
- collectExternalTypeSymbols(meta, relationTypeNames) {
574
- const symbols = new Set();
575
- const ignoredNames = new Set([
576
- 'string',
577
- 'number',
578
- 'boolean',
579
- 'null',
580
- 'undefined',
581
- 'unknown',
582
- 'any',
583
- 'object',
584
- 'bigint',
585
- 'never',
586
- 'void',
587
- 'true',
588
- 'false',
589
- 'Record',
590
- 'Array',
591
- 'Promise',
592
- 'ReadonlyArray'
593
- ]);
594
- for (const column of meta.columns) {
491
+ }
492
+ const targetPath = import_path.default.join(this.config.dtoOutputDir, targetMeta.dtoFileName);
493
+ return [targetMeta.dtoName, this.createRelativeJSImportPath(dtoFilePath, targetPath)];
494
+ }).filter((item) => {
495
+ return item !== null;
496
+ });
497
+ const relationTypeNames = new Set(validRelations.map((relation) => relation.targetDTOName));
498
+ const externalTypeImports = this.collectExternalTypeImports(meta, dtoFilePath, relationTypeNames);
499
+ const importMap = /* @__PURE__ */ new Map();
500
+ for (const [typeName, moduleSpecifier] of [...relationImports, ...externalTypeImports]) {
501
+ if (!importMap.has(moduleSpecifier)) {
502
+ importMap.set(moduleSpecifier, /* @__PURE__ */ new Set());
503
+ }
504
+ (_a = importMap.get(moduleSpecifier)) == null ? void 0 : _a.add(typeName);
505
+ }
506
+ const imports = [...importMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([moduleSpecifier, typeNames]) => {
507
+ let newModuleSpecifier = moduleSpecifier;
508
+ if (!this.config.includeExtensionOnImports) {
509
+ newModuleSpecifier = newModuleSpecifier.replace(".ts", "");
510
+ }
511
+ return `import type { ${[...typeNames].sort((a, b) => a.localeCompare(b)).join(", ")} } from '${newModuleSpecifier}'`;
512
+ });
513
+ const columnLines = meta.columns.map((column) => {
514
+ const optional = column.optional ? "?" : "";
515
+ return ` ${column.name}${optional}: ${column.type}`;
516
+ });
517
+ const relationLines = validRelations.map((relation) => {
518
+ const type = relation.isArray ? `${relation.targetDTOName}[]` : `${relation.targetDTOName}${relation.nullable ? " | null" : ""}`;
519
+ return ` ${relation.name}?: ${type}`;
520
+ });
521
+ const importBlock = imports.length ? `${imports.join("\n")}
522
+
523
+ ` : "";
524
+ const body = [...columnLines, ...relationLines].join("\n");
525
+ return `${importBlock}export type ${meta.dtoName} = {
526
+ ${body}
527
+ }
528
+ `;
529
+ }
530
+ collectExternalTypeImports(meta, dtoFilePath, relationTypeNames) {
531
+ const symbols = this.collectExternalTypeSymbols(meta, relationTypeNames);
532
+ const importSpecifier = this.getCommonTypesImportSpecifier(dtoFilePath);
533
+ return [...symbols].map((symbol) => {
534
+ return [symbol, importSpecifier];
535
+ });
536
+ }
537
+ collectExternalTypeSymbols(meta, relationTypeNames) {
538
+ const symbols = /* @__PURE__ */ new Set();
539
+ const ignoredNames = /* @__PURE__ */ new Set([
540
+ "string",
541
+ "number",
542
+ "boolean",
543
+ "null",
544
+ "undefined",
545
+ "unknown",
546
+ "any",
547
+ "object",
548
+ "bigint",
549
+ "never",
550
+ "void",
551
+ "true",
552
+ "false",
553
+ "Record",
554
+ "Array",
555
+ "Promise",
556
+ "ReadonlyArray"
557
+ ]);
558
+ for (const column of meta.columns) {
559
+ const identifiers = column.type.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) ?? [];
560
+ for (const identifier of identifiers) {
561
+ if (ignoredNames.has(identifier)) {
562
+ continue;
563
+ }
564
+ if (relationTypeNames.has(identifier) || identifier === meta.dtoName) {
565
+ continue;
566
+ }
567
+ symbols.add(identifier);
568
+ }
569
+ }
570
+ return symbols;
571
+ }
572
+ collectAllExternalTypeSymbols(metas) {
573
+ const symbols = /* @__PURE__ */ new Set();
574
+ for (const meta of metas) {
575
+ const relationTypeNames = new Set(meta.relations.map((relation) => relation.targetDTOName));
576
+ const metaSymbols = this.collectExternalTypeSymbols(meta, relationTypeNames);
577
+ for (const symbol of metaSymbols) {
578
+ symbols.add(symbol);
579
+ }
580
+ }
581
+ return symbols;
582
+ }
583
+ findTypeDeclarationByName(typeName) {
584
+ for (const sourceFile of this.project.getSourceFiles()) {
585
+ if (this.isIgnoredSourcePath(sourceFile.getFilePath())) {
586
+ continue;
587
+ }
588
+ const enumDeclaration = sourceFile.getEnum(typeName);
589
+ if (enumDeclaration) {
590
+ return enumDeclaration;
591
+ }
592
+ const interfaceDeclaration = sourceFile.getInterface(typeName);
593
+ if (interfaceDeclaration) {
594
+ return interfaceDeclaration;
595
+ }
596
+ const typeAliasDeclaration = sourceFile.getTypeAlias(typeName);
597
+ if (typeAliasDeclaration) {
598
+ return typeAliasDeclaration;
599
+ }
600
+ }
601
+ return null;
602
+ }
603
+ findEnumDeclarationByName(typeName) {
604
+ for (const sourceFile of this.project.getSourceFiles()) {
605
+ if (this.isIgnoredSourcePath(sourceFile.getFilePath())) {
606
+ continue;
607
+ }
608
+ const enumDeclaration = sourceFile.getEnum(typeName);
609
+ if (enumDeclaration) {
610
+ return enumDeclaration;
611
+ }
612
+ }
613
+ return null;
614
+ }
615
+ normalizeDeclarationToExportText(typeName, preferEnum = false) {
616
+ const declaration = preferEnum ? this.findEnumDeclarationByName(typeName) ?? this.findTypeDeclarationByName(typeName) : this.findTypeDeclarationByName(typeName);
617
+ if (!declaration) {
618
+ return null;
619
+ }
620
+ const declarationText = declaration.getText().trim();
621
+ if (declarationText.startsWith("export ")) {
622
+ return declarationText;
623
+ }
624
+ return `export ${declarationText}`;
625
+ }
626
+ formatEnumKey(value) {
627
+ const normalized = String(value).replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
628
+ return normalized || `VALUE_${String(value)}`;
629
+ }
630
+ formatEnumValue(value) {
631
+ return typeof value === "number" ? String(value) : `'${value.replace(/'/g, "\\'")}'`;
632
+ }
633
+ createCommonEnumDeclaration(typeName, values) {
634
+ const uniqueValues = this.unique(values);
635
+ const lines = uniqueValues.map((value) => {
636
+ return ` ${this.formatEnumKey(value)} = ${this.formatEnumValue(value)}`;
637
+ });
638
+ return `export enum ${typeName} {
639
+ ${lines.join(",\n")}
640
+ }`;
641
+ }
642
+ ensureCommonTypesFile(metas) {
643
+ var _a;
644
+ const symbols = [...this.collectAllExternalTypeSymbols(metas)].sort((a, b) => a.localeCompare(b));
645
+ const enumBySymbol = /* @__PURE__ */ new Map();
646
+ for (const meta of metas) {
647
+ for (const column of meta.columns) {
648
+ if ((_a = column.enumValues) == null ? void 0 : _a.length) {
649
+ enumBySymbol.set(column.type, column.enumValues);
650
+ }
651
+ }
652
+ }
653
+ const lines = symbols.map((symbol) => {
654
+ const enumValues = enumBySymbol.get(symbol);
655
+ const declarationText = this.normalizeDeclarationToExportText(symbol, Boolean(enumValues));
656
+ if (declarationText) {
657
+ return declarationText.replace(/^export declare /, "export ");
658
+ }
659
+ if (!enumValues) {
660
+ throw new Error(`DTOGeneratorService config error: could not generate common type '${symbol}'.`);
661
+ }
662
+ return this.createCommonEnumDeclaration(symbol, enumValues);
663
+ });
664
+ this.writeFile(this.getCommonTypesFilePath(), `${lines.join("\n\n")}
665
+ `);
666
+ }
667
+ resolveCommonTypesSourceContent() {
668
+ const commonTypesFilePath = this.getCommonTypesFilePath();
669
+ if (!import_fs.default.existsSync(commonTypesFilePath)) {
670
+ return null;
671
+ }
672
+ return import_fs.default.readFileSync(commonTypesFilePath, "utf-8");
673
+ }
674
+ validateCommonTypeCoverage(metas) {
675
+ const commonTypesContent = this.resolveCommonTypesSourceContent();
676
+ if (!commonTypesContent) {
677
+ throw new Error(`DTOGeneratorService config error: common.dto.ts was not generated in '${this.config.dtoOutputDir}'.`);
678
+ }
679
+ const missing = [];
680
+ for (const meta of metas) {
681
+ const relationTypeNames = new Set(meta.relations.map((relation) => relation.targetDTOName));
682
+ const symbols = this.collectExternalTypeSymbols(meta, relationTypeNames);
683
+ for (const symbol of symbols) {
684
+ const pattern = new RegExp(`export\\s+(?:enum|type|interface)\\s+${symbol}\\b`);
685
+ if (!pattern.test(commonTypesContent)) {
686
+ const columns = meta.columns.filter((column) => {
595
687
  const identifiers = column.type.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) ?? [];
596
- for (const identifier of identifiers) {
597
- if (ignoredNames.has(identifier)) {
598
- continue;
599
- }
600
- if (relationTypeNames.has(identifier) || identifier === meta.dtoName) {
601
- continue;
602
- }
603
- symbols.add(identifier);
604
- }
605
- }
606
- return symbols;
607
- }
608
- collectAllExternalTypeSymbols(metas) {
609
- const symbols = new Set();
610
- for (const meta of metas) {
611
- const relationTypeNames = new Set(meta.relations.map((relation) => relation.targetDTOName));
612
- const metaSymbols = this.collectExternalTypeSymbols(meta, relationTypeNames);
613
- for (const symbol of metaSymbols) {
614
- symbols.add(symbol);
615
- }
616
- }
617
- return symbols;
618
- }
619
- findTypeDeclarationByName(typeName) {
620
- for (const sourceFile of this.project.getSourceFiles()) {
621
- const enumDeclaration = sourceFile.getEnum(typeName);
622
- if (enumDeclaration) {
623
- return enumDeclaration;
624
- }
625
- const interfaceDeclaration = sourceFile.getInterface(typeName);
626
- if (interfaceDeclaration) {
627
- return interfaceDeclaration;
628
- }
629
- const typeAliasDeclaration = sourceFile.getTypeAlias(typeName);
630
- if (typeAliasDeclaration) {
631
- return typeAliasDeclaration;
632
- }
633
- }
634
- return null;
635
- }
636
- getCommonTypeReExportModuleSpecifier(typeName, commonTypesFilePath) {
637
- const knownLibTypeFiles = [
638
- path.join(process.cwd(), 'packages/lib/src/midtrans-client.ts'),
639
- path.join(process.cwd(), 'packages/lib/src/common.ts')
640
- ];
641
- for (const filePath of knownLibTypeFiles) {
642
- const sourceFile = this.project.addSourceFileAtPathIfExists(filePath);
643
- const symbolNames = sourceFile?.getExportSymbols().map((symbol) => symbol.getName()) ?? [];
644
- if (symbolNames.includes(typeName)) {
645
- const enumDeclaration = sourceFile?.getEnum(typeName);
646
- return {
647
- moduleSpecifier: this.createRelativeJSImportPath(commonTypesFilePath, filePath),
648
- isTypeOnly: !enumDeclaration
649
- };
650
- }
651
- }
652
- return null;
653
- }
654
- normalizeDeclarationToExportText(typeName) {
655
- const declaration = this.findTypeDeclarationByName(typeName);
656
- if (!declaration) {
657
- return null;
658
- }
659
- const declarationText = declaration.getText().trim();
660
- if (declarationText.startsWith('export ')) {
661
- return declarationText;
662
- }
663
- return `export ${declarationText}`;
664
- }
665
- ensureCommonTypesFile(metas) {
666
- const commonTypesFilePath = this.getCommonTypesFilePath();
667
- const symbols = [...this.collectAllExternalTypeSymbols(metas)].sort((a, b) => a.localeCompare(b));
668
- const lines = [
669
- 'export type SearchDTO = {',
670
- ' title: string',
671
- ' subtitle?: string | null',
672
- ' path: string',
673
- '}'
674
- ];
675
- for (const symbol of symbols) {
676
- const reExportModule = this.getCommonTypeReExportModuleSpecifier(symbol, commonTypesFilePath);
677
- if (reExportModule) {
678
- const exportKeyword = reExportModule.isTypeOnly ? 'export type' : 'export';
679
- lines.push(`${exportKeyword} { ${symbol} } from '${reExportModule.moduleSpecifier}'`);
680
- continue;
681
- }
682
- const declarationText = this.normalizeDeclarationToExportText(symbol);
683
- if (!declarationText) {
684
- throw new Error(`DTOGeneratorService config error: could not generate common type '${symbol}'.`);
685
- }
686
- lines.push(declarationText.replace(/^export declare /, 'export '));
687
- }
688
- const content = `${lines.join('\n\n')}\n`;
689
- this.writeFile(commonTypesFilePath, content);
690
- this.project.addSourceFileAtPathIfExists(commonTypesFilePath);
691
- }
692
- resolveCommonTypesSourceFile() {
693
- const commonTypesFilePath = this.getCommonTypesFilePath();
694
- if (!fs.existsSync(commonTypesFilePath)) {
695
- return null;
696
- }
697
- return this.project.addSourceFileAtPathIfExists(commonTypesFilePath) ?? null;
698
- }
699
- validateCommonTypeCoverage(metas) {
700
- const commonTypesSource = this.resolveCommonTypesSourceFile();
701
- if (!commonTypesSource) {
702
- throw new Error(`DTOGeneratorService config error: common.dto.ts was not generated in '${this.config.dtoOutputDir}'.`);
703
- }
704
- const exportedSymbols = new Set(commonTypesSource.getExportSymbols().map((symbol) => symbol.getName()));
705
- const missing = [];
706
- for (const meta of metas) {
707
- const relationTypeNames = new Set(meta.relations.map((relation) => relation.targetDTOName));
708
- const symbols = this.collectExternalTypeSymbols(meta, relationTypeNames);
709
- for (const symbol of symbols) {
710
- if (!exportedSymbols.has(symbol)) {
711
- const columns = meta.columns.filter((column) => {
712
- const identifiers = column.type.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) ?? [];
713
- return identifiers.includes(symbol);
714
- }).map((column) => column.name);
715
- const uniqueColumns = this.unique(columns);
716
- missing.push(`${symbol} (used in ${meta.dtoName}.${uniqueColumns.join(', ')})`);
717
- }
718
- }
719
- }
720
- if (missing.length) {
721
- const details = this.unique(missing).sort((a, b) => a.localeCompare(b)).join('\n- ');
722
- throw new Error(`DTOGeneratorService validation error: generated/common.dto.ts is missing exports for:\n- ${details}`);
723
- }
724
- }
725
- createDTOIndexContent(metas) {
726
- const dtoTypeExports = metas
727
- .map((meta) => {
728
- return `export type { ${meta.dtoName} } from './${meta.moduleName}.dto.ts'`;
729
- })
730
- .join('\n');
731
- return `export * from './common.dto.ts'\n${dtoTypeExports}`;
732
- }
733
- createGeneratedMapperContent(metas) {
734
- const hasDateColumn = metas.some((meta) => {
735
- return meta.columns.some((column) => {
736
- return column.isDate;
737
- });
738
- });
739
- const helperImport = hasDateColumn ? `import { toDTODate } from './dto-date.util.ts'\n` : '';
740
- const entityImports = metas.map((meta) => {
741
- return `import { ${meta.className} } from '${this.createRelativeJSImportPath(this.config.mapperOutputFile, meta.filePath)}'`;
742
- });
743
- const dtoImports = `import type { ${metas.map((meta) => meta.dtoName).join(', ')} } from '${this.config.sharedDTOImportPath}'`;
744
- const mapperFunctions = metas.map((meta) => {
745
- return this.createMapperFunctionContent(meta, metas);
746
- });
747
- return `${helperImport}${entityImports.join('\n')}\n${dtoImports}\n\n${mapperFunctions.join('\n\n')}\n`;
748
- }
749
- createMapperFunctionContent(meta, metas) {
750
- const validRelations = this.getValidRelations(meta, metas);
751
- const columnLines = meta.columns.map((column) => {
752
- const value = column.isDate ? `toDTODate(entity.${column.name})` : `entity.${column.name}`;
753
- return ` ${column.name}: ${value}`;
754
- });
755
- const relationLines = validRelations.map((relation) => {
756
- return this.createRelationMapperLine(relation, metas);
757
- });
758
- const relationBlock = relationLines.length ? `\n${relationLines.join('\n\n')}\n` : '';
759
- return `export function ${meta.mapperName}(entity: ${meta.className}): ${meta.dtoName} {\n const dto: ${meta.dtoName} = {\n${columnLines.join(',\n')}\n }\n${relationBlock}\n return dto\n}\n\nexport function ${meta.mapperName}Array(entities: ${meta.className}[]): ${meta.dtoName}[] {\n return entities.map(${meta.mapperName})\n}`;
760
- }
761
- createRelationMapperLine(relation, metas) {
762
- const targetMeta = this.findMetaByClassName(metas, relation.targetClassName);
763
- const parserName = targetMeta?.mapperName;
764
- if (relation.isArray) {
765
- return ` if (Array.isArray(entity.${relation.name})) {\n dto.${relation.name} = entity.${relation.name}.map(${parserName})\n }`;
766
- }
767
- if (relation.nullable) {
768
- return ` if (entity.${relation.name} !== undefined) {\n dto.${relation.name} = entity.${relation.name} === null ? null : ${parserName}(entity.${relation.name})\n }`;
769
- }
770
- return ` if (entity.${relation.name} !== undefined && entity.${relation.name} !== null) {\n dto.${relation.name} = ${parserName}(entity.${relation.name})\n }`;
771
- }
688
+ return identifiers.includes(symbol);
689
+ }).map((column) => column.name);
690
+ const uniqueColumns = this.unique(columns);
691
+ missing.push(`${symbol} (used in ${meta.dtoName}.${uniqueColumns.join(", ")})`);
692
+ }
693
+ }
694
+ }
695
+ if (missing.length) {
696
+ const details = this.unique(missing).sort((a, b) => a.localeCompare(b)).join("\n- ");
697
+ throw new Error(
698
+ `DTOGeneratorService validation error: generated/common.dto.ts is missing exports for:
699
+ - ${details}`
700
+ );
701
+ }
702
+ }
703
+ createDTOIndexContent(metas) {
704
+ const dtoTypeExports = metas.map((meta) => {
705
+ return `export type { ${meta.dtoName} } from './${meta.moduleName}.dto${this.config.includeExtensionOnImports ? ".ts" : ""}'`;
706
+ }).join("\n");
707
+ return `export * from './common.dto${this.config.includeExtensionOnImports ? ".ts" : ""}'
708
+ ${dtoTypeExports}`;
709
+ }
710
+ getMetasWithFilePath(metas) {
711
+ return metas.filter((meta) => {
712
+ return Boolean(meta.filePath);
713
+ });
714
+ }
715
+ createEntityImports(metas) {
716
+ var _a;
717
+ const metasWithFilePath = this.getMetasWithFilePath(metas);
718
+ if (metasWithFilePath.length !== metas.length) {
719
+ return "";
720
+ }
721
+ const importMap = /* @__PURE__ */ new Map();
722
+ for (const meta of metasWithFilePath) {
723
+ if (!meta.filePath) {
724
+ continue;
725
+ }
726
+ const moduleSpecifier = this.createRelativeJSImportPath(this.config.mapperOutputFile, meta.filePath);
727
+ if (!importMap.has(moduleSpecifier)) {
728
+ importMap.set(moduleSpecifier, /* @__PURE__ */ new Set());
729
+ }
730
+ (_a = importMap.get(moduleSpecifier)) == null ? void 0 : _a.add(meta.className);
731
+ }
732
+ return [...importMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([moduleSpecifier, classNames]) => {
733
+ let newModuleSpecifier = moduleSpecifier;
734
+ if (!this.config.includeExtensionOnImports) {
735
+ newModuleSpecifier = newModuleSpecifier.replace(".ts", "");
736
+ }
737
+ return `import { ${[...classNames].sort((a, b) => a.localeCompare(b)).join(", ")} } from '${newModuleSpecifier}'`;
738
+ }).join("\n");
739
+ }
740
+ getMapperEntityTypeName(meta) {
741
+ return meta.filePath ? meta.className : `${meta.className}EntityLike`;
742
+ }
743
+ createStructuralEntityTypes(metas) {
744
+ if (this.getMetasWithFilePath(metas).length === metas.length) {
745
+ return "";
746
+ }
747
+ return metas.map((meta) => {
748
+ return `type ${meta.className}EntityLike = Record<string, any>`;
749
+ }).join("\n");
750
+ }
751
+ createGeneratedMapperContent(metas) {
752
+ const entityImports = this.createEntityImports(metas);
753
+ const dtoImports = `import type { ${metas.map((meta) => meta.dtoName).join(", ")} } from '${this.config.sharedDTOImportPath}'`;
754
+ const structuralTypes = this.createStructuralEntityTypes(metas);
755
+ const mapperFunctions = metas.map((meta) => {
756
+ return this.createMapperFunctionContent(meta, metas);
757
+ });
758
+ const blocks = [
759
+ entityImports,
760
+ dtoImports,
761
+ structuralTypes,
762
+ mapperFunctions.join("\n\n")
763
+ ].filter(Boolean);
764
+ let contents = `${blocks.join("\n")}
765
+
766
+ `;
767
+ const dtoDateContent = `export function toDTODate(value: Date | string): string
768
+ export function toDTODate(value: Date | string | null | undefined): string | null
769
+ export function toDTODate(value: Date | string | null | undefined) {
770
+ if (!value) {
771
+ return null
772
+ }
773
+
774
+ if (value instanceof Date) {
775
+ return value.toISOString()
776
+ }
777
+
778
+ return value
779
+ }
780
+ `;
781
+ contents += dtoDateContent;
782
+ return contents;
783
+ }
784
+ createMapperFunctionContent(meta, metas) {
785
+ const validRelations = this.getValidRelations(meta, metas);
786
+ const entityTypeName = this.getMapperEntityTypeName(meta);
787
+ const columnLines = meta.columns.map((column) => {
788
+ let value = `entity.${column.name}`;
789
+ if (column.isDate) {
790
+ value = `toDTODate(entity.${column.name})`;
791
+ } else if (column.mapAsNumber) {
792
+ value = column.type.includes("null") ? `entity.${column.name} === null || entity.${column.name} === undefined ? null : Number(entity.${column.name})` : `Number(entity.${column.name})`;
793
+ }
794
+ return ` ${column.name}: ${value}`;
795
+ });
796
+ const relationLines = validRelations.map((relation) => {
797
+ return this.createRelationMapperLine(relation, metas);
798
+ });
799
+ const relationBlock = relationLines.length ? `
800
+ ${relationLines.join("\n\n")}
801
+ ` : "";
802
+ return `export function ${meta.mapperName}(entity: ${entityTypeName}): ${meta.dtoName} {
803
+ const dto: ${meta.dtoName} = {
804
+ ${columnLines.join(",\n")}
805
+ }
806
+ ${relationBlock}
807
+ return dto
772
808
  }
809
+
810
+ export function ${meta.mapperName}Array(entities: ${entityTypeName}[]): ${meta.dtoName}[] {
811
+ return entities.map(${meta.mapperName})
812
+
813
+ }`;
814
+ }
815
+ createRelationMapperLine(relation, metas) {
816
+ const targetMeta = this.findMetaByClassName(metas, relation.targetClassName);
817
+ const parserName = targetMeta == null ? void 0 : targetMeta.mapperName;
818
+ if (relation.isArray) {
819
+ return ` if (Array.isArray(entity.${relation.name})) {
820
+ dto.${relation.name} = entity.${relation.name}.map(${parserName})
821
+ }`;
822
+ }
823
+ if (relation.nullable) {
824
+ return ` if (entity.${relation.name} !== undefined) {
825
+ dto.${relation.name} = entity.${relation.name} === null ? null : ${parserName}(entity.${relation.name})
826
+ }`;
827
+ }
828
+ return ` if (entity.${relation.name} !== undefined && entity.${relation.name} !== null) {
829
+ dto.${relation.name} = ${parserName}(entity.${relation.name})
830
+ }`;
831
+ }
832
+ };
833
+ // Annotate the CommonJS export names for ESM import in node:
834
+ 0 && (module.exports = {
835
+ TORMDTOGenerator
836
+ });
837
+ //# sourceMappingURL=index.js.map