mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
Complete table types generation
This commit is contained in:
parent
17b6f98833
commit
7709f3cb8a
7 changed files with 117 additions and 43 deletions
|
@ -5,10 +5,12 @@
|
|||
"repository": "https://github.com/logto-io/schemas",
|
||||
"author": "Logto",
|
||||
"license": "UNLICENSED",
|
||||
"files": ["lib"],
|
||||
"scripts": {
|
||||
"generate": "ts-node src/gen/index.ts",
|
||||
"build": "rm -rf lib/ && tsc --p tsconfig.build.json",
|
||||
"lint": "xo src/"
|
||||
"build": "yarn generate && rm -rf lib/ && tsc --p tsconfig.build.json",
|
||||
"lint": "xo src/",
|
||||
"prepare": "yarn build"
|
||||
},
|
||||
"engines": {
|
||||
"node": "14",
|
||||
|
@ -16,6 +18,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "14",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"camelcase": "^6.2.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.3.4",
|
||||
"xo": "0.39.1"
|
||||
|
|
3
packages/schemas/src/db-entries/index.ts
Normal file
3
packages/schemas/src/db-entries/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
export * from './oidc-model-instance';
|
11
packages/schemas/src/db-entries/oidc-model-instance.ts
Normal file
11
packages/schemas/src/db-entries/oidc-model-instance.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
export type OidcModelInstanceDBEntry = {
|
||||
modelName: string;
|
||||
id: string;
|
||||
payload: Record<string, unknown>;
|
||||
expiresAt: number;
|
||||
userCode?: string;
|
||||
uid?: string;
|
||||
grantId?: string;
|
||||
};
|
|
@ -1,8 +1,16 @@
|
|||
import assert from 'assert';
|
||||
import camelcase from 'camelcase';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import pluralize from 'pluralize';
|
||||
|
||||
import { findFirstParentheses, getType, normalizeWhitespaces, removeParentheses } from './utils';
|
||||
import {
|
||||
conditionalString,
|
||||
findFirstParentheses,
|
||||
getType,
|
||||
normalizeWhitespaces,
|
||||
removeParentheses,
|
||||
} from './utils';
|
||||
|
||||
type Field = {
|
||||
name: string;
|
||||
|
@ -21,47 +29,85 @@ const dir = 'tables';
|
|||
const generate = async () => {
|
||||
const files = await fs.readdir(dir);
|
||||
const generated = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
return (await fs.readFile(path.join(dir, file), { encoding: 'utf-8' }))
|
||||
.split(';')
|
||||
.map((value) => normalizeWhitespaces(value).toLowerCase())
|
||||
.filter((value) => value.startsWith('create table'))
|
||||
.map((value) => findFirstParentheses(value))
|
||||
.filter((value): value is NonNullable<typeof value> => Boolean(value))
|
||||
.map<Table>(({ prefix, body }) => {
|
||||
const name = normalizeWhitespaces(prefix).split(' ')[2];
|
||||
assert(name, 'Missing table name: ' + prefix);
|
||||
files
|
||||
.filter((file) => file.endsWith('.sql'))
|
||||
.map<Promise<[string, Table[]]>>(async (file) => [
|
||||
file,
|
||||
(
|
||||
await fs.readFile(path.join(dir, file), { encoding: 'utf-8' })
|
||||
)
|
||||
.split(';')
|
||||
.map((value) => normalizeWhitespaces(value).toLowerCase())
|
||||
.filter((value) => value.startsWith('create table'))
|
||||
.map((value) => findFirstParentheses(value))
|
||||
.filter((value): value is NonNullable<typeof value> => Boolean(value))
|
||||
.map<Table>(({ prefix, body }) => {
|
||||
const name = normalizeWhitespaces(prefix).split(' ')[2];
|
||||
assert(name, 'Missing table name: ' + prefix);
|
||||
|
||||
const fields = removeParentheses(body)
|
||||
.split(',')
|
||||
.map((value) => normalizeWhitespaces(value))
|
||||
.filter((value) =>
|
||||
['primary', 'foreign', 'unique', 'exclude', 'check'].every(
|
||||
(constraint) => !value.startsWith(constraint)
|
||||
const fields = removeParentheses(body)
|
||||
.split(',')
|
||||
.map((value) => normalizeWhitespaces(value))
|
||||
.filter((value) =>
|
||||
['primary', 'foreign', 'unique', 'exclude', 'check'].every(
|
||||
(constraint) => !value.startsWith(constraint)
|
||||
)
|
||||
)
|
||||
)
|
||||
.map<Field>((value) => {
|
||||
const [name, type, ...rest] = value.split(' ');
|
||||
assert(name && type, 'Missing column name or type: ' + value);
|
||||
.map<Field>((value) => {
|
||||
const [name, type, ...rest] = value.split(' ');
|
||||
assert(name && type, 'Missing column name or type: ' + value);
|
||||
|
||||
const restJoined = rest.join(' ');
|
||||
// CAUTION: Only works for single dimension arrays
|
||||
const isArray = Boolean(/\[.*]/.test(type)) || restJoined.includes('array');
|
||||
const required = restJoined.includes('not null');
|
||||
const restJoined = rest.join(' ');
|
||||
// CAUTION: Only works for single dimension arrays
|
||||
const isArray = Boolean(/\[.*]/.test(type)) || restJoined.includes('array');
|
||||
const required = restJoined.includes('not null');
|
||||
|
||||
return {
|
||||
name,
|
||||
type: getType(type),
|
||||
isArray,
|
||||
required,
|
||||
};
|
||||
});
|
||||
return { name, fields };
|
||||
});
|
||||
return {
|
||||
name,
|
||||
type: getType(type),
|
||||
isArray,
|
||||
required,
|
||||
};
|
||||
});
|
||||
return { name, fields };
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
const generatedDir = 'src/db-entries';
|
||||
const header = '// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n\n';
|
||||
const getOutputFileName = (file: string) => pluralize(file.slice(0, -4).replace(/_/g, '-'), 1);
|
||||
|
||||
await fs.rmdir(generatedDir, { recursive: true });
|
||||
await fs.mkdir(generatedDir, { recursive: true });
|
||||
await Promise.all(
|
||||
generated.map(async ([file, tables]) => {
|
||||
const content =
|
||||
header +
|
||||
tables
|
||||
.map(({ name, fields }) =>
|
||||
[
|
||||
`export type ${pluralize(camelcase(name, { pascalCase: true }), 1)}DBEntry = {`,
|
||||
...fields.map(
|
||||
({ name, type, isArray, required }) =>
|
||||
` ${camelcase(name)}${conditionalString(
|
||||
!required && '?'
|
||||
)}: ${type}${conditionalString(isArray && '[]')};`
|
||||
),
|
||||
'};',
|
||||
].join('\n')
|
||||
)
|
||||
.join('\n') +
|
||||
'\n';
|
||||
await fs.writeFile(path.join(generatedDir, getOutputFileName(file) + '.ts'), content);
|
||||
})
|
||||
);
|
||||
const tables = generated.flat();
|
||||
console.log(tables);
|
||||
await fs.writeFile(
|
||||
path.join(generatedDir, 'index.ts'),
|
||||
header +
|
||||
generated.map(([file]) => `export * from './${getOutputFileName(file)}';`).join('\n') +
|
||||
'\n'
|
||||
);
|
||||
};
|
||||
|
||||
void generate();
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { Optional } from '../global';
|
||||
|
||||
export type Falsy = 0 | undefined | null | false | '';
|
||||
|
||||
export const conditionalString = (value: string | Falsy): string => (value ? value : '');
|
||||
|
||||
export const normalizeWhitespaces = (string: string): string => string.replace(/\s+/g, ' ').trim();
|
||||
|
||||
const getCountDelta = (value: string): number => {
|
||||
|
@ -80,7 +84,9 @@ const getRawType = (value: string): string => {
|
|||
|
||||
// Reference: https://github.com/SweetIQ/schemats/blob/7c3d3e16b5d507b4d9bd246794e7463b05d20e75/src/schemaPostgres.ts
|
||||
// eslint-disable-next-line complexity
|
||||
export const getType = (value: string): 'string' | 'number' | 'boolean' | 'Object' | 'Date' => {
|
||||
export const getType = (
|
||||
value: string
|
||||
): 'string' | 'number' | 'boolean' | 'Record<string, unknown>' | 'Date' => {
|
||||
switch (getRawType(value)) {
|
||||
case 'bpchar':
|
||||
case 'char':
|
||||
|
@ -109,7 +115,7 @@ export const getType = (value: string): 'string' | 'number' | 'boolean' | 'Objec
|
|||
return 'boolean';
|
||||
case 'json':
|
||||
case 'jsonb':
|
||||
return 'Object';
|
||||
return 'Record<string, unknown>';
|
||||
case 'date':
|
||||
case 'timestamp':
|
||||
case 'timestamptz':
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
export type A = {
|
||||
foo: string;
|
||||
};
|
||||
export * from './db-entries';
|
||||
|
|
|
@ -359,6 +359,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||
|
||||
"@types/pluralize@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c"
|
||||
integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^4.22.0":
|
||||
version "4.28.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz#1a66f03b264844387beb7dc85e1f1d403bd1803f"
|
||||
|
|
Loading…
Reference in a new issue