langaro-api 1.0.6 → 1.0.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/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) => req.method === 'OPTIONS',
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, extractMethods } = require('../utils');
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 generateControllersDts(controllersDir) {
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
- lines.push(
33
- `declare class ${pc}ControllerBase {`,
34
- ` service: ServicesMap['${pc}Services'];`,
35
- ' services: ServicesMap;',
36
- ' Queue: QueueMap;',
37
- ' io: any;',
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 = extractMethods(filePath);
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) => lines.push(` ${m}(...args: any[]): any;`));
49
- lines.push('}', '');
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 — used to type the "controllers" param in routers
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
- return lines.join('\n');
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 };
@@ -1,69 +1,159 @@
1
- module.exports = function generateCrudDts() {
2
- return [
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
- 'declare class CRUD {',
6
- ' table: string;',
7
- ' knex: any;',
8
- ' hide: string[];',
9
- ' append: string[];',
10
- ' fields: Record<string, any>;',
11
- ' schema: any;',
12
- ' options: {',
13
- ' perPage: number;',
14
- ' currentPage: number;',
15
- ' isLengthAware: boolean;',
16
- ' exactMatch: boolean;',
17
- ' sortBy: string;',
18
- ' sort: string;',
19
- ' };',
20
- '',
21
- ' createTransaction(): Promise<any>;',
22
- '',
23
- ' create(data?: Record<string, any>, transaction?: any): Promise<{ success: boolean; data: any }>;',
24
- ' batchCreate(data?: any[], transaction?: any): Promise<{ success: boolean; data: any[] }>;',
25
- '',
26
- ' get(options?: CRUDGetOptions, transaction?: any): Promise<{ success: boolean; data: any[]; pagination?: any }>;',
27
- ' getWhere(prop: string, value: any, options?: CRUDGetOptions, transaction?: any): Promise<{ success: boolean; data: any }>;',
28
- ' search(field: string, term: string, options?: CRUDGetOptions, transaction?: any): Promise<{ success: boolean; data: any[] }>;',
29
- ' count(options?: CRUDGetOptions, transaction?: any): Promise<number>;',
30
- '',
31
- ' updateWhere(',
32
- ' prop: string, value: any, data?: Record<string, any>,',
33
- ' options?: CRUDUpdateOptions, transaction?: any',
34
- ' ): Promise<{ success: boolean; data: any }>;',
35
- ' batchUpdate(data: any[], options?: CRUDBatchUpdateOptions, transaction?: any): Promise<any[]>;',
36
- '',
37
- ' deleteWhere(',
38
- ' prop: string, values: any, options?: CRUDDeleteOptions, transaction?: any',
39
- ' ): Promise<{ success: boolean; data: any }>;',
40
- '',
41
- ' defaultInsertValidations(',
42
- ' requestData: Record<string, any>, options?: Record<string, any>, transaction?: any',
43
- ' ): Promise<Record<string, any>>;',
44
- ' defaultUpdateValidations(',
45
- ' itemId: any, requestData: Record<string, any>,',
46
- ' options?: Record<string, any>, transaction?: any',
47
- ' ): Promise<Record<string, any>>;',
48
- ' validateAndFormatFields(',
49
- ' data: Record<string, any>, skipFields?: string[], transaction?: any',
50
- ' ): Promise<{ success: boolean; data?: Record<string, any>; err?: string }>;',
51
- ' validateForeignIds(data: Record<string, any>, transaction?: any): Promise<void>;',
52
- '',
53
- ' tableInfo(options?: Record<string, any>, transaction?: any): Promise<Record<string, any>>;',
54
- ' requiredFields(options?: Record<string, any>, transaction?: any): Promise<{ data: string[] }>;',
55
- '',
56
- ' appendItems(',
57
- ' items: any[], appendArr: string[], appendOptions?: Record<string, any>, transaction?: any',
58
- ' ): Promise<any[]>;',
59
- ' refreshCache(transaction?: any): Promise<void>;',
60
- ' clearSchemaCache(): void;',
61
- ' clearTableCache(tableName: string): void;',
62
- ' disableCache(): void;',
63
- ' enableCache(): void;',
64
- ' setCacheTimeout(timeout: number): void;',
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
- ].join('\n');
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, extractMethods, isCustomService } = require('../utils');
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 = extractMethods(filePath);
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) => lines.push(` ${m}(...args: any[]): any;`));
29
- lines.push('}', '');
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 — typed table names for "new models.xxx()"
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) lines.push(`declare class ${serviceName}Base extends CRUD {}`);
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
- return lines.join('\n');
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
- fs.writeFileSync(path.join(outputDir, 'crud.d.ts'), generateCrudDts());
48
- fs.writeFileSync(path.join(outputDir, 'services.d.ts'), generateServicesDts(servicesDir, modelsDir));
49
- fs.writeFileSync(path.join(outputDir, 'controllers.d.ts'), generateControllersDts(controllersDir));
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
@@ -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 names = patterns.flatMap((r) => matchAll(r, content).map((m) => m[1]));
38
- return [...new Set(names)].filter(
39
- (n) => n !== 'constructor' && !n.startsWith('_') && !JS_RESERVED.has(n),
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.6",
3
+ "version": "1.0.8",
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
- "knex-extended-crud": ">=2.0.31",
25
- "cron": ">=3.0.0"
24
+ "cron": ">=3.0.0",
25
+ "knex-extended-crud": ">=2.0.31"
26
26
  },
27
27
  "peerDependenciesMeta": {
28
28
  "cron": {