langaro-api 1.2.5 → 1.2.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/bin/langaro-api.js +10 -10
- package/lib/cli/new.js +2 -39
- package/lib/db.js +41 -0
- package/lib/generators/crud.js +25 -5
- package/lib/generators/services.js +17 -1
- package/lib/index.js +6 -2
- package/package.json +2 -2
package/bin/langaro-api.js
CHANGED
|
@@ -74,23 +74,23 @@ function confirm(question) {
|
|
|
74
74
|
|
|
75
75
|
// ── Command runners ──
|
|
76
76
|
|
|
77
|
-
function runGenerateTypes() {
|
|
77
|
+
async function runGenerateTypes() {
|
|
78
78
|
const { generateTypes } = require('../lib/index');
|
|
79
79
|
const config = loadConfig();
|
|
80
|
-
generateTypes(config);
|
|
80
|
+
await generateTypes(config);
|
|
81
81
|
console.log('[langaro-api] Types + JSDoc annotations generated.');
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function runWatch() {
|
|
84
|
+
async function runWatch() {
|
|
85
85
|
const { generateTypes, getWatchDirs } = require('../lib/index');
|
|
86
86
|
|
|
87
|
-
function generate() {
|
|
87
|
+
async function generate() {
|
|
88
88
|
const config = loadConfig();
|
|
89
|
-
generateTypes(config);
|
|
89
|
+
await generateTypes(config);
|
|
90
90
|
console.log('[langaro-api] Types + JSDoc annotations generated.');
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
generate();
|
|
93
|
+
await generate();
|
|
94
94
|
|
|
95
95
|
const config = loadConfig();
|
|
96
96
|
const dirs = getWatchDirs(config);
|
|
@@ -102,7 +102,7 @@ function runWatch() {
|
|
|
102
102
|
if (filename && filename.endsWith('.js')) {
|
|
103
103
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
104
104
|
debounceTimer = setTimeout(() => {
|
|
105
|
-
|
|
105
|
+
generate().catch((err) => console.error('[langaro-api] Error:', err.message));
|
|
106
106
|
}, 300);
|
|
107
107
|
}
|
|
108
108
|
});
|
|
@@ -153,13 +153,13 @@ async function main() {
|
|
|
153
153
|
if (command === 'update-docs') { runUpdateDocs(); return process.exit(0); }
|
|
154
154
|
if (command === '--watch') return runWatch();
|
|
155
155
|
if (command === 'generate' || command === 'types') {
|
|
156
|
-
runGenerateTypes();
|
|
156
|
+
await runGenerateTypes();
|
|
157
157
|
return process.exit(0);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// No args + non-TTY (e.g. piped, CI): just generate types
|
|
161
161
|
if (!process.stdin.isTTY) {
|
|
162
|
-
runGenerateTypes();
|
|
162
|
+
await runGenerateTypes();
|
|
163
163
|
return process.exit(0);
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -176,7 +176,7 @@ async function main() {
|
|
|
176
176
|
|
|
177
177
|
console.log('');
|
|
178
178
|
|
|
179
|
-
if (choice === 'types') { runGenerateTypes(); process.exit(0); }
|
|
179
|
+
if (choice === 'types') { await runGenerateTypes(); process.exit(0); }
|
|
180
180
|
if (choice === 'new') return runNew();
|
|
181
181
|
if (choice === 'migrate') return runMigrate();
|
|
182
182
|
if (choice === 'init') return runInit();
|
package/lib/cli/new.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
const { pascalCase } = require('../utils');
|
|
5
|
+
const { fetchTablesFromDatabase } = require('../db');
|
|
5
6
|
|
|
6
7
|
const DEFAULTS = {
|
|
7
8
|
services: 'src/database/services',
|
|
@@ -181,44 +182,6 @@ function singleSelect(question, allOptions) {
|
|
|
181
182
|
});
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
async function fetchTablesFromDatabase() {
|
|
185
|
-
try {
|
|
186
|
-
// Resolve modules from the project's node_modules, not langaro-api's
|
|
187
|
-
const projectRoot = process.cwd();
|
|
188
|
-
const resolveFrom = (mod) => require(require.resolve(mod, { paths: [projectRoot] }));
|
|
189
|
-
|
|
190
|
-
resolveFrom('dotenv').config({ path: path.join(projectRoot, '.env') });
|
|
191
|
-
|
|
192
|
-
const {
|
|
193
|
-
DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT,
|
|
194
|
-
} = process.env;
|
|
195
|
-
|
|
196
|
-
if (!DB_NAME) {
|
|
197
|
-
console.log('\x1b[33m Could not connect to database. DB_NAME not found in .env\x1b[0m');
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const knex = resolveFrom('knex')({
|
|
202
|
-
client: 'mysql2',
|
|
203
|
-
connection: {
|
|
204
|
-
host: DB_HOST, user: DB_USER, password: DB_PASSWORD, database: DB_NAME, port: DB_PORT,
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const results = await knex.raw(
|
|
209
|
-
'SELECT table_name FROM information_schema.tables WHERE table_schema = ?',
|
|
210
|
-
[DB_NAME],
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
const tables = results[0].map((row) => row.TABLE_NAME).sort();
|
|
214
|
-
await knex.destroy();
|
|
215
|
-
return tables;
|
|
216
|
-
} catch (err) {
|
|
217
|
-
console.log(`\x1b[33m Could not connect to database: ${err.message}\x1b[0m`);
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
185
|
function multiSelect(rl, question, options) {
|
|
223
186
|
return new Promise((resolve) => {
|
|
224
187
|
const selected = new Set(options.map((_, i) => i));
|
|
@@ -393,7 +356,7 @@ async function run(config = {}) {
|
|
|
393
356
|
if (created.length > 0) {
|
|
394
357
|
// Auto-regenerate types so the new resource gets IntelliSense immediately
|
|
395
358
|
const { generateTypes } = require('../index');
|
|
396
|
-
generateTypes(cfg);
|
|
359
|
+
await generateTypes(cfg);
|
|
397
360
|
console.log(`\n\x1b[32mDone!\x1b[0m Created ${created.length} file(s). Types regenerated.\n`);
|
|
398
361
|
} else {
|
|
399
362
|
console.log('\nNo files created (all already exist).\n');
|
package/lib/db.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
async function fetchTablesFromDatabase() {
|
|
4
|
+
try {
|
|
5
|
+
// Resolve modules from the project's node_modules, not langaro-api's
|
|
6
|
+
const projectRoot = process.cwd();
|
|
7
|
+
const resolveFrom = (mod) => require(require.resolve(mod, { paths: [projectRoot] }));
|
|
8
|
+
|
|
9
|
+
resolveFrom('dotenv').config({ path: path.join(projectRoot, '.env') });
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT,
|
|
13
|
+
} = process.env;
|
|
14
|
+
|
|
15
|
+
if (!DB_NAME) {
|
|
16
|
+
console.log('\x1b[33m Could not connect to database. DB_NAME not found in .env\x1b[0m');
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const knex = resolveFrom('knex')({
|
|
21
|
+
client: 'mysql2',
|
|
22
|
+
connection: {
|
|
23
|
+
host: DB_HOST, user: DB_USER, password: DB_PASSWORD, database: DB_NAME, port: DB_PORT,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const results = await knex.raw(
|
|
28
|
+
'SELECT table_name FROM information_schema.tables WHERE table_schema = ?',
|
|
29
|
+
[DB_NAME],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const tables = results[0].map((row) => row.TABLE_NAME).sort();
|
|
33
|
+
await knex.destroy();
|
|
34
|
+
return tables;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.log(`\x1b[33m Could not connect to database: ${err.message}\x1b[0m`);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { fetchTablesFromDatabase };
|
package/lib/generators/crud.js
CHANGED
|
@@ -140,7 +140,7 @@ module.exports = function generateCrudDts(projectRoot, outputDir) {
|
|
|
140
140
|
|
|
141
141
|
addMethodMapping(lines, 'appendItems');
|
|
142
142
|
lines.push(' appendItems(');
|
|
143
|
-
lines.push(' items: any[], appendArr: string[], appendOptions?: Record<string,
|
|
143
|
+
lines.push(' items: any[], appendArr: string[], appendOptions?: Record<string, AppendTableOptions>, transaction?: any');
|
|
144
144
|
lines.push(' ): Promise<any[]>;');
|
|
145
145
|
addMethodMapping(lines, 'refreshCache');
|
|
146
146
|
lines.push(' refreshCache(transaction?: any): Promise<void>;');
|
|
@@ -161,12 +161,32 @@ module.exports = function generateCrudDts(projectRoot, outputDir) {
|
|
|
161
161
|
|
|
162
162
|
// Options interfaces (no source mappings needed)
|
|
163
163
|
lines.push(
|
|
164
|
+
"declare type AndWhereOperator = '=' | '>' | '>=' | '<' | '<=' | '<>' | 'in' | 'not in' | 'between' | 'not between' | 'like' | 'json_overlaps';",
|
|
165
|
+
'',
|
|
166
|
+
'/** A condition with an explicit operator: [field, operator, value] */',
|
|
167
|
+
'declare type AndWhereConditionWithOperator = [string, AndWhereOperator, any];',
|
|
168
|
+
'/** A condition using the default "=" operator: [field, value] */',
|
|
169
|
+
'declare type AndWhereConditionWithoutOperator = [string, any];',
|
|
170
|
+
'',
|
|
171
|
+
'declare type AndWhereCondition = AndWhereConditionWithOperator | AndWhereConditionWithoutOperator;',
|
|
172
|
+
'',
|
|
173
|
+
'/** Options for an appended (related) table inside appendOptions */',
|
|
174
|
+
'declare interface AppendTableOptions {',
|
|
175
|
+
' /** Filter conditions applied to the appended table */',
|
|
176
|
+
' andWhere?: AndWhereCondition[];',
|
|
177
|
+
' /** Select only these columns from the appended table */',
|
|
178
|
+
' showOnly?: string | string[];',
|
|
179
|
+
' /** Fields to show (overrides hide list) */',
|
|
180
|
+
' show?: string[];',
|
|
181
|
+
' [key: string]: any;',
|
|
182
|
+
'}',
|
|
183
|
+
'',
|
|
164
184
|
'declare interface CRUDGetOptions {',
|
|
165
185
|
' perPage?: number;',
|
|
166
186
|
' currentPage?: number;',
|
|
167
187
|
' append?: string[];',
|
|
168
|
-
' appendOptions?: Record<string,
|
|
169
|
-
' andWhere?:
|
|
188
|
+
' appendOptions?: Record<string, AppendTableOptions>;',
|
|
189
|
+
' andWhere?: AndWhereCondition[];',
|
|
170
190
|
' search?: string;',
|
|
171
191
|
' searchExactMatch?: boolean;',
|
|
172
192
|
' searchFields?: string[];',
|
|
@@ -189,7 +209,7 @@ module.exports = function generateCrudDts(projectRoot, outputDir) {
|
|
|
189
209
|
' allowForbiddenUpdates?: boolean;',
|
|
190
210
|
' extraValidations?: Array<(data: any) => void>;',
|
|
191
211
|
' where?: (query: any) => any;',
|
|
192
|
-
' andWhere?:
|
|
212
|
+
' andWhere?: AndWhereCondition[];',
|
|
193
213
|
' whenSuccess?: (data: any) => void;',
|
|
194
214
|
' /** Skip automatic Redis query cache invalidation */',
|
|
195
215
|
' skipCacheInvalidation?: boolean;',
|
|
@@ -204,7 +224,7 @@ module.exports = function generateCrudDts(projectRoot, outputDir) {
|
|
|
204
224
|
'',
|
|
205
225
|
'declare interface CRUDDeleteOptions {',
|
|
206
226
|
' where?: (query: any) => any;',
|
|
207
|
-
' andWhere?:
|
|
227
|
+
' andWhere?: AndWhereCondition[];',
|
|
208
228
|
' /** Skip automatic Redis query cache invalidation */',
|
|
209
229
|
' skipCacheInvalidation?: boolean;',
|
|
210
230
|
' [key: string]: any;',
|
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { pascalCase, extractMethodsWithLocations, isCustomService } = require('../utils');
|
|
4
4
|
const { generateSourceMap } = require('../sourcemap');
|
|
5
5
|
|
|
6
|
-
module.exports = function generateServicesDts(servicesDir, modelsDir, outputDir) {
|
|
6
|
+
module.exports = function generateServicesDts(servicesDir, modelsDir, outputDir, dbTableNames = []) {
|
|
7
7
|
const lines = ['// Auto-generated by langaro-api — DO NOT EDIT', ''];
|
|
8
8
|
const serviceEntries = [];
|
|
9
9
|
const mappings = [];
|
|
@@ -89,6 +89,22 @@ module.exports = function generateServicesDts(servicesDir, modelsDir, outputDir)
|
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// DB tables discovered via introspection — pure CRUD (no file on disk)
|
|
93
|
+
dbTableNames
|
|
94
|
+
.sort()
|
|
95
|
+
.forEach((tableName) => {
|
|
96
|
+
const serviceName = `${pascalCase(tableName)}Services`;
|
|
97
|
+
if (serviceEntries.find((e) => e.serviceName === serviceName)) return;
|
|
98
|
+
const interfaceName = `I${serviceName}`;
|
|
99
|
+
lines.push(`declare interface ${interfaceName} extends CRUD {}`, '');
|
|
100
|
+
serviceEntries.push({
|
|
101
|
+
interfaceName,
|
|
102
|
+
serviceName,
|
|
103
|
+
tableName,
|
|
104
|
+
custom: false,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
92
108
|
// ServicesMap
|
|
93
109
|
lines.push('declare interface ServicesMap {');
|
|
94
110
|
serviceEntries
|
package/lib/index.js
CHANGED
|
@@ -27,7 +27,7 @@ const DEFAULTS = {
|
|
|
27
27
|
middlewares: 'src/middlewares',
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
function generateTypes(userConfig = {}) {
|
|
30
|
+
async function generateTypes(userConfig = {}) {
|
|
31
31
|
const config = { ...DEFAULTS, ...userConfig };
|
|
32
32
|
const root = config.root;
|
|
33
33
|
|
|
@@ -51,7 +51,11 @@ function generateTypes(userConfig = {}) {
|
|
|
51
51
|
fs.writeFileSync(path.join(outputDir, 'crud.d.ts.map'), crud.sourceMap);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
// Discover DB tables via introspection (same as loadModels does at runtime)
|
|
55
|
+
const { fetchTablesFromDatabase } = require('./db');
|
|
56
|
+
const dbTableNames = await fetchTablesFromDatabase() || [];
|
|
57
|
+
|
|
58
|
+
const services = generateServicesDts(servicesDir, modelsDir, outputDir, dbTableNames);
|
|
55
59
|
fs.writeFileSync(path.join(outputDir, 'services.d.ts'), services.content);
|
|
56
60
|
fs.writeFileSync(path.join(outputDir, 'services.d.ts.map'), services.sourceMap);
|
|
57
61
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langaro-api",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.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": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"cron": ">=3.0.0",
|
|
25
|
-
"knex-extended-crud": ">=2.1.
|
|
25
|
+
"knex-extended-crud": ">=2.1.5"
|
|
26
26
|
},
|
|
27
27
|
"peerDependenciesMeta": {
|
|
28
28
|
"cron": {
|