langaro-api 1.0.6 → 1.0.7
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/lib/cli/init.js +17 -1
- package/lib/generators/controllers.js +79 -18
- package/lib/generators/crud.js +166 -65
- package/lib/generators/services.js +69 -10
- package/lib/index.js +15 -3
- package/lib/sourcemap.js +67 -0
- package/lib/utils.js +22 -4
- package/package.json +3 -3
package/lib/cli/init.js
CHANGED
|
@@ -395,7 +395,23 @@ class App {
|
|
|
395
395
|
|
|
396
396
|
if (process.env.NODE_ENV !== 'test') {
|
|
397
397
|
this.express.use(morgan('dev', {
|
|
398
|
-
skip: (req) =>
|
|
398
|
+
skip: (req) => {
|
|
399
|
+
// Ignore OPTIONS requests
|
|
400
|
+
if (req.method === 'OPTIONS') {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const skipUrls = [
|
|
405
|
+
process.env.BULLBOARD_PATH,
|
|
406
|
+
\`\${process.env.BULLBOARD_PATH}/static\`,
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
if (skipUrls.indexOf(req.baseUrl) !== -1) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return false;
|
|
414
|
+
},
|
|
399
415
|
}));
|
|
400
416
|
}
|
|
401
417
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { pascalCase,
|
|
3
|
+
const { pascalCase, extractMethodsWithLocations } = require('../utils');
|
|
4
|
+
const { generateSourceMap } = require('../sourcemap');
|
|
4
5
|
|
|
5
6
|
function scanControllerEntries(controllersDir) {
|
|
6
7
|
const entries = [];
|
|
@@ -22,42 +23,102 @@ function scanControllerEntries(controllersDir) {
|
|
|
22
23
|
return entries.sort((a, b) => a.tableName.localeCompare(b.tableName));
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function
|
|
26
|
+
function resolveServiceFile(tableName, servicesDir, modelsDir) {
|
|
27
|
+
if (servicesDir) {
|
|
28
|
+
const serviceFile = path.join(servicesDir, `${tableName}.services.js`);
|
|
29
|
+
if (fs.existsSync(serviceFile)) return serviceFile;
|
|
30
|
+
}
|
|
31
|
+
if (modelsDir) {
|
|
32
|
+
const modelFile = path.join(modelsDir, `${tableName}.model.js`);
|
|
33
|
+
if (fs.existsSync(modelFile)) return modelFile;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function generateControllersDts(controllersDir, outputDir, servicesDir, modelsDir) {
|
|
26
39
|
const lines = ['// Auto-generated by langaro-api — DO NOT EDIT', ''];
|
|
27
40
|
const controllerEntries = scanControllerEntries(controllersDir);
|
|
41
|
+
const mappings = [];
|
|
42
|
+
const sources = [];
|
|
43
|
+
const sourceIndexMap = {};
|
|
44
|
+
|
|
45
|
+
function getSourceIndex(filePath) {
|
|
46
|
+
const rel = path.relative(outputDir, filePath).replace(/\\/g, '/');
|
|
47
|
+
if (!(rel in sourceIndexMap)) {
|
|
48
|
+
sourceIndexMap[rel] = sources.length;
|
|
49
|
+
sources.push(rel);
|
|
50
|
+
}
|
|
51
|
+
return sourceIndexMap[rel];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function addMapping(col, sourceIdx, srcLine, srcCol) {
|
|
55
|
+
mappings.push({
|
|
56
|
+
genLine: lines.length,
|
|
57
|
+
genCol: col,
|
|
58
|
+
sourceIndex: sourceIdx,
|
|
59
|
+
sourceLine: srcLine,
|
|
60
|
+
sourceCol: srcCol,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
28
63
|
|
|
29
64
|
// Base classes for JSDoc @param on "ServicesClass"
|
|
30
|
-
controllerEntries.forEach(({ tableName }) => {
|
|
65
|
+
controllerEntries.forEach(({ tableName, filePath }) => {
|
|
31
66
|
const pc = pascalCase(tableName);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
67
|
+
const controllerIdx = getSourceIndex(filePath);
|
|
68
|
+
const serviceFile = resolveServiceFile(tableName, servicesDir, modelsDir);
|
|
69
|
+
const serviceIdx = serviceFile ? getSourceIndex(serviceFile) : controllerIdx;
|
|
70
|
+
|
|
71
|
+
addMapping(0, controllerIdx, 0, 0);
|
|
72
|
+
lines.push(`declare class ${pc}ControllerBase {`);
|
|
73
|
+
|
|
74
|
+
// service: property → maps to the service/model file
|
|
75
|
+
addMapping(0, serviceIdx, 0, 0);
|
|
76
|
+
lines.push(` service: ServicesMap['${pc}Services'];`);
|
|
77
|
+
|
|
78
|
+
// remaining properties → map to controller file
|
|
79
|
+
addMapping(0, controllerIdx, 0, 0);
|
|
80
|
+
lines.push(' services: ServicesMap;');
|
|
81
|
+
addMapping(0, controllerIdx, 0, 0);
|
|
82
|
+
lines.push(' Queue: QueueMap;');
|
|
83
|
+
addMapping(0, controllerIdx, 0, 0);
|
|
84
|
+
lines.push(' io: any;');
|
|
85
|
+
addMapping(0, controllerIdx, 0, 0);
|
|
86
|
+
lines.push('}');
|
|
87
|
+
lines.push('');
|
|
41
88
|
});
|
|
42
89
|
|
|
43
90
|
// Controller interfaces with their methods (for router autocomplete)
|
|
44
91
|
controllerEntries.forEach(({ tableName, filePath }) => {
|
|
45
92
|
const pc = pascalCase(tableName);
|
|
46
|
-
const methods =
|
|
93
|
+
const methods = extractMethodsWithLocations(filePath);
|
|
94
|
+
const sourceIdx = getSourceIndex(filePath);
|
|
95
|
+
|
|
96
|
+
addMapping(0, sourceIdx, 0, 0);
|
|
47
97
|
lines.push(`declare interface I${pc}Controller extends ${pc}ControllerBase {`);
|
|
48
|
-
methods.forEach((m) =>
|
|
49
|
-
|
|
98
|
+
methods.forEach((m) => {
|
|
99
|
+
addMapping(2, sourceIdx, m.line, m.col);
|
|
100
|
+
lines.push(` ${m.name}(...args: any[]): any;`);
|
|
101
|
+
});
|
|
102
|
+
addMapping(0, sourceIdx, 0, 0);
|
|
103
|
+
lines.push('}');
|
|
104
|
+
lines.push('');
|
|
50
105
|
});
|
|
51
106
|
|
|
52
|
-
// ControllersMap
|
|
107
|
+
// ControllersMap
|
|
53
108
|
lines.push('declare interface ControllersMap {');
|
|
54
|
-
controllerEntries.forEach(({ tableName }) => {
|
|
109
|
+
controllerEntries.forEach(({ tableName, filePath }) => {
|
|
55
110
|
const pc = pascalCase(tableName);
|
|
111
|
+
addMapping(2, getSourceIndex(filePath), 0, 0);
|
|
56
112
|
lines.push(` ${pc}Controller: I${pc}Controller;`);
|
|
57
113
|
});
|
|
58
114
|
lines.push('}', '');
|
|
59
115
|
|
|
60
|
-
|
|
116
|
+
lines.push('//# sourceMappingURL=controllers.d.ts.map');
|
|
117
|
+
|
|
118
|
+
const content = lines.join('\n');
|
|
119
|
+
const sourceMap = generateSourceMap('controllers.d.ts', sources, mappings);
|
|
120
|
+
|
|
121
|
+
return { content, sourceMap };
|
|
61
122
|
}
|
|
62
123
|
|
|
63
124
|
module.exports = { scanControllerEntries, generateControllersDts };
|
package/lib/generators/crud.js
CHANGED
|
@@ -1,69 +1,159 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { extractMethodsWithLocations } = require('../utils');
|
|
3
|
+
const { generateSourceMap } = require('../sourcemap');
|
|
4
|
+
|
|
5
|
+
// Extract method name from a .d.ts declaration line like " getWhere(...): ..."
|
|
6
|
+
function extractMethodName(line) {
|
|
7
|
+
const match = line.match(/^\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/);
|
|
8
|
+
return match ? match[1] : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = function generateCrudDts(projectRoot, outputDir) {
|
|
12
|
+
// Try to resolve knex-extended-crud source
|
|
13
|
+
let crudSourcePath = null;
|
|
14
|
+
let methodLineMap = {};
|
|
15
|
+
|
|
16
|
+
if (projectRoot && outputDir) {
|
|
17
|
+
try {
|
|
18
|
+
crudSourcePath = require.resolve('knex-extended-crud', {
|
|
19
|
+
paths: [projectRoot],
|
|
20
|
+
});
|
|
21
|
+
const methods = extractMethodsWithLocations(crudSourcePath);
|
|
22
|
+
methods.forEach((m) => {
|
|
23
|
+
methodLineMap[m.name] = { line: m.line, col: m.col };
|
|
24
|
+
});
|
|
25
|
+
} catch {
|
|
26
|
+
crudSourcePath = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const hasMappings = !!crudSourcePath;
|
|
31
|
+
const mappings = [];
|
|
32
|
+
const sources = [];
|
|
33
|
+
let sourceIdx = -1;
|
|
34
|
+
|
|
35
|
+
if (hasMappings) {
|
|
36
|
+
const rel = path.relative(outputDir, crudSourcePath).replace(/\\/g, '/');
|
|
37
|
+
sources.push(rel);
|
|
38
|
+
sourceIdx = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function addMethodMapping(linesArr, methodName) {
|
|
42
|
+
if (!hasMappings) return;
|
|
43
|
+
const loc = methodLineMap[methodName];
|
|
44
|
+
if (loc) {
|
|
45
|
+
mappings.push({
|
|
46
|
+
genLine: linesArr.length,
|
|
47
|
+
genCol: 2,
|
|
48
|
+
sourceIndex: sourceIdx,
|
|
49
|
+
sourceLine: loc.line,
|
|
50
|
+
sourceCol: loc.col,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const lines = [
|
|
3
56
|
'// Auto-generated by langaro-api — DO NOT EDIT',
|
|
4
57
|
'',
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// CRUD class
|
|
61
|
+
lines.push('declare class CRUD {');
|
|
62
|
+
lines.push(' table: string;');
|
|
63
|
+
lines.push(' knex: any;');
|
|
64
|
+
lines.push(' hide: string[];');
|
|
65
|
+
lines.push(' append: string[];');
|
|
66
|
+
lines.push(' fields: Record<string, any>;');
|
|
67
|
+
lines.push(' schema: any;');
|
|
68
|
+
lines.push(' options: {');
|
|
69
|
+
lines.push(' perPage: number;');
|
|
70
|
+
lines.push(' currentPage: number;');
|
|
71
|
+
lines.push(' isLengthAware: boolean;');
|
|
72
|
+
lines.push(' exactMatch: boolean;');
|
|
73
|
+
lines.push(' sortBy: string;');
|
|
74
|
+
lines.push(' sort: string;');
|
|
75
|
+
lines.push(' };');
|
|
76
|
+
lines.push('');
|
|
77
|
+
|
|
78
|
+
addMethodMapping(lines, 'createTransaction');
|
|
79
|
+
lines.push(' createTransaction(): Promise<any>;');
|
|
80
|
+
lines.push('');
|
|
81
|
+
|
|
82
|
+
addMethodMapping(lines, 'create');
|
|
83
|
+
lines.push(' create(data?: Record<string, any>, transaction?: any): Promise<{ success: boolean; data: any }>;');
|
|
84
|
+
addMethodMapping(lines, 'batchCreate');
|
|
85
|
+
lines.push(' batchCreate(data?: any[], transaction?: any): Promise<{ success: boolean; data: any[] }>;');
|
|
86
|
+
lines.push('');
|
|
87
|
+
|
|
88
|
+
addMethodMapping(lines, 'get');
|
|
89
|
+
lines.push(' get(options?: CRUDGetOptions, transaction?: any): Promise<{ success: boolean; data: any[]; pagination?: any }>;');
|
|
90
|
+
addMethodMapping(lines, 'getWhere');
|
|
91
|
+
lines.push(' getWhere(prop: string, value: any, options?: CRUDGetOptions, transaction?: any): Promise<{ success: boolean; data: any }>;');
|
|
92
|
+
addMethodMapping(lines, 'search');
|
|
93
|
+
lines.push(' search(field: string, term: string, options?: CRUDGetOptions, transaction?: any): Promise<{ success: boolean; data: any[] }>;');
|
|
94
|
+
addMethodMapping(lines, 'count');
|
|
95
|
+
lines.push(' count(options?: CRUDGetOptions, transaction?: any): Promise<number>;');
|
|
96
|
+
lines.push('');
|
|
97
|
+
|
|
98
|
+
addMethodMapping(lines, 'updateWhere');
|
|
99
|
+
lines.push(' updateWhere(');
|
|
100
|
+
lines.push(' prop: string, value: any, data?: Record<string, any>,');
|
|
101
|
+
lines.push(' options?: CRUDUpdateOptions, transaction?: any');
|
|
102
|
+
lines.push(' ): Promise<{ success: boolean; data: any }>;');
|
|
103
|
+
addMethodMapping(lines, 'batchUpdate');
|
|
104
|
+
lines.push(' batchUpdate(data: any[], options?: CRUDBatchUpdateOptions, transaction?: any): Promise<any[]>;');
|
|
105
|
+
lines.push('');
|
|
106
|
+
|
|
107
|
+
addMethodMapping(lines, 'deleteWhere');
|
|
108
|
+
lines.push(' deleteWhere(');
|
|
109
|
+
lines.push(' prop: string, values: any, options?: CRUDDeleteOptions, transaction?: any');
|
|
110
|
+
lines.push(' ): Promise<{ success: boolean; data: any }>;');
|
|
111
|
+
lines.push('');
|
|
112
|
+
|
|
113
|
+
addMethodMapping(lines, 'defaultInsertValidations');
|
|
114
|
+
lines.push(' defaultInsertValidations(');
|
|
115
|
+
lines.push(' requestData: Record<string, any>, options?: Record<string, any>, transaction?: any');
|
|
116
|
+
lines.push(' ): Promise<Record<string, any>>;');
|
|
117
|
+
addMethodMapping(lines, 'defaultUpdateValidations');
|
|
118
|
+
lines.push(' defaultUpdateValidations(');
|
|
119
|
+
lines.push(' itemId: any, requestData: Record<string, any>,');
|
|
120
|
+
lines.push(' options?: Record<string, any>, transaction?: any');
|
|
121
|
+
lines.push(' ): Promise<Record<string, any>>;');
|
|
122
|
+
addMethodMapping(lines, 'validateAndFormatFields');
|
|
123
|
+
lines.push(' validateAndFormatFields(');
|
|
124
|
+
lines.push(' data: Record<string, any>, skipFields?: string[], transaction?: any');
|
|
125
|
+
lines.push(' ): Promise<{ success: boolean; data?: Record<string, any>; err?: string }>;');
|
|
126
|
+
addMethodMapping(lines, 'validateForeignIds');
|
|
127
|
+
lines.push(' validateForeignIds(data: Record<string, any>, transaction?: any): Promise<void>;');
|
|
128
|
+
lines.push('');
|
|
129
|
+
|
|
130
|
+
addMethodMapping(lines, 'tableInfo');
|
|
131
|
+
lines.push(' tableInfo(options?: Record<string, any>, transaction?: any): Promise<Record<string, any>>;');
|
|
132
|
+
addMethodMapping(lines, 'requiredFields');
|
|
133
|
+
lines.push(' requiredFields(options?: Record<string, any>, transaction?: any): Promise<{ data: string[] }>;');
|
|
134
|
+
lines.push('');
|
|
135
|
+
|
|
136
|
+
addMethodMapping(lines, 'appendItems');
|
|
137
|
+
lines.push(' appendItems(');
|
|
138
|
+
lines.push(' items: any[], appendArr: string[], appendOptions?: Record<string, any>, transaction?: any');
|
|
139
|
+
lines.push(' ): Promise<any[]>;');
|
|
140
|
+
addMethodMapping(lines, 'refreshCache');
|
|
141
|
+
lines.push(' refreshCache(transaction?: any): Promise<void>;');
|
|
142
|
+
addMethodMapping(lines, 'clearSchemaCache');
|
|
143
|
+
lines.push(' clearSchemaCache(): void;');
|
|
144
|
+
addMethodMapping(lines, 'clearTableCache');
|
|
145
|
+
lines.push(' clearTableCache(tableName: string): void;');
|
|
146
|
+
addMethodMapping(lines, 'disableCache');
|
|
147
|
+
lines.push(' disableCache(): void;');
|
|
148
|
+
addMethodMapping(lines, 'enableCache');
|
|
149
|
+
lines.push(' enableCache(): void;');
|
|
150
|
+
addMethodMapping(lines, 'setCacheTimeout');
|
|
151
|
+
lines.push(' setCacheTimeout(timeout: number): void;');
|
|
152
|
+
lines.push('}');
|
|
153
|
+
lines.push('');
|
|
154
|
+
|
|
155
|
+
// Options interfaces (no source mappings needed)
|
|
156
|
+
lines.push(
|
|
67
157
|
'declare interface CRUDGetOptions {',
|
|
68
158
|
' perPage?: number;',
|
|
69
159
|
' currentPage?: number;',
|
|
@@ -153,5 +243,16 @@ module.exports = function generateCrudDts() {
|
|
|
153
243
|
' };',
|
|
154
244
|
'}',
|
|
155
245
|
'',
|
|
156
|
-
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (hasMappings) {
|
|
249
|
+
lines.push('//# sourceMappingURL=crud.d.ts.map');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const content = lines.join('\n');
|
|
253
|
+
const sourceMap = hasMappings
|
|
254
|
+
? generateSourceMap('crud.d.ts', sources, mappings)
|
|
255
|
+
: null;
|
|
256
|
+
|
|
257
|
+
return { content, sourceMap };
|
|
157
258
|
};
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { pascalCase,
|
|
3
|
+
const { pascalCase, extractMethodsWithLocations, isCustomService } = require('../utils');
|
|
4
|
+
const { generateSourceMap } = require('../sourcemap');
|
|
4
5
|
|
|
5
|
-
module.exports = function generateServicesDts(servicesDir, modelsDir) {
|
|
6
|
+
module.exports = function generateServicesDts(servicesDir, modelsDir, outputDir) {
|
|
6
7
|
const lines = ['// Auto-generated by langaro-api — DO NOT EDIT', ''];
|
|
7
8
|
const serviceEntries = [];
|
|
9
|
+
const mappings = [];
|
|
10
|
+
const sources = [];
|
|
11
|
+
const sourceIndexMap = {};
|
|
12
|
+
|
|
13
|
+
function getSourceIndex(filePath) {
|
|
14
|
+
const rel = path.relative(outputDir, filePath).replace(/\\/g, '/');
|
|
15
|
+
if (!(rel in sourceIndexMap)) {
|
|
16
|
+
sourceIndexMap[rel] = sources.length;
|
|
17
|
+
sources.push(rel);
|
|
18
|
+
}
|
|
19
|
+
return sourceIndexMap[rel];
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
// Services with custom .services.js files
|
|
10
23
|
if (fs.existsSync(servicesDir)) {
|
|
@@ -17,22 +30,42 @@ module.exports = function generateServicesDts(servicesDir, modelsDir) {
|
|
|
17
30
|
const interfaceName = `I${serviceName}`;
|
|
18
31
|
const filePath = path.join(servicesDir, file);
|
|
19
32
|
const custom = isCustomService(filePath);
|
|
20
|
-
const methods =
|
|
33
|
+
const methods = extractMethodsWithLocations(filePath);
|
|
34
|
+
const sourceIdx = getSourceIndex(filePath);
|
|
35
|
+
|
|
36
|
+
const addMapping = (col, srcLine, srcCol) => {
|
|
37
|
+
mappings.push({
|
|
38
|
+
genLine: lines.length,
|
|
39
|
+
genCol: col,
|
|
40
|
+
sourceIndex: sourceIdx,
|
|
41
|
+
sourceLine: srcLine,
|
|
42
|
+
sourceCol: srcCol,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
21
45
|
|
|
22
46
|
if (custom) {
|
|
47
|
+
addMapping(0, 0, 0);
|
|
23
48
|
lines.push(`declare interface ${interfaceName} {`);
|
|
49
|
+
addMapping(0, 0, 0);
|
|
24
50
|
lines.push(' table: string;');
|
|
25
51
|
} else {
|
|
52
|
+
addMapping(0, 0, 0);
|
|
26
53
|
lines.push(`declare interface ${interfaceName} extends CRUD {`);
|
|
27
54
|
}
|
|
28
|
-
methods.forEach((m) =>
|
|
29
|
-
|
|
55
|
+
methods.forEach((m) => {
|
|
56
|
+
addMapping(2, m.line, m.col);
|
|
57
|
+
lines.push(` ${m.name}(...args: any[]): any;`);
|
|
58
|
+
});
|
|
59
|
+
addMapping(0, 0, 0);
|
|
60
|
+
lines.push('}');
|
|
61
|
+
lines.push('');
|
|
30
62
|
|
|
31
63
|
serviceEntries.push({
|
|
32
64
|
interfaceName,
|
|
33
65
|
serviceName,
|
|
34
66
|
tableName,
|
|
35
67
|
custom,
|
|
68
|
+
sourceIdx,
|
|
36
69
|
});
|
|
37
70
|
});
|
|
38
71
|
}
|
|
@@ -60,12 +93,22 @@ module.exports = function generateServicesDts(servicesDir, modelsDir) {
|
|
|
60
93
|
lines.push('declare interface ServicesMap {');
|
|
61
94
|
serviceEntries
|
|
62
95
|
.sort((a, b) => a.serviceName.localeCompare(b.serviceName))
|
|
63
|
-
.forEach(({ interfaceName, serviceName }) => {
|
|
96
|
+
.forEach(({ interfaceName, serviceName, sourceIdx }) => {
|
|
97
|
+
const genLine = lines.length;
|
|
64
98
|
lines.push(` ${serviceName}: ${interfaceName};`);
|
|
99
|
+
if (sourceIdx !== undefined) {
|
|
100
|
+
mappings.push({
|
|
101
|
+
genLine,
|
|
102
|
+
genCol: 2,
|
|
103
|
+
sourceIndex: sourceIdx,
|
|
104
|
+
sourceLine: 0,
|
|
105
|
+
sourceCol: 0,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
65
108
|
});
|
|
66
109
|
lines.push('}', '');
|
|
67
110
|
|
|
68
|
-
// ModelsConstructorMap
|
|
111
|
+
// ModelsConstructorMap
|
|
69
112
|
lines.push('declare interface ModelsConstructorMap {');
|
|
70
113
|
serviceEntries
|
|
71
114
|
.filter(({ custom }) => !custom)
|
|
@@ -77,10 +120,26 @@ module.exports = function generateServicesDts(servicesDir, modelsDir) {
|
|
|
77
120
|
lines.push('}', '');
|
|
78
121
|
|
|
79
122
|
// Base classes for JSDoc @param on the "model" parameter
|
|
80
|
-
serviceEntries.forEach(({ serviceName, custom }) => {
|
|
81
|
-
if (!custom)
|
|
123
|
+
serviceEntries.forEach(({ serviceName, custom, sourceIdx }) => {
|
|
124
|
+
if (!custom) {
|
|
125
|
+
if (sourceIdx !== undefined) {
|
|
126
|
+
mappings.push({
|
|
127
|
+
genLine: lines.length,
|
|
128
|
+
genCol: 0,
|
|
129
|
+
sourceIndex: sourceIdx,
|
|
130
|
+
sourceLine: 0,
|
|
131
|
+
sourceCol: 0,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
lines.push(`declare class ${serviceName}Base extends CRUD {}`);
|
|
135
|
+
}
|
|
82
136
|
});
|
|
83
137
|
lines.push('');
|
|
84
138
|
|
|
85
|
-
|
|
139
|
+
lines.push('//# sourceMappingURL=services.d.ts.map');
|
|
140
|
+
|
|
141
|
+
const content = lines.join('\n');
|
|
142
|
+
const sourceMap = generateSourceMap('services.d.ts', sources, mappings);
|
|
143
|
+
|
|
144
|
+
return { content, sourceMap };
|
|
86
145
|
};
|
package/lib/index.js
CHANGED
|
@@ -44,9 +44,21 @@ function generateTypes(userConfig = {}) {
|
|
|
44
44
|
|
|
45
45
|
// Generate .d.ts files
|
|
46
46
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
fs.writeFileSync(path.join(outputDir, '
|
|
47
|
+
|
|
48
|
+
const crud = generateCrudDts(root, outputDir);
|
|
49
|
+
fs.writeFileSync(path.join(outputDir, 'crud.d.ts'), crud.content);
|
|
50
|
+
if (crud.sourceMap) {
|
|
51
|
+
fs.writeFileSync(path.join(outputDir, 'crud.d.ts.map'), crud.sourceMap);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const services = generateServicesDts(servicesDir, modelsDir, outputDir);
|
|
55
|
+
fs.writeFileSync(path.join(outputDir, 'services.d.ts'), services.content);
|
|
56
|
+
fs.writeFileSync(path.join(outputDir, 'services.d.ts.map'), services.sourceMap);
|
|
57
|
+
|
|
58
|
+
const controllers = generateControllersDts(controllersDir, outputDir, servicesDir, modelsDir);
|
|
59
|
+
fs.writeFileSync(path.join(outputDir, 'controllers.d.ts'), controllers.content);
|
|
60
|
+
fs.writeFileSync(path.join(outputDir, 'controllers.d.ts.map'), controllers.sourceMap);
|
|
61
|
+
|
|
50
62
|
fs.writeFileSync(path.join(outputDir, 'index.d.ts'), generateIndexDts());
|
|
51
63
|
|
|
52
64
|
// Inject JSDoc annotations into source files
|
package/lib/sourcemap.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
2
|
+
|
|
3
|
+
function encodeVLQ(value) {
|
|
4
|
+
let vlq = value < 0 ? (-value << 1) + 1 : value << 1;
|
|
5
|
+
let encoded = '';
|
|
6
|
+
do {
|
|
7
|
+
let digit = vlq & 0x1f;
|
|
8
|
+
vlq >>>= 5;
|
|
9
|
+
if (vlq > 0) digit |= 0x20;
|
|
10
|
+
encoded += BASE64[digit];
|
|
11
|
+
} while (vlq > 0);
|
|
12
|
+
return encoded;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a source map JSON string.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} generatedFile - The name of the generated .d.ts file
|
|
19
|
+
* @param {string[]} sources - Array of relative source file paths
|
|
20
|
+
* @param {Array<{genLine: number, genCol: number, sourceIndex: number, sourceLine: number, sourceCol: number}>} mappings
|
|
21
|
+
* @returns {string} JSON source map
|
|
22
|
+
*/
|
|
23
|
+
function generateSourceMap(generatedFile, sources, mappings) {
|
|
24
|
+
const sorted = [...mappings].sort((a, b) => a.genLine - b.genLine || a.genCol - b.genCol);
|
|
25
|
+
|
|
26
|
+
let prevGenLine = 0;
|
|
27
|
+
let prevSourceIndex = 0;
|
|
28
|
+
let prevSourceLine = 0;
|
|
29
|
+
let prevSourceCol = 0;
|
|
30
|
+
|
|
31
|
+
const lineSegments = [];
|
|
32
|
+
|
|
33
|
+
sorted.forEach((m) => {
|
|
34
|
+
// Fill empty lines with ;
|
|
35
|
+
while (prevGenLine < m.genLine) {
|
|
36
|
+
lineSegments.push('');
|
|
37
|
+
prevGenLine++;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const segment =
|
|
41
|
+
encodeVLQ(m.genCol) +
|
|
42
|
+
encodeVLQ(m.sourceIndex - prevSourceIndex) +
|
|
43
|
+
encodeVLQ(m.sourceLine - prevSourceLine) +
|
|
44
|
+
encodeVLQ(m.sourceCol - prevSourceCol);
|
|
45
|
+
|
|
46
|
+
// Append to current line
|
|
47
|
+
if (lineSegments.length === 0 || prevGenLine > lineSegments.length - 1) {
|
|
48
|
+
lineSegments.push(segment);
|
|
49
|
+
} else {
|
|
50
|
+
const existing = lineSegments[lineSegments.length - 1];
|
|
51
|
+
lineSegments[lineSegments.length - 1] = existing ? existing + ',' + segment : segment;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
prevSourceIndex = m.sourceIndex;
|
|
55
|
+
prevSourceLine = m.sourceLine;
|
|
56
|
+
prevSourceCol = m.sourceCol;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return JSON.stringify({
|
|
60
|
+
version: 3,
|
|
61
|
+
file: generatedFile,
|
|
62
|
+
sources,
|
|
63
|
+
mappings: lineSegments.join(';'),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { generateSourceMap };
|
package/lib/utils.js
CHANGED
|
@@ -26,6 +26,10 @@ function matchAll(regex, text) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function extractMethods(filePath) {
|
|
29
|
+
return extractMethodsWithLocations(filePath).map((m) => m.name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extractMethodsWithLocations(filePath) {
|
|
29
33
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
30
34
|
|
|
31
35
|
const patterns = [
|
|
@@ -34,10 +38,23 @@ function extractMethods(filePath) {
|
|
|
34
38
|
/^async\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/gm,
|
|
35
39
|
];
|
|
36
40
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const results = [];
|
|
43
|
+
|
|
44
|
+
patterns.forEach((r) => {
|
|
45
|
+
matchAll(r, content).forEach((m) => {
|
|
46
|
+
const name = m[1];
|
|
47
|
+
if (name === 'constructor' || name.startsWith('_') || JS_RESERVED.has(name)) return;
|
|
48
|
+
if (seen.has(name)) return;
|
|
49
|
+
seen.add(name);
|
|
50
|
+
const line = content.substring(0, m.index).split('\n').length - 1;
|
|
51
|
+
const lineText = content.split('\n')[line];
|
|
52
|
+
const col = lineText.indexOf(name);
|
|
53
|
+
results.push({ name, line, col: col >= 0 ? col : 0 });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return results;
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
function isCustomService(filePath) {
|
|
@@ -64,6 +81,7 @@ module.exports = {
|
|
|
64
81
|
pascalCase,
|
|
65
82
|
matchAll,
|
|
66
83
|
extractMethods,
|
|
84
|
+
extractMethodsWithLocations,
|
|
67
85
|
isCustomService,
|
|
68
86
|
scanJsFilesRecursively,
|
|
69
87
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langaro-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Auto-generate TypeScript types, JSDoc annotations, and boilerplate loaders for knex-extended-crud projects",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"boilerplate"
|
|
22
22
|
],
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"cron": "^4.4.0",
|
|
25
|
+
"knex-extended-crud": ">=2.0.31"
|
|
26
26
|
},
|
|
27
27
|
"peerDependenciesMeta": {
|
|
28
28
|
"cron": {
|