@apso/cli 0.1.6 → 0.1.8
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 +1 -1
- package/dist/commands/server/scaffold.js +40 -4
- package/dist/lib/apsorc-parser.js +29 -5
- package/dist/lib/base-command.d.ts +1 -1
- package/dist/lib/base-command.js +2 -2
- package/dist/lib/controller.js +60 -16
- package/dist/lib/dto.js +14 -18
- package/dist/lib/entity.js +2 -5
- package/dist/lib/enums.js +2 -5
- package/dist/lib/gql-dto.js +2 -6
- package/dist/lib/index-module.js +2 -5
- package/dist/lib/module.js +1 -5
- package/dist/lib/service.js +5 -7
- package/dist/lib/templates/rest/controller-rest.eta +2 -4
- package/dist/lib/utils/file-system.d.ts +1 -1
- package/dist/lib/utils/file-system.js +5 -5
- package/dist/lib/utils/relationships/parse.d.ts +21 -1
- package/dist/lib/utils/relationships/parse.js +97 -39
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -6,8 +6,15 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
6
6
|
const lib_1 = require("../../lib");
|
|
7
7
|
const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
|
|
8
8
|
const apsorc_parser_1 = require("../../lib/apsorc-parser");
|
|
9
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
10
|
+
const Eta = tslib_1.__importStar(require("eta"));
|
|
11
|
+
Eta.configure({
|
|
12
|
+
views: path.join(__dirname, "../../lib/templates"),
|
|
13
|
+
cache: false
|
|
14
|
+
});
|
|
9
15
|
class Scaffold extends base_command_1.default {
|
|
10
16
|
async scaffoldServer(dir, entity, relationshipMap, apiType) {
|
|
17
|
+
const entityBuildStart = perf_hooks_1.performance.now();
|
|
11
18
|
this.log(`Building... ${entity.name}`);
|
|
12
19
|
const entityName = entity.name;
|
|
13
20
|
const filePath = path.join(dir, entityName);
|
|
@@ -24,13 +31,31 @@ class Scaffold extends base_command_1.default {
|
|
|
24
31
|
break;
|
|
25
32
|
}
|
|
26
33
|
await (0, lib_1.createModule)(filePath, entity, { apiType });
|
|
34
|
+
const entityBuildTime = perf_hooks_1.performance.now() - entityBuildStart;
|
|
35
|
+
console.log(`[apso] Finished building entity '${entity.name}' in ${entityBuildTime.toFixed(2)} ms`);
|
|
27
36
|
}
|
|
28
37
|
async setupRestFiles(dir, entity, relationshipMap, apiType) {
|
|
29
38
|
const filePath = path.join(dir, entity.name);
|
|
30
39
|
const entityRelationships = relationshipMap[entity.name] || [];
|
|
40
|
+
const tDto = perf_hooks_1.performance.now();
|
|
31
41
|
await (0, lib_1.createDto)(filePath, entity, entityRelationships, { apiType });
|
|
42
|
+
if (process.env.DEBUG) {
|
|
43
|
+
console.log(`[timing] createDto for ${entity.name}: ${(perf_hooks_1.performance.now() - tDto).toFixed(2)}ms`);
|
|
44
|
+
const used = process.memoryUsage();
|
|
45
|
+
console.log(`[mem] heapUsed after createDto for ${entity.name}: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
46
|
+
}
|
|
47
|
+
const tService = perf_hooks_1.performance.now();
|
|
32
48
|
await (0, lib_1.createService)(filePath, entity);
|
|
49
|
+
if (process.env.DEBUG) {
|
|
50
|
+
console.log(`[timing] createService for ${entity.name}: ${(perf_hooks_1.performance.now() - tService).toFixed(2)}ms`);
|
|
51
|
+
const used = process.memoryUsage();
|
|
52
|
+
console.log(`[mem] heapUsed after createService for ${entity.name}: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
53
|
+
}
|
|
54
|
+
const tController = perf_hooks_1.performance.now();
|
|
33
55
|
await (0, lib_1.createController)(filePath, entity, relationshipMap);
|
|
56
|
+
if (process.env.DEBUG) {
|
|
57
|
+
console.log(`[timing] createController for ${entity.name}: ${(perf_hooks_1.performance.now() - tController).toFixed(2)}ms`);
|
|
58
|
+
}
|
|
34
59
|
}
|
|
35
60
|
async setupGraphqlFiles(dir, entity, relationshipMap) {
|
|
36
61
|
const filePath = path.join(dir, entity.name);
|
|
@@ -38,17 +63,28 @@ class Scaffold extends base_command_1.default {
|
|
|
38
63
|
await (0, lib_1.createGqlDTO)(filePath, entity, entityRelationships);
|
|
39
64
|
}
|
|
40
65
|
async run() {
|
|
66
|
+
const totalBuildStart = perf_hooks_1.performance.now();
|
|
41
67
|
const { rootFolder, entities, relationshipMap, apiType } = (0, lib_1.parseApsorc)();
|
|
42
68
|
const rootPath = path.join(process.cwd(), rootFolder);
|
|
43
69
|
const autogenPath = path.join(rootPath, "autogen");
|
|
44
70
|
const lowerCaseApiType = apiType.toLowerCase();
|
|
45
71
|
await (0, lib_1.createEnums)(autogenPath, entities, lowerCaseApiType);
|
|
46
|
-
entities.
|
|
72
|
+
await Promise.all(entities.map(async (entity) => {
|
|
47
73
|
const scaffoldModel = this.scaffoldServer.bind(this);
|
|
48
|
-
scaffoldModel(autogenPath, entity, relationshipMap, lowerCaseApiType);
|
|
49
|
-
|
|
74
|
+
await scaffoldModel(autogenPath, entity, relationshipMap, lowerCaseApiType);
|
|
75
|
+
if (process.env.DEBUG) {
|
|
76
|
+
const used = process.memoryUsage();
|
|
77
|
+
console.log(`[mem] heapUsed after ${entity.name}: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
78
|
+
}
|
|
79
|
+
}));
|
|
50
80
|
await (0, lib_1.createIndexAppModule)(autogenPath, entities, lowerCaseApiType);
|
|
51
|
-
|
|
81
|
+
const totalBuildTime = perf_hooks_1.performance.now() - totalBuildStart;
|
|
82
|
+
console.log(`[apso] Finished building all entities in ${totalBuildTime.toFixed(2)} ms`);
|
|
83
|
+
const formatStart = perf_hooks_1.performance.now();
|
|
84
|
+
console.log("[apso] Formatting files...");
|
|
85
|
+
await this.runNpmCommand(["run", "format", "src/autogen/**/*.ts"], true);
|
|
86
|
+
const formatTime = perf_hooks_1.performance.now() - formatStart;
|
|
87
|
+
console.log(`[apso] Finished formatting in ${formatTime.toFixed(2)} ms`);
|
|
52
88
|
}
|
|
53
89
|
}
|
|
54
90
|
Scaffold.description = "Setup new entities and interfaces for an Apso Server";
|
|
@@ -6,6 +6,7 @@ const fs = tslib_1.__importStar(require("fs"));
|
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
7
|
const rc_1 = tslib_1.__importDefault(require("rc"));
|
|
8
8
|
const relationships_1 = require("./utils/relationships");
|
|
9
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
9
10
|
var ApiType;
|
|
10
11
|
(function (ApiType) {
|
|
11
12
|
ApiType["Graphql"] = "graphql";
|
|
@@ -42,6 +43,8 @@ const parseRc = () => {
|
|
|
42
43
|
};
|
|
43
44
|
};
|
|
44
45
|
const parseApsorc = () => {
|
|
46
|
+
const debug = process.env.DEBUG;
|
|
47
|
+
const start = perf_hooks_1.performance.now();
|
|
45
48
|
const apsoConfig = parseRc();
|
|
46
49
|
if (apsoConfig.version === 1 &&
|
|
47
50
|
(apsoConfig === null || apsoConfig === void 0 ? void 0 : apsoConfig.apiType.toLowerCase()) !== ApiType.Rest.toLowerCase()) {
|
|
@@ -49,18 +52,39 @@ const parseApsorc = () => {
|
|
|
49
52
|
}
|
|
50
53
|
else {
|
|
51
54
|
switch (apsoConfig.version) {
|
|
52
|
-
case 1:
|
|
53
|
-
|
|
55
|
+
case 1: {
|
|
56
|
+
const v1Start = perf_hooks_1.performance.now();
|
|
57
|
+
const result = {
|
|
54
58
|
rootFolder: apsoConfig.rootFolder,
|
|
55
59
|
apiType: apsoConfig.apiType,
|
|
56
60
|
...(0, exports.parseApsorcV1)(apsoConfig),
|
|
57
61
|
};
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
if (debug) {
|
|
63
|
+
console.log(`[timing] parseApsorcV1: ${(perf_hooks_1.performance.now() - v1Start).toFixed(2)}ms`);
|
|
64
|
+
}
|
|
65
|
+
if (debug) {
|
|
66
|
+
console.log(`[timing] parseApsorc total: ${(perf_hooks_1.performance.now() - start).toFixed(2)}ms`);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
case 2: {
|
|
71
|
+
const result = {
|
|
60
72
|
rootFolder: apsoConfig.rootFolder,
|
|
61
73
|
apiType: apsoConfig.apiType,
|
|
62
|
-
...(
|
|
74
|
+
...(() => {
|
|
75
|
+
const relStart = perf_hooks_1.performance.now();
|
|
76
|
+
const parsed = (0, exports.parseApsorcV2)(apsoConfig);
|
|
77
|
+
if (debug) {
|
|
78
|
+
console.log(`[timing] parseApsorcV2: ${(perf_hooks_1.performance.now() - relStart).toFixed(2)}ms`);
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
})(),
|
|
63
82
|
};
|
|
83
|
+
if (debug) {
|
|
84
|
+
console.log(`[timing] parseApsorc total: ${(perf_hooks_1.performance.now() - start).toFixed(2)}ms`);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
64
88
|
}
|
|
65
89
|
throw new Error(`Invalid apsorc config version: ${apsoConfig.version}`);
|
|
66
90
|
}
|
package/dist/lib/base-command.js
CHANGED
|
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
class BaseCommand extends core_1.Command {
|
|
6
|
-
async runNpmCommand(args) {
|
|
6
|
+
async runNpmCommand(args, silent = false) {
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
8
|
const command = "npm";
|
|
9
9
|
const cmdStr = `${command} ${args.join(" ")}`;
|
|
10
10
|
this.log(cmdStr);
|
|
11
|
-
const child = (0, child_process_1.spawn)(command, args, { stdio: "inherit" });
|
|
11
|
+
const child = (0, child_process_1.spawn)(command, args, { stdio: silent ? "ignore" : "inherit" });
|
|
12
12
|
child.on("close", (code) => {
|
|
13
13
|
if (code !== 0) {
|
|
14
14
|
reject({
|
package/dist/lib/controller.js
CHANGED
|
@@ -6,7 +6,13 @@ const Eta = tslib_1.__importStar(require("eta"));
|
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
7
|
const file_system_1 = require("./utils/file-system");
|
|
8
8
|
const relationships_1 = require("./utils/relationships");
|
|
9
|
+
const parse_1 = require("./utils/relationships/parse");
|
|
9
10
|
const pluralize_1 = tslib_1.__importDefault(require("pluralize"));
|
|
11
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
12
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
13
|
+
const os_1 = tslib_1.__importDefault(require("os"));
|
|
14
|
+
const util_1 = require("util");
|
|
15
|
+
const unlinkAsync = (0, util_1.promisify)(fs_1.default.unlink);
|
|
10
16
|
/**
|
|
11
17
|
* Generates NestJS controller and spec files for a given entity.
|
|
12
18
|
*
|
|
@@ -19,33 +25,71 @@ const createController = async (apiBaseDir, entity, relationshipMap) => {
|
|
|
19
25
|
const { name: entityName } = entity;
|
|
20
26
|
const controllerFileName = path.join(apiBaseDir, `${entityName}.controller.ts`);
|
|
21
27
|
const specFileName = path.join(apiBaseDir, `${entityName}.controller.spec.ts`);
|
|
22
|
-
|
|
28
|
+
const startTotal = perf_hooks_1.performance.now();
|
|
29
|
+
const startRel = perf_hooks_1.performance.now();
|
|
30
|
+
const relationships = (0, relationships_1.getRelationshipForTemplate)(entityName, relationshipMap[entityName]);
|
|
31
|
+
const relTime = perf_hooks_1.performance.now() - startRel;
|
|
32
|
+
if (process.env.DEBUG) {
|
|
33
|
+
console.log(`[createController][${entityName}] getRelationshipForTemplate: ${relTime.toFixed(2)} ms`);
|
|
34
|
+
}
|
|
35
|
+
const startNested = perf_hooks_1.performance.now();
|
|
36
|
+
const tmpDir = os_1.default.tmpdir();
|
|
37
|
+
const nestedJoinsFile = path.join(tmpDir, `apso-nested-joins-${entityName}-${Date.now()}.eta`);
|
|
38
|
+
const nestedJoinsStream = fs_1.default.createWriteStream(nestedJoinsFile, { encoding: "utf8" });
|
|
39
|
+
let nestedCount = 0;
|
|
40
|
+
nestedCount = await (0, parse_1.streamNestedRelationships)(entityName, relationshipMap, nestedJoinsStream);
|
|
41
|
+
nestedJoinsStream.end();
|
|
42
|
+
await new Promise((resolve) => {
|
|
43
|
+
nestedJoinsStream.on("finish", resolve);
|
|
44
|
+
});
|
|
45
|
+
const nestedTime = perf_hooks_1.performance.now() - startNested;
|
|
46
|
+
if (process.env.DEBUG) {
|
|
47
|
+
console.log(`[createController][${entityName}] getNestedRelationships (streamed ${nestedCount} paths): ${nestedTime.toFixed(2)} ms`);
|
|
48
|
+
}
|
|
23
49
|
const svcName = `${entityName}Service`;
|
|
24
50
|
const ctrlName = `${entityName}Controller`;
|
|
25
51
|
const pluralEntityName = (0, pluralize_1.default)(entityName);
|
|
26
|
-
// Get processed relationships specific to this entity
|
|
27
|
-
const relationships = (0, relationships_1.getRelationshipForTemplate)(entityName, relationshipMap[entityName]);
|
|
28
|
-
// Get nested relationship paths for the @Crud join configuration
|
|
29
|
-
const nestedRelationships = (0, relationships_1.getNestedRelationships)(entityName, relationshipMap);
|
|
30
|
-
// Data object for the Eta template
|
|
31
52
|
const data = {
|
|
32
53
|
svcName,
|
|
33
54
|
ctrlName,
|
|
34
55
|
entityName,
|
|
35
56
|
pluralEntityName,
|
|
36
|
-
// Ensure uniqueness in associations (though getRelationshipForTemplate might handle this)
|
|
37
57
|
associations: [...new Set(relationships)],
|
|
38
|
-
|
|
58
|
+
nestedJoinsFile,
|
|
39
59
|
};
|
|
40
|
-
|
|
41
|
-
Eta.configure({
|
|
42
|
-
views: path.join(__dirname, "templates"),
|
|
43
|
-
});
|
|
44
|
-
// Render controller template
|
|
60
|
+
const startRenderController = perf_hooks_1.performance.now();
|
|
45
61
|
const controllerContent = await Eta.renderFileAsync("./rest/controller-rest", data);
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
const renderControllerTime = perf_hooks_1.performance.now() - startRenderController;
|
|
63
|
+
if (process.env.DEBUG) {
|
|
64
|
+
console.log(`[createController][${entityName}] renderController: ${renderControllerTime.toFixed(2)} ms`);
|
|
65
|
+
}
|
|
66
|
+
const startWriteController = perf_hooks_1.performance.now();
|
|
67
|
+
await (0, file_system_1.createFile)(controllerFileName, controllerContent);
|
|
68
|
+
const writeControllerTime = perf_hooks_1.performance.now() - startWriteController;
|
|
69
|
+
if (process.env.DEBUG) {
|
|
70
|
+
console.log(`[createController][${entityName}] writeController: ${writeControllerTime.toFixed(2)} ms`);
|
|
71
|
+
}
|
|
72
|
+
const startRenderSpec = perf_hooks_1.performance.now();
|
|
48
73
|
const specContent = await Eta.renderFileAsync("./rest/controller-rest-spec", data);
|
|
49
|
-
|
|
74
|
+
const renderSpecTime = perf_hooks_1.performance.now() - startRenderSpec;
|
|
75
|
+
if (process.env.DEBUG) {
|
|
76
|
+
console.log(`[createController][${entityName}] renderSpec: ${renderSpecTime.toFixed(2)} ms`);
|
|
77
|
+
}
|
|
78
|
+
const startWriteSpec = perf_hooks_1.performance.now();
|
|
79
|
+
await (0, file_system_1.createFile)(specFileName, specContent);
|
|
80
|
+
const writeSpecTime = perf_hooks_1.performance.now() - startWriteSpec;
|
|
81
|
+
if (process.env.DEBUG) {
|
|
82
|
+
console.log(`[createController][${entityName}] writeSpec: ${writeSpecTime.toFixed(2)} ms`);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await unlinkAsync(nestedJoinsFile);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.warn(`[createController][${entityName}] Warning: Failed to delete temp file ${nestedJoinsFile}:`, error);
|
|
89
|
+
}
|
|
90
|
+
const totalTime = perf_hooks_1.performance.now() - startTotal;
|
|
91
|
+
if (process.env.DEBUG) {
|
|
92
|
+
console.log(`[createController][${entityName}] TOTAL: ${totalTime.toFixed(2)} ms`);
|
|
93
|
+
}
|
|
50
94
|
};
|
|
51
95
|
exports.createController = createController;
|
package/dist/lib/dto.js
CHANGED
|
@@ -26,23 +26,23 @@ options) => {
|
|
|
26
26
|
const dtoFile = path.join(dtoDir, `${entityName}.dto.ts`);
|
|
27
27
|
const columns = (0, field_1.getFieldForTemplate)(fields, entityName);
|
|
28
28
|
const relationshipsTemplate = (0, relationships_1.getRelationshipForTemplate)(entityName, relationships);
|
|
29
|
-
if (process.env.DEBUG) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
29
|
+
// if (process.env.DEBUG) {
|
|
30
|
+
// console.log('[DTO DEBUG] Generating DTO for:', entityName);
|
|
31
|
+
// console.log('[DTO DEBUG] relationshipsTemplate:', relationshipsTemplate);
|
|
32
|
+
// }
|
|
33
33
|
// Add foreign key columns for ManyToOne relationships if not present
|
|
34
34
|
relationshipsTemplate.forEach(rel => {
|
|
35
35
|
if (rel.type === 'ManyToOne') {
|
|
36
36
|
const fkName = (0, casing_1.camelCase)(rel.referenceName || rel.name) + 'Id';
|
|
37
|
-
if (process.env.DEBUG) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
37
|
+
// if (process.env.DEBUG) {
|
|
38
|
+
// console.log('[DTO DEBUG]', {
|
|
39
|
+
// relType: rel.type,
|
|
40
|
+
// relName: rel.name,
|
|
41
|
+
// referenceName: rel.referenceName,
|
|
42
|
+
// fkName,
|
|
43
|
+
// columns: columns.map(c => c.name)
|
|
44
|
+
// });
|
|
45
|
+
// }
|
|
46
46
|
if (!columns.some(col => col.name === fkName)) {
|
|
47
47
|
columns.push({
|
|
48
48
|
name: fkName,
|
|
@@ -73,13 +73,9 @@ options) => {
|
|
|
73
73
|
apiType: (_a = options === null || options === void 0 ? void 0 : options.apiType) !== null && _a !== void 0 ? _a : "rest",
|
|
74
74
|
importEnums: (0, field_1.typeExistsInEntity)(entity, "enum") !== -1,
|
|
75
75
|
};
|
|
76
|
-
// Configure Eta
|
|
77
|
-
Eta.configure({
|
|
78
|
-
views: path.join(__dirname, "templates"),
|
|
79
|
-
});
|
|
80
76
|
// Render the unified template only once
|
|
81
77
|
const dtoContent = await Eta.renderFileAsync("./rest/dto-rest", data);
|
|
82
78
|
// Create only one DTO file
|
|
83
|
-
(0, file_system_1.createFile)(dtoFile, dtoContent);
|
|
79
|
+
await (0, file_system_1.createFile)(dtoFile, dtoContent);
|
|
84
80
|
};
|
|
85
81
|
exports.createDto = createDto;
|
package/dist/lib/entity.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createEntity = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const Eta = tslib_1.__importStar(require("eta"));
|
|
6
5
|
const path = tslib_1.__importStar(require("path"));
|
|
7
6
|
const file_system_1 = require("./utils/file-system");
|
|
8
7
|
const field_1 = require("./utils/field");
|
|
9
8
|
const relationships_1 = require("./utils/relationships");
|
|
10
9
|
const casing_1 = require("./utils/casing");
|
|
11
10
|
const pluralize_1 = tslib_1.__importDefault(require("pluralize"));
|
|
11
|
+
const Eta = tslib_1.__importStar(require("eta"));
|
|
12
12
|
/**
|
|
13
13
|
* Generates a TypeORM entity file based on the entity definition and relationships.
|
|
14
14
|
*
|
|
@@ -43,11 +43,8 @@ const createEntity = async (apiBaseDir, entity, relationshipsNew, apiType) => {
|
|
|
43
43
|
importEnums: (0, field_1.typeExistsInEntity)(entity, "enum") !== -1,
|
|
44
44
|
apiType,
|
|
45
45
|
};
|
|
46
|
-
Eta.configure({
|
|
47
|
-
views: path.join(__dirname, "templates"),
|
|
48
|
-
});
|
|
49
46
|
const templatePath = apiType === "graphql" ? "./graphql/gql-entity-graphql" : "./entities/entity";
|
|
50
47
|
const content = await Eta.renderFileAsync(templatePath, data);
|
|
51
|
-
(0, file_system_1.createFile)(File, content);
|
|
48
|
+
await (0, file_system_1.createFile)(File, content);
|
|
52
49
|
};
|
|
53
50
|
exports.createEntity = createEntity;
|
package/dist/lib/enums.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createEnums = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const Eta = tslib_1.__importStar(require("eta"));
|
|
6
5
|
const path = tslib_1.__importStar(require("path"));
|
|
7
6
|
const file_system_1 = require("./utils/file-system");
|
|
8
7
|
const field_1 = require("./utils/field");
|
|
8
|
+
const Eta = tslib_1.__importStar(require("eta"));
|
|
9
9
|
const createEnums = async (apiBaseDir, entities, apiType) => {
|
|
10
10
|
if (entities.length > 0) {
|
|
11
11
|
const outputFile = path.join(apiBaseDir, "enums.ts");
|
|
@@ -18,13 +18,10 @@ const createEnums = async (apiBaseDir, entities, apiType) => {
|
|
|
18
18
|
}))
|
|
19
19
|
.filter(Boolean);
|
|
20
20
|
if (allEnumsFields.length > 0) {
|
|
21
|
-
Eta.configure({
|
|
22
|
-
views: path.join(__dirname, "templates"),
|
|
23
|
-
});
|
|
24
21
|
const content = await Eta.renderFileAsync(`./${apiType}/enums-${apiType}`, {
|
|
25
22
|
allEnumsFields,
|
|
26
23
|
});
|
|
27
|
-
(0, file_system_1.createFile)(outputFile, content);
|
|
24
|
+
await (0, file_system_1.createFile)(outputFile, content);
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
27
|
};
|
package/dist/lib/gql-dto.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createGqlDTO = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const Eta = tslib_1.__importStar(require("eta"));
|
|
6
5
|
const path = tslib_1.__importStar(require("path"));
|
|
7
6
|
const pluralize_1 = tslib_1.__importDefault(require("pluralize"));
|
|
7
|
+
const Eta = tslib_1.__importStar(require("eta"));
|
|
8
8
|
const field_1 = require("./utils/field");
|
|
9
9
|
const relationships_1 = require("./utils/relationships");
|
|
10
10
|
const casing_1 = require("./utils/casing");
|
|
@@ -33,11 +33,7 @@ const createGqlDTO = async (apiBaseDir, entity, relationshipsNew) => {
|
|
|
33
33
|
entitiesToImport,
|
|
34
34
|
importEnums: (0, field_1.typeExistsInEntity)(entity, "enum") !== -1,
|
|
35
35
|
};
|
|
36
|
-
Eta.configure({
|
|
37
|
-
// This tells Eta to look for templates in the /templates directory
|
|
38
|
-
views: path.join(__dirname, "templates"),
|
|
39
|
-
});
|
|
40
36
|
const contentEntity = await Eta.renderFileAsync("./graphql/gql-dto-graphql", data);
|
|
41
|
-
(0, file_system_1.createFile)(File, contentEntity);
|
|
37
|
+
await (0, file_system_1.createFile)(File, contentEntity);
|
|
42
38
|
};
|
|
43
39
|
exports.createGqlDTO = createGqlDTO;
|
package/dist/lib/index-module.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createIndexAppModule = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const file_system_1 = require("./utils/file-system");
|
|
6
5
|
const Eta = tslib_1.__importStar(require("eta"));
|
|
6
|
+
const file_system_1 = require("./utils/file-system");
|
|
7
7
|
const path = tslib_1.__importStar(require("path"));
|
|
8
8
|
/**
|
|
9
9
|
* Generates an index file (e.g., index.ts) that exports all generated entity modules.
|
|
@@ -18,12 +18,9 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
18
18
|
*/
|
|
19
19
|
const createIndexAppModule = async (apiBaseDir, entities, apiType) => {
|
|
20
20
|
const File = path.join(apiBaseDir, `index.ts`);
|
|
21
|
-
Eta.configure({
|
|
22
|
-
views: path.join(__dirname, "templates"),
|
|
23
|
-
});
|
|
24
21
|
const content = await Eta.renderFileAsync(`./${apiType}/index-module-${apiType}`, {
|
|
25
22
|
entities,
|
|
26
23
|
});
|
|
27
|
-
(0, file_system_1.createFile)(File, content);
|
|
24
|
+
await (0, file_system_1.createFile)(File, content);
|
|
28
25
|
};
|
|
29
26
|
exports.createIndexAppModule = createIndexAppModule;
|
package/dist/lib/module.js
CHANGED
|
@@ -30,15 +30,11 @@ const createModule = async (apiBaseDir, entity, options) => {
|
|
|
30
30
|
resolverName,
|
|
31
31
|
entityName,
|
|
32
32
|
};
|
|
33
|
-
// Configure Eta
|
|
34
|
-
Eta.configure({
|
|
35
|
-
views: path.join(__dirname, "templates"),
|
|
36
|
-
});
|
|
37
33
|
// Determine template based on API type
|
|
38
34
|
const templatePath = (options === null || options === void 0 ? void 0 : options.apiType) === "graphql" ? "./graphql/gql-module-graphql" : "./rest/module-rest";
|
|
39
35
|
// Render the template
|
|
40
36
|
const content = await Eta.renderFileAsync(templatePath, data);
|
|
41
37
|
// Create the module file
|
|
42
|
-
(0, file_system_1.createFile)(moduleFileName, content);
|
|
38
|
+
await (0, file_system_1.createFile)(moduleFileName, content);
|
|
43
39
|
};
|
|
44
40
|
exports.createModule = createModule;
|
package/dist/lib/service.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createService = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const file_system_1 = require("./utils/file-system");
|
|
6
5
|
const Eta = tslib_1.__importStar(require("eta"));
|
|
6
|
+
const file_system_1 = require("./utils/file-system");
|
|
7
7
|
const path = tslib_1.__importStar(require("path"));
|
|
8
8
|
/**
|
|
9
9
|
* Generates NestJS service and spec files for a given entity.
|
|
@@ -25,15 +25,13 @@ const createService = async (apiBaseDir, entity) => {
|
|
|
25
25
|
repoName,
|
|
26
26
|
entityName,
|
|
27
27
|
};
|
|
28
|
-
// Configure Eta
|
|
29
|
-
Eta.configure({
|
|
30
|
-
views: path.join(__dirname, "templates"),
|
|
31
|
-
});
|
|
32
28
|
// Render service template
|
|
33
29
|
const serviceContent = await Eta.renderFileAsync("./rest/service-rest", data);
|
|
34
|
-
(0, file_system_1.createFile)(serviceFileName, serviceContent);
|
|
30
|
+
await (0, file_system_1.createFile)(serviceFileName, serviceContent);
|
|
31
|
+
Eta.templates.remove("./rest/service-rest");
|
|
35
32
|
// Render spec template
|
|
36
33
|
const specContent = await Eta.renderFileAsync("./rest/service-rest-spec", data);
|
|
37
|
-
(0, file_system_1.createFile)(specFileName, specContent);
|
|
34
|
+
await (0, file_system_1.createFile)(specFileName, specContent);
|
|
35
|
+
Eta.templates.remove("./rest/service-rest-spec");
|
|
38
36
|
};
|
|
39
37
|
exports.createService = createService;
|
|
@@ -36,10 +36,8 @@ import {<%= it.svcName %>} from './<%= it.entityName %>.service';
|
|
|
36
36
|
<%= assoc.relationshipName %>: { eager: false },
|
|
37
37
|
<% } %>
|
|
38
38
|
<% }) %>
|
|
39
|
-
<% if (it.
|
|
40
|
-
|
|
41
|
-
"<%= assoc %>": { eager: false },
|
|
42
|
-
<% }) %>
|
|
39
|
+
<% if (it.nestedJoinsFile) {%>
|
|
40
|
+
<%~ includeFile(it.nestedJoinsFile) %>
|
|
43
41
|
<% }%>
|
|
44
42
|
},
|
|
45
43
|
<% } %>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const createFile: (file: string, content: string) => void
|
|
1
|
+
export declare const createFile: (file: string, content: string) => Promise<void>;
|
|
2
2
|
export declare const createDirectoryContents: (templatePath: string, newProjectPath: string, apiType: string) => void;
|
|
@@ -4,15 +4,15 @@ exports.createDirectoryContents = exports.createFile = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const path = tslib_1.__importStar(require("path"));
|
|
6
6
|
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
-
const createDir = (dirname) => {
|
|
7
|
+
const createDir = async (dirname) => {
|
|
8
8
|
if (!fs.existsSync(dirname)) {
|
|
9
|
-
fs.
|
|
9
|
+
await fs.promises.mkdir(dirname, { recursive: true });
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
-
const createFile = (file, content) => {
|
|
12
|
+
const createFile = async (file, content) => {
|
|
13
13
|
const dirname = path.dirname(file);
|
|
14
|
-
createDir(dirname);
|
|
15
|
-
fs.
|
|
14
|
+
await createDir(dirname);
|
|
15
|
+
await fs.promises.writeFile(file, content);
|
|
16
16
|
};
|
|
17
17
|
exports.createFile = createFile;
|
|
18
18
|
const createDirectoryContents = (templatePath, newProjectPath, apiType) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import { ApsorcRelationship, Relationship, RelationshipMap, RelationshipForTemplate } from "../../types";
|
|
2
3
|
/**
|
|
3
4
|
* Parses a OneToMany relationship definition.
|
|
@@ -51,5 +52,24 @@ export declare const getRelationshipIdField: (relationship: Relationship) => str
|
|
|
51
52
|
* @returns An array of RelationshipForTemplate objects with extra properties for the template.
|
|
52
53
|
*/
|
|
53
54
|
export declare const getRelationshipForTemplate: (entityName: string, relationships: Relationship[]) => RelationshipForTemplate[];
|
|
54
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Generates all valid nested join paths for an entity, avoiding cycles and redundant paths.
|
|
57
|
+
*
|
|
58
|
+
* Requirements enforced:
|
|
59
|
+
* 1. No cycles by tracking visited (entity, reference) pairs in the current path
|
|
60
|
+
* 2. Allow the same entity with different references (proper name vs alias)
|
|
61
|
+
* 3. Allow different aliases to the same entity in different branches
|
|
62
|
+
* 4. Respect max depth limit
|
|
63
|
+
*
|
|
64
|
+
* @param entityName - Current entity to generate relationships from
|
|
65
|
+
* @param relationshipMap - Map of all entity relationships
|
|
66
|
+
* @param maxDepth - Maximum depth of nested joins (default: 6)
|
|
67
|
+
* @param path - Current join path being built (internal use)
|
|
68
|
+
* @param visitedPairs - Set of (entity, reference) pairs visited in current path (internal use)
|
|
69
|
+
* @param baseEntity - The root entity we started from (internal use)
|
|
70
|
+
* @returns Generator yielding valid join path strings
|
|
71
|
+
*/
|
|
72
|
+
export declare function generateNestedRelationships(entityName: string, relationshipMap: RelationshipMap, maxDepth?: number, path?: string[], visitedPairs?: Set<string>, baseEntity?: string): Generator<string>;
|
|
73
|
+
export declare function getNestedRelationships(entityName: string, relationshipMap: RelationshipMap, maxDepth?: number): string[];
|
|
74
|
+
export declare function streamNestedRelationships(entityName: string, relationshipMap: RelationshipMap, writable: NodeJS.WritableStream, maxDepth?: number): Promise<number>;
|
|
55
75
|
export declare const getRelationshipsForImport: (entityName: string, relationships?: Relationship[]) => string[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getRelationshipsForImport = exports.getNestedRelationships = exports.getRelationshipForTemplate = exports.getRelationshipIdField = exports.parseRelationships = exports.parseRelationship = exports.parseManyToMany = exports.parseManytoOne = exports.parseOneToMany = void 0;
|
|
3
|
+
exports.getRelationshipsForImport = exports.streamNestedRelationships = exports.getNestedRelationships = exports.generateNestedRelationships = exports.getRelationshipForTemplate = exports.getRelationshipIdField = exports.parseRelationships = exports.parseRelationship = exports.parseManyToMany = exports.parseManytoOne = exports.parseOneToMany = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const pluralize_1 = tslib_1.__importDefault(require("pluralize"));
|
|
6
6
|
const casing_1 = require("../../utils/casing");
|
|
@@ -355,9 +355,15 @@ const getRelationshipForTemplate = (entityName, relationships) => {
|
|
|
355
355
|
const relationshipName = (0, casing_1.camelCase)(thisReferenceName || relationship.name);
|
|
356
356
|
const needsPluralInverse = relationship.type === 'ManyToOne' || relationship.type === 'ManyToMany';
|
|
357
357
|
// DEBUG LOGGING
|
|
358
|
-
if (process.env.DEBUG) {
|
|
359
|
-
|
|
360
|
-
|
|
358
|
+
// if (process.env.DEBUG) {
|
|
359
|
+
// console.log(
|
|
360
|
+
// '[DEBUG] relationship.name:', relationship.name,
|
|
361
|
+
// '| relationship.referenceName:', relationship.referenceName,
|
|
362
|
+
// '| thisReferenceName:', thisReferenceName,
|
|
363
|
+
// '| pluralizedRelationshipName:', camelCase(pluralize(thisReferenceName || relationship.name)),
|
|
364
|
+
// '| pluralizedName:', camelCase(pluralize(relationship.name))
|
|
365
|
+
// );
|
|
366
|
+
// }
|
|
361
367
|
const inverseSidePropertyName = calculateInversePropertyName(relationship, entityName, relationshipName, needsPluralInverse);
|
|
362
368
|
if (relationship.biDirectional && !relationship.inverseReferenceName &&
|
|
363
369
|
(relationship.type === 'ManyToMany' || relationship.type === 'OneToMany' || relationship.type === 'OneToOne')) {
|
|
@@ -379,47 +385,99 @@ const getRelationshipForTemplate = (entityName, relationships) => {
|
|
|
379
385
|
});
|
|
380
386
|
};
|
|
381
387
|
exports.getRelationshipForTemplate = getRelationshipForTemplate;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Generates all valid nested join paths for an entity, avoiding cycles and redundant paths.
|
|
390
|
+
*
|
|
391
|
+
* Requirements enforced:
|
|
392
|
+
* 1. No cycles by tracking visited (entity, reference) pairs in the current path
|
|
393
|
+
* 2. Allow the same entity with different references (proper name vs alias)
|
|
394
|
+
* 3. Allow different aliases to the same entity in different branches
|
|
395
|
+
* 4. Respect max depth limit
|
|
396
|
+
*
|
|
397
|
+
* @param entityName - Current entity to generate relationships from
|
|
398
|
+
* @param relationshipMap - Map of all entity relationships
|
|
399
|
+
* @param maxDepth - Maximum depth of nested joins (default: 6)
|
|
400
|
+
* @param path - Current join path being built (internal use)
|
|
401
|
+
* @param visitedPairs - Set of (entity, reference) pairs visited in current path (internal use)
|
|
402
|
+
* @param baseEntity - The root entity we started from (internal use)
|
|
403
|
+
* @returns Generator yielding valid join path strings
|
|
404
|
+
*/
|
|
405
|
+
function* generateNestedRelationships(entityName, relationshipMap, maxDepth = 4, path = [], visitedPairs = new Set(), baseEntity = entityName) {
|
|
406
|
+
// Stop if we've reached maximum depth
|
|
407
|
+
if (path.length >= maxDepth)
|
|
408
|
+
return;
|
|
409
|
+
// Get relationships for the current entity
|
|
410
|
+
const relationships = relationshipMap[entityName];
|
|
411
|
+
if (!relationships)
|
|
412
|
+
return;
|
|
413
|
+
for (const relationship of relationships) {
|
|
393
414
|
const referenceName = getRelationshipName(relationship);
|
|
415
|
+
const type = relationship.type;
|
|
416
|
+
// Format the property name based on relationship type
|
|
417
|
+
// OneToMany and ManyToMany relationships use plural forms
|
|
394
418
|
const formattedName = type === "ManyToOne" || type === "OneToOne"
|
|
395
|
-
?
|
|
396
|
-
: (0,
|
|
397
|
-
// Create
|
|
398
|
-
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
419
|
+
? referenceName
|
|
420
|
+
: (0, pluralize_1.default)(referenceName);
|
|
421
|
+
// Create a unique key for this (entity, reference) pair
|
|
422
|
+
const entityReferencePair = `${relationship.name}:${referenceName}`;
|
|
423
|
+
// Check if this exact (entity, reference) pair would create a cycle
|
|
424
|
+
const wouldCreateCycle = visitedPairs.has(entityReferencePair);
|
|
425
|
+
// Special case: prevent immediate self-reference loops (same property name)
|
|
426
|
+
// But allow different properties on the same entity type
|
|
427
|
+
const isImmediateSelfLoop = relationship.name === entityName &&
|
|
428
|
+
path.length > 0 &&
|
|
429
|
+
path[path.length - 1] === formattedName;
|
|
430
|
+
// Skip immediate self-loops (like networkStack.networkStack)
|
|
431
|
+
if (isImmediateSelfLoop)
|
|
432
|
+
continue;
|
|
433
|
+
// Skip cycles of the same (entity, reference) pair
|
|
434
|
+
if (wouldCreateCycle)
|
|
435
|
+
continue;
|
|
436
|
+
// Handle base entity returns: allow functional aliases but block generic relationships
|
|
437
|
+
if (relationship.name === baseEntity) {
|
|
438
|
+
// Check if this is a specific functional alias (different from default entity name)
|
|
439
|
+
const defaultSingularName = (0, casing_1.camelCase)(relationship.name);
|
|
440
|
+
const defaultPluralName = (0, casing_1.camelCase)((0, pluralize_1.default)(relationship.name));
|
|
441
|
+
const originalEntityName = relationship.name;
|
|
442
|
+
const isSpecificFunctionalAlias = relationship.referenceName &&
|
|
443
|
+
relationship.referenceName !== defaultSingularName &&
|
|
444
|
+
relationship.referenceName !== defaultPluralName &&
|
|
445
|
+
relationship.referenceName !== originalEntityName &&
|
|
446
|
+
relationship.referenceName !== relationship.name.toLowerCase();
|
|
447
|
+
// Block generic returns to base entity, allow functional aliases
|
|
448
|
+
if (!isSpecificFunctionalAlias) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
406
451
|
}
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const deepEnough = prefix.split(".").length >= 4;
|
|
413
|
-
const canAdd = hasChild && !exists && !deepEnough;
|
|
414
|
-
if (canAdd) {
|
|
415
|
-
relationships.push(relationshipPath);
|
|
452
|
+
// Build the new path
|
|
453
|
+
const newPath = [...path, formattedName];
|
|
454
|
+
// Always yield the join path if it's longer than 1 level
|
|
455
|
+
if (newPath.length > 1) {
|
|
456
|
+
yield newPath.join(".");
|
|
416
457
|
}
|
|
417
|
-
|
|
418
|
-
|
|
458
|
+
// Create new visited pairs set with the current relationship pair added
|
|
459
|
+
const newVisitedPairs = new Set(visitedPairs);
|
|
460
|
+
newVisitedPairs.add(entityReferencePair); // Add the relationship we're about to follow
|
|
461
|
+
// Recursively generate deeper nested relationships
|
|
462
|
+
yield* generateNestedRelationships(relationship.name, relationshipMap, maxDepth, newPath, newVisitedPairs, baseEntity);
|
|
419
463
|
}
|
|
420
|
-
|
|
421
|
-
|
|
464
|
+
}
|
|
465
|
+
exports.generateNestedRelationships = generateNestedRelationships;
|
|
466
|
+
// In-memory collector
|
|
467
|
+
function getNestedRelationships(entityName, relationshipMap, maxDepth) {
|
|
468
|
+
return [...generateNestedRelationships(entityName, relationshipMap, maxDepth)];
|
|
469
|
+
}
|
|
422
470
|
exports.getNestedRelationships = getNestedRelationships;
|
|
471
|
+
// Streaming function
|
|
472
|
+
async function streamNestedRelationships(entityName, relationshipMap, writable, maxDepth) {
|
|
473
|
+
let count = 0;
|
|
474
|
+
for (const joinPath of generateNestedRelationships(entityName, relationshipMap, maxDepth)) {
|
|
475
|
+
writable.write(`"${joinPath}": { eager: false },\n`);
|
|
476
|
+
count++;
|
|
477
|
+
}
|
|
478
|
+
return count;
|
|
479
|
+
}
|
|
480
|
+
exports.streamNestedRelationships = streamNestedRelationships;
|
|
423
481
|
const getRelationshipsForImport = (entityName, relationships) => {
|
|
424
482
|
if (relationships === undefined) {
|
|
425
483
|
return [];
|
package/oclif.manifest.json
CHANGED