@devmoods/express-extras 0.60.0 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manage.js +20 -2
- package/dist/postgres-ts-generator.d.ts +11 -0
- package/dist/postgres-ts-generator.js +115 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +4 -4
- package/package.json +2 -1
package/dist/manage.js
CHANGED
|
@@ -71,9 +71,9 @@ export function registerDefaultCommands(app) {
|
|
|
71
71
|
path: openApiFile,
|
|
72
72
|
},
|
|
73
73
|
output: {
|
|
74
|
-
format:
|
|
74
|
+
format: false,
|
|
75
75
|
path: options.output,
|
|
76
|
-
lint:
|
|
76
|
+
lint: false,
|
|
77
77
|
tsConfigPath: resolve('./tsconfig.json'),
|
|
78
78
|
},
|
|
79
79
|
plugins: [
|
|
@@ -91,4 +91,22 @@ export function registerDefaultCommands(app) {
|
|
|
91
91
|
});
|
|
92
92
|
}));
|
|
93
93
|
});
|
|
94
|
+
registerManagementCommand((command, cli) => {
|
|
95
|
+
command('generate-db')
|
|
96
|
+
.option('--output <string>', 'Output file path', 'src/types/db.gen.ts')
|
|
97
|
+
.option('--connection <string>', 'Database connection string')
|
|
98
|
+
.action(cli.wrap(async (options) => {
|
|
99
|
+
const { generateDatabaseTypes } = await import('./postgres-ts-generator.js');
|
|
100
|
+
try {
|
|
101
|
+
await generateDatabaseTypes({
|
|
102
|
+
outputPath: options.output,
|
|
103
|
+
connectionString: options.connection || config.value.POSTGRES_URL,
|
|
104
|
+
});
|
|
105
|
+
cli.writeLine(`✅ Database types generated successfully at: ${options.output}\n`);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
cli.writeLine(`❌ Failed to generate database types: ${error}\n`);
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
94
112
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface GenerateDatabaseTypesOptions {
|
|
2
|
+
outputPath: string;
|
|
3
|
+
connectionString: string;
|
|
4
|
+
schema?: string;
|
|
5
|
+
excludeTables?: string[];
|
|
6
|
+
includeTables?: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generates complete TypeScript definitions from database schema
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateDatabaseTypes(options: GenerateDatabaseTypesOptions): Promise<void>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import {} from 'pg-structure';
|
|
4
|
+
import { pgStructure } from 'pg-structure/dist/main.js';
|
|
5
|
+
/**
|
|
6
|
+
* Converts PostgreSQL data type to TypeScript type
|
|
7
|
+
*/
|
|
8
|
+
function pgTypeToTsType(pgType, isNullable) {
|
|
9
|
+
const nullableSuffix = isNullable ? ' | null' : '';
|
|
10
|
+
switch (pgType.name.toLowerCase()) {
|
|
11
|
+
case 'uuid':
|
|
12
|
+
return `string${nullableSuffix}`;
|
|
13
|
+
case 'text':
|
|
14
|
+
case 'varchar':
|
|
15
|
+
case 'char':
|
|
16
|
+
case 'character varying':
|
|
17
|
+
return `string${nullableSuffix}`;
|
|
18
|
+
case 'integer':
|
|
19
|
+
case 'int':
|
|
20
|
+
case 'int4':
|
|
21
|
+
return `number${nullableSuffix}`;
|
|
22
|
+
case 'bigint':
|
|
23
|
+
case 'int8':
|
|
24
|
+
case 'bigserial':
|
|
25
|
+
case 'serial':
|
|
26
|
+
return `number${nullableSuffix}`;
|
|
27
|
+
case 'smallint':
|
|
28
|
+
case 'int2':
|
|
29
|
+
return `number${nullableSuffix}`;
|
|
30
|
+
case 'decimal':
|
|
31
|
+
case 'numeric':
|
|
32
|
+
case 'real':
|
|
33
|
+
case 'double precision':
|
|
34
|
+
case 'float':
|
|
35
|
+
case 'float4':
|
|
36
|
+
case 'float8':
|
|
37
|
+
return `number${nullableSuffix}`;
|
|
38
|
+
case 'boolean':
|
|
39
|
+
case 'bool':
|
|
40
|
+
return `boolean${nullableSuffix}`;
|
|
41
|
+
case 'timestamp':
|
|
42
|
+
case 'timestamp with time zone':
|
|
43
|
+
case 'timestamptz':
|
|
44
|
+
return `Date${nullableSuffix}`;
|
|
45
|
+
case 'date':
|
|
46
|
+
return `Date${nullableSuffix}`;
|
|
47
|
+
case 'time':
|
|
48
|
+
case 'time with time zone':
|
|
49
|
+
return `string${nullableSuffix}`;
|
|
50
|
+
case 'json':
|
|
51
|
+
case 'jsonb':
|
|
52
|
+
return `Record<string, any>${nullableSuffix}`;
|
|
53
|
+
case 'bytea':
|
|
54
|
+
return `Buffer${nullableSuffix}`;
|
|
55
|
+
case 'inet':
|
|
56
|
+
case 'cidr':
|
|
57
|
+
return `string${nullableSuffix}`;
|
|
58
|
+
case 'array':
|
|
59
|
+
return `any[]${nullableSuffix}`;
|
|
60
|
+
default:
|
|
61
|
+
return `any${nullableSuffix}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generates TypeScript interface for a table
|
|
66
|
+
*/
|
|
67
|
+
function generateTableInterface(table, tableName) {
|
|
68
|
+
const interfaceName = tableName.charAt(0).toUpperCase() + tableName.slice(1);
|
|
69
|
+
let interfaceCode = `\n/**\n * ${interfaceName} table interface\n */\nexport interface ${interfaceName} {\n`;
|
|
70
|
+
for (const column of table.columns) {
|
|
71
|
+
const tsType = pgTypeToTsType(column.type, !column.notNull);
|
|
72
|
+
const comment = column.comment ? ` /** ${column.comment} */` : '';
|
|
73
|
+
interfaceCode += ` /** ${column.name} */\n ${column.name}: ${tsType};${comment}\n`;
|
|
74
|
+
}
|
|
75
|
+
interfaceCode += '}\n';
|
|
76
|
+
return interfaceCode;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generates complete TypeScript definitions from database schema
|
|
80
|
+
*/
|
|
81
|
+
export async function generateDatabaseTypes(options) {
|
|
82
|
+
const { outputPath, connectionString, schema = 'public', excludeTables = ['migrations', 'migration_lock'], includeTables, } = options;
|
|
83
|
+
// Connect to database and get schema
|
|
84
|
+
const db = await pgStructure(connectionString);
|
|
85
|
+
let output = `/**
|
|
86
|
+
* TypeScript definitions for database tables
|
|
87
|
+
* Auto-generated from database schema
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
`;
|
|
91
|
+
const tables = db.schemas.get(schema)?.tables || [];
|
|
92
|
+
const filteredTables = tables.filter((table) => {
|
|
93
|
+
if (includeTables && includeTables.length > 0) {
|
|
94
|
+
return includeTables.includes(table.name);
|
|
95
|
+
}
|
|
96
|
+
return !excludeTables.includes(table.name);
|
|
97
|
+
});
|
|
98
|
+
// Generate table interfaces
|
|
99
|
+
for (const table of filteredTables) {
|
|
100
|
+
output += generateTableInterface(table, table.name);
|
|
101
|
+
}
|
|
102
|
+
// Generate DB interface
|
|
103
|
+
output +=
|
|
104
|
+
'\n/**\n * Database schema interface\n * Maps table names to their respective interfaces\n */\nexport interface DB {\n';
|
|
105
|
+
for (const table of filteredTables) {
|
|
106
|
+
const interfaceName = table.name.charAt(0).toUpperCase() + table.name.slice(1);
|
|
107
|
+
output += ` ${table.name}: ${interfaceName};\n`;
|
|
108
|
+
}
|
|
109
|
+
output += '}\n';
|
|
110
|
+
// Ensure output directory exists
|
|
111
|
+
const outputDir = dirname(resolve(outputPath));
|
|
112
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
113
|
+
// Write the generated types
|
|
114
|
+
await fs.writeFile(resolve(outputPath), output, 'utf-8');
|
|
115
|
+
}
|
package/dist/routes.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface Key {
|
|
|
21
21
|
optional: boolean;
|
|
22
22
|
offset: number;
|
|
23
23
|
}
|
|
24
|
+
export declare function extractTagsFromPath(path: string): string[];
|
|
25
|
+
export declare function convertPathToOperationId(method: string, path: string): string;
|
|
24
26
|
export declare function toOpenAPISpec(app: Express, info?: {
|
|
25
27
|
title: string;
|
|
26
28
|
version: string;
|
package/dist/routes.js
CHANGED
|
@@ -143,9 +143,9 @@ const mapKeysToPath = (layerRegexPath, keys) => {
|
|
|
143
143
|
.replace(/\\/gi, '')
|
|
144
144
|
.replace(/\/{2,}/gi, '/');
|
|
145
145
|
};
|
|
146
|
-
function extractTagsFromPath(path) {
|
|
146
|
+
export function extractTagsFromPath(path) {
|
|
147
147
|
const segments = path
|
|
148
|
-
.replace(/^\/api\/v\d
|
|
148
|
+
.replace(/^\/api(\/v\d+)?\//, '') // Remove /api, /api/v1, /api/v2, etc.
|
|
149
149
|
.split('/')
|
|
150
150
|
.filter(Boolean);
|
|
151
151
|
if (segments.length === 0) {
|
|
@@ -155,12 +155,12 @@ function extractTagsFromPath(path) {
|
|
|
155
155
|
const primaryTag = segments[0].charAt(0).toUpperCase() + segments[0].slice(1);
|
|
156
156
|
return [primaryTag];
|
|
157
157
|
}
|
|
158
|
-
function convertPathToOperationId(method, path) {
|
|
158
|
+
export function convertPathToOperationId(method, path) {
|
|
159
159
|
// Clean the path and split into segments
|
|
160
160
|
const segments = path
|
|
161
161
|
.replace(/[{}]/g, '') // Remove OpenAPI parameter braces
|
|
162
162
|
.replace(/[^a-zA-Z0-9/-]/g, '') // Keep hyphens, remove other non-alphanumeric chars
|
|
163
|
-
.replace(/^\/api\/v\d
|
|
163
|
+
.replace(/^\/api(\/v\d+)?\//, '') // Remove /api, /api/v1, /api/v2, etc. prefixes
|
|
164
164
|
.split('/') // Split by slashes
|
|
165
165
|
.filter(Boolean) // Remove empty strings
|
|
166
166
|
.map((segment) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"jsonwebtoken": "^9.0.2",
|
|
93
93
|
"nodemailer": "^7.0.6",
|
|
94
94
|
"pg": "^8.16.3",
|
|
95
|
+
"pg-structure": "^7.15.3",
|
|
95
96
|
"redis": "^5.8.2",
|
|
96
97
|
"sonda": "^0.9.0",
|
|
97
98
|
"sql-formatter": "^15.6.6",
|