From 7709f3cb8a65bc8e6769fe765065945e2d6f6e91 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Wed, 23 Jun 2021 21:23:17 +0800 Subject: [PATCH] Complete table types generation --- packages/schemas/package.json | 9 +- packages/schemas/src/db-entries/index.ts | 3 + .../src/db-entries/oidc-model-instance.ts | 11 ++ packages/schemas/src/gen/index.ts | 118 ++++++++++++------ packages/schemas/src/gen/utils.ts | 10 +- packages/schemas/src/index.ts | 4 +- packages/schemas/yarn.lock | 5 + 7 files changed, 117 insertions(+), 43 deletions(-) create mode 100644 packages/schemas/src/db-entries/index.ts create mode 100644 packages/schemas/src/db-entries/oidc-model-instance.ts diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 5731d66ab..804652301 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -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" diff --git a/packages/schemas/src/db-entries/index.ts b/packages/schemas/src/db-entries/index.ts new file mode 100644 index 000000000..aa7d188fc --- /dev/null +++ b/packages/schemas/src/db-entries/index.ts @@ -0,0 +1,3 @@ +// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + +export * from './oidc-model-instance'; diff --git a/packages/schemas/src/db-entries/oidc-model-instance.ts b/packages/schemas/src/db-entries/oidc-model-instance.ts new file mode 100644 index 000000000..646b71836 --- /dev/null +++ b/packages/schemas/src/db-entries/oidc-model-instance.ts @@ -0,0 +1,11 @@ +// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + +export type OidcModelInstanceDBEntry = { + modelName: string; + id: string; + payload: Record; + expiresAt: number; + userCode?: string; + uid?: string; + grantId?: string; +}; diff --git a/packages/schemas/src/gen/index.ts b/packages/schemas/src/gen/index.ts index cb28c7fa1..8547d5484 100644 --- a/packages/schemas/src/gen/index.ts +++ b/packages/schemas/src/gen/index.ts @@ -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 => Boolean(value)) - .map(({ prefix, body }) => { - const name = normalizeWhitespaces(prefix).split(' ')[2]; - assert(name, 'Missing table name: ' + prefix); + files + .filter((file) => file.endsWith('.sql')) + .map>(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 => Boolean(value)) + .map
(({ 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((value) => { - const [name, type, ...rest] = value.split(' '); - assert(name && type, 'Missing column name or type: ' + value); + .map((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(); diff --git a/packages/schemas/src/gen/utils.ts b/packages/schemas/src/gen/utils.ts index 91e50ed5f..ac3d41e13 100644 --- a/packages/schemas/src/gen/utils.ts +++ b/packages/schemas/src/gen/utils.ts @@ -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' | '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'; case 'date': case 'timestamp': case 'timestamptz': diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts index 06fadb519..a3e3629b1 100644 --- a/packages/schemas/src/index.ts +++ b/packages/schemas/src/index.ts @@ -1,3 +1 @@ -export type A = { - foo: string; -}; +export * from './db-entries'; diff --git a/packages/schemas/yarn.lock b/packages/schemas/yarn.lock index 7c9d137d9..1ae3a3aac 100644 --- a/packages/schemas/yarn.lock +++ b/packages/schemas/yarn.lock @@ -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"