pg-codegen 2.1.5
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 +23 -0
- package/README.md +144 -0
- package/codegen/codegen.d.ts +7 -0
- package/codegen/codegen.js +152 -0
- package/codegen/namespaces.d.ts +7 -0
- package/codegen/namespaces.js +100 -0
- package/dist/README.md +144 -0
- package/dist/package.json +51 -0
- package/esm/codegen/codegen.js +122 -0
- package/esm/codegen/namespaces.js +70 -0
- package/esm/index.js +69 -0
- package/esm/introspect.js +26 -0
- package/esm/query.js +420 -0
- package/esm/types.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +74 -0
- package/introspect.d.ts +11 -0
- package/introspect.js +30 -0
- package/package.json +51 -0
- package/query.d.ts +5 -0
- package/query.js +424 -0
- package/types.d.ts +128 -0
- package/types.js +2 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import generate from '@babel/generator';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
export const generateCodeTree = (databaseObjects, options) => {
|
|
4
|
+
const { includeTimestamps, includeUUID } = options;
|
|
5
|
+
const schemaFiles = {};
|
|
6
|
+
// Common types
|
|
7
|
+
const commonTypes = [];
|
|
8
|
+
if (includeTimestamps) {
|
|
9
|
+
commonTypes.push(t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier('Timestamp'), null, t.tsStringKeyword())));
|
|
10
|
+
}
|
|
11
|
+
if (includeUUID) {
|
|
12
|
+
commonTypes.push(t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier('UUID'), null, t.tsStringKeyword())));
|
|
13
|
+
}
|
|
14
|
+
schemaFiles['schemas/_common.ts'] = commonTypes;
|
|
15
|
+
// Classes & Interfaces per schema
|
|
16
|
+
databaseObjects.forEach((obj) => {
|
|
17
|
+
if (obj.kind === 'class' && obj.classKind === 'r') {
|
|
18
|
+
const schemaName = obj.namespaceName;
|
|
19
|
+
const pascalName = toPascalCase(obj.name);
|
|
20
|
+
const interfaceFields = [];
|
|
21
|
+
const classFields = [];
|
|
22
|
+
const constructorBody = [];
|
|
23
|
+
const usedTypes = new Set();
|
|
24
|
+
databaseObjects.forEach((attr) => {
|
|
25
|
+
if (attr.kind === 'attribute' && attr.classId === obj.id) {
|
|
26
|
+
const fieldType = mapPostgresTypeToTSType(attr.typeId, attr.isNotNull);
|
|
27
|
+
const postgresType = mapPostgresTypeToIdentifier(attr.typeId);
|
|
28
|
+
if (postgresType)
|
|
29
|
+
usedTypes.add(postgresType);
|
|
30
|
+
interfaceFields.push(t.tsPropertySignature(t.identifier(attr.name), t.tsTypeAnnotation(fieldType)));
|
|
31
|
+
classFields.push(t.classProperty(t.identifier(attr.name), undefined, t.tsTypeAnnotation(fieldType)));
|
|
32
|
+
constructorBody.push(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(attr.name)), t.memberExpression(t.identifier('data'), t.identifier(attr.name)))));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const interfaceDeclaration = t.exportNamedDeclaration(t.tsInterfaceDeclaration(t.identifier(pascalName), null, [], t.tsInterfaceBody(interfaceFields)));
|
|
36
|
+
const data = t.identifier('data');
|
|
37
|
+
data.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(pascalName)));
|
|
38
|
+
const classImplements = t.tsExpressionWithTypeArguments(t.identifier(pascalName));
|
|
39
|
+
const classDeclaration = t.exportNamedDeclaration(t.classDeclaration(t.identifier(pascalName), null, t.classBody([
|
|
40
|
+
...classFields,
|
|
41
|
+
t.classMethod('constructor', t.identifier('constructor'), [data], t.blockStatement(constructorBody))
|
|
42
|
+
])));
|
|
43
|
+
classDeclaration.declaration.implements = [classImplements];
|
|
44
|
+
const filePath = `schemas/${schemaName}.ts`;
|
|
45
|
+
if (!schemaFiles[filePath])
|
|
46
|
+
schemaFiles[filePath] = [];
|
|
47
|
+
if (usedTypes.size > 0) {
|
|
48
|
+
const existingImports = schemaFiles[filePath].find((s) => t.isImportDeclaration(s) && s.source.value === './_common');
|
|
49
|
+
if (!existingImports) {
|
|
50
|
+
schemaFiles[filePath].unshift(t.importDeclaration(Array.from(usedTypes).map((type) => t.importSpecifier(t.identifier(type), t.identifier(type))), t.stringLiteral('./_common')));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const current = new Set(existingImports.specifiers.map((s) => s.local.name));
|
|
54
|
+
Array.from(usedTypes).forEach((type) => {
|
|
55
|
+
if (!current.has(type)) {
|
|
56
|
+
existingImports.specifiers.push(t.importSpecifier(t.identifier(type), t.identifier(type)));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
schemaFiles[filePath].push(interfaceDeclaration, classDeclaration);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// index.ts exports
|
|
65
|
+
const indexFileStatements = [];
|
|
66
|
+
Object.keys(schemaFiles).forEach((filePath) => {
|
|
67
|
+
const schemaName = filePath.replace('schemas/', '').replace('.ts', '');
|
|
68
|
+
if (schemaName === '_common')
|
|
69
|
+
return;
|
|
70
|
+
if (schemaName === 'public') {
|
|
71
|
+
indexFileStatements.push(t.exportAllDeclaration(t.stringLiteral(`./${filePath.replace('.ts', '')}`)));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
indexFileStatements.push(t.importDeclaration([t.importNamespaceSpecifier(t.identifier(schemaName))], t.stringLiteral(`./${filePath.replace('.ts', '')}`)), t.exportNamedDeclaration(null, [
|
|
75
|
+
t.exportSpecifier(t.identifier(schemaName), t.identifier(schemaName))
|
|
76
|
+
]));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const fileTree = {};
|
|
80
|
+
Object.entries(schemaFiles).forEach(([filePath, statements]) => {
|
|
81
|
+
fileTree[filePath] = generate(t.program(statements)).code;
|
|
82
|
+
});
|
|
83
|
+
fileTree['index.ts'] = generate(t.program(indexFileStatements)).code;
|
|
84
|
+
return fileTree;
|
|
85
|
+
};
|
|
86
|
+
const toPascalCase = (str) => str
|
|
87
|
+
.replace(/[_-](\w)/g, (_, c) => c.toUpperCase())
|
|
88
|
+
.replace(/^\w/, (c) => c.toUpperCase());
|
|
89
|
+
const mapPostgresTypeToTSType = (typeId, isNotNull) => {
|
|
90
|
+
const optionalType = (type) => isNotNull ? type : t.tsUnionType([type, t.tsNullKeyword()]);
|
|
91
|
+
switch (typeId) {
|
|
92
|
+
case '20': // BIGINT
|
|
93
|
+
case '21': // SMALLINT
|
|
94
|
+
case '23': // INTEGER
|
|
95
|
+
case '1700': // NUMERIC
|
|
96
|
+
return optionalType(t.tsNumberKeyword());
|
|
97
|
+
case '25': // TEXT
|
|
98
|
+
case '1043': // VARCHAR
|
|
99
|
+
return optionalType(t.tsStringKeyword());
|
|
100
|
+
case '1114': // TIMESTAMP
|
|
101
|
+
case '1184': // TIMESTAMPTZ
|
|
102
|
+
return optionalType(t.tsTypeReference(t.identifier('Timestamp')));
|
|
103
|
+
case '2950': // UUID
|
|
104
|
+
return optionalType(t.tsTypeReference(t.identifier('UUID')));
|
|
105
|
+
case '16': // BOOLEAN
|
|
106
|
+
return optionalType(t.tsBooleanKeyword());
|
|
107
|
+
default:
|
|
108
|
+
return optionalType(t.tsAnyKeyword());
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
// Map Postgres type OIDs to type names for imports
|
|
112
|
+
const mapPostgresTypeToIdentifier = (typeId) => {
|
|
113
|
+
switch (typeId) {
|
|
114
|
+
case '1114': // TIMESTAMP
|
|
115
|
+
case '1184': // TIMESTAMPTZ
|
|
116
|
+
return 'Timestamp';
|
|
117
|
+
case '2950': // UUID
|
|
118
|
+
return 'UUID';
|
|
119
|
+
default:
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import generate from '@babel/generator';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
export const generateCode = (databaseObjects, options) => {
|
|
4
|
+
const { includeTimestamps, includeUUID } = options;
|
|
5
|
+
const namespaces = {};
|
|
6
|
+
// Organize objects by namespace
|
|
7
|
+
databaseObjects.forEach((obj) => {
|
|
8
|
+
if (obj.kind === 'namespace') {
|
|
9
|
+
namespaces[obj.name] = [];
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
// Generate interfaces for tables (Class objects with kind 'r')
|
|
13
|
+
databaseObjects.forEach((obj) => {
|
|
14
|
+
if (obj.kind === 'class' && obj.classKind === 'r') {
|
|
15
|
+
const namespace = obj.namespaceName;
|
|
16
|
+
const fields = [];
|
|
17
|
+
// Find attributes for the class
|
|
18
|
+
databaseObjects.forEach((attr) => {
|
|
19
|
+
if (attr.kind === 'attribute' && attr.classId === obj.id) {
|
|
20
|
+
fields.push(t.tsPropertySignature(t.identifier(attr.name), t.tsTypeAnnotation(mapPostgresTypeToTSType(attr.typeId, attr.isNotNull))));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// Create the interface declaration
|
|
24
|
+
const interfaceDeclaration = t.tsInterfaceDeclaration(t.identifier(obj.name), null, [], t.tsInterfaceBody(fields));
|
|
25
|
+
// Add to the namespace
|
|
26
|
+
if (namespaces[namespace]) {
|
|
27
|
+
namespaces[namespace].push(interfaceDeclaration);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Generate the final AST
|
|
32
|
+
const programBody = [];
|
|
33
|
+
if (includeTimestamps) {
|
|
34
|
+
programBody.push(t.tsTypeAliasDeclaration(t.identifier('Timestamp'), null, t.tsStringKeyword()));
|
|
35
|
+
}
|
|
36
|
+
if (includeUUID) {
|
|
37
|
+
programBody.push(t.tsTypeAliasDeclaration(t.identifier('UUID'), null, t.tsStringKeyword()));
|
|
38
|
+
}
|
|
39
|
+
Object.keys(namespaces).forEach((namespace) => {
|
|
40
|
+
const namespaceDeclaration = t.tsModuleDeclaration(t.identifier(namespace), t.tsModuleBlock(namespaces[namespace]));
|
|
41
|
+
namespaceDeclaration.declare = true;
|
|
42
|
+
programBody.push(namespaceDeclaration);
|
|
43
|
+
});
|
|
44
|
+
const program = t.program(programBody);
|
|
45
|
+
// Generate the code
|
|
46
|
+
return generate(program).code;
|
|
47
|
+
};
|
|
48
|
+
// Map Postgres type OIDs to TypeScript types
|
|
49
|
+
const mapPostgresTypeToTSType = (typeId, isNotNull) => {
|
|
50
|
+
const optionalType = (type) => isNotNull ? type : t.tsUnionType([type, t.tsNullKeyword()]);
|
|
51
|
+
switch (typeId) {
|
|
52
|
+
case '20': // BIGINT
|
|
53
|
+
case '21': // SMALLINT
|
|
54
|
+
case '23': // INTEGER
|
|
55
|
+
case '1700': // NUMERIC
|
|
56
|
+
return optionalType(t.tsNumberKeyword());
|
|
57
|
+
case '25': // TEXT
|
|
58
|
+
case '1043': // VARCHAR
|
|
59
|
+
return optionalType(t.tsStringKeyword());
|
|
60
|
+
case '1114': // TIMESTAMP
|
|
61
|
+
case '1184': // TIMESTAMPTZ
|
|
62
|
+
return optionalType(t.tsTypeReference(t.identifier('Timestamp')));
|
|
63
|
+
case '2950': // UUID
|
|
64
|
+
return optionalType(t.tsTypeReference(t.identifier('UUID')));
|
|
65
|
+
case '16': // BOOLEAN
|
|
66
|
+
return optionalType(t.tsBooleanKeyword());
|
|
67
|
+
default:
|
|
68
|
+
return optionalType(t.tsAnyKeyword());
|
|
69
|
+
}
|
|
70
|
+
};
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { generateCodeTree } from './codegen/codegen';
|
|
4
|
+
import getIntrospectionRows from './introspect';
|
|
5
|
+
import { Logger } from '@launchql/server-utils';
|
|
6
|
+
import { getPgEnvOptions } from '@launchql/types';
|
|
7
|
+
import { getRootPgPool } from '@launchql/server-utils';
|
|
8
|
+
const log = new Logger('codegen');
|
|
9
|
+
(async () => {
|
|
10
|
+
const env = getPgEnvOptions();
|
|
11
|
+
const pool = getRootPgPool(env);
|
|
12
|
+
const options = {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
client: pool, // hope this is ok?
|
|
15
|
+
introspectionOptions: {
|
|
16
|
+
pgLegacyFunctionsOnly: false,
|
|
17
|
+
pgIgnoreRBAC: true,
|
|
18
|
+
},
|
|
19
|
+
namespacesToIntrospect: ['collections_public'],
|
|
20
|
+
includeExtensions: false,
|
|
21
|
+
};
|
|
22
|
+
const outputDirectory = path.join(__dirname, '../../../__fixtures__/output');
|
|
23
|
+
try {
|
|
24
|
+
// Clean the output directory
|
|
25
|
+
await fs.rm(outputDirectory, { recursive: true, force: true });
|
|
26
|
+
log.info(`Cleaned output directory: ${outputDirectory}`);
|
|
27
|
+
// Fetch introspection rows
|
|
28
|
+
const rows = await getIntrospectionRows(options);
|
|
29
|
+
log.info('Introspection Rows Fetched:', rows);
|
|
30
|
+
// Generate TypeScript code
|
|
31
|
+
const codegenOptions = {
|
|
32
|
+
includeTimestamps: true,
|
|
33
|
+
includeUUID: true,
|
|
34
|
+
};
|
|
35
|
+
const generatedCode = generateCodeTree(rows, codegenOptions);
|
|
36
|
+
log.info('Generated TypeScript Code Tree:', generatedCode);
|
|
37
|
+
// Write the generated code to files
|
|
38
|
+
await writeGeneratedFiles(outputDirectory, generatedCode);
|
|
39
|
+
log.info(`Generated files written to ${outputDirectory}`);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
log.error('Failed to fetch introspection rows or generate code:', error);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
/**
|
|
46
|
+
* Writes the generated code files to the specified directory.
|
|
47
|
+
*
|
|
48
|
+
* @param outputDir The base directory to write the files.
|
|
49
|
+
* @param fileTree The file tree containing file paths and content.
|
|
50
|
+
*/
|
|
51
|
+
const writeGeneratedFiles = async (outputDir, fileTree) => {
|
|
52
|
+
try {
|
|
53
|
+
// Ensure the output directory exists
|
|
54
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
55
|
+
// Write each file to its corresponding path
|
|
56
|
+
for (const [filePath, content] of Object.entries(fileTree)) {
|
|
57
|
+
const fullPath = path.join(outputDir, filePath);
|
|
58
|
+
// Ensure the directory for the file exists
|
|
59
|
+
const dirName = path.dirname(fullPath);
|
|
60
|
+
await fs.mkdir(dirName, { recursive: true });
|
|
61
|
+
// Write the file content
|
|
62
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
log.error(`Failed to write files to ${outputDir}:`, error);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { makeIntrospectionQuery } from './query';
|
|
2
|
+
import { Logger } from '@launchql/server-utils';
|
|
3
|
+
const log = new Logger('codegen');
|
|
4
|
+
export const getIntrospectionRows = async (options) => {
|
|
5
|
+
const { client, introspectionOptions, namespacesToIntrospect, includeExtensions = false, } = options;
|
|
6
|
+
try {
|
|
7
|
+
// Query for server version in integer format
|
|
8
|
+
const res = await client.query('SHOW server_version_num');
|
|
9
|
+
const serverVersionNum = parseInt(res.rows[0].server_version_num, 10);
|
|
10
|
+
// Generate the introspection query
|
|
11
|
+
const introspectionQuery = makeIntrospectionQuery(serverVersionNum, introspectionOptions);
|
|
12
|
+
// Execute the introspection query
|
|
13
|
+
const queryResult = await client.query(introspectionQuery, [
|
|
14
|
+
namespacesToIntrospect,
|
|
15
|
+
includeExtensions,
|
|
16
|
+
]);
|
|
17
|
+
// Map the result rows to the `DatabaseObject` type
|
|
18
|
+
const rows = queryResult.rows.map((result) => result.object);
|
|
19
|
+
return rows;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
log.error('Error during introspection:', error);
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
export default getIntrospectionRows;
|