0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(schemas): support table comments

This commit is contained in:
Gao Sun 2023-10-03 14:15:01 +08:00
parent f3ec4c6734
commit 059a06ea7e
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
4 changed files with 54 additions and 14 deletions

View file

@ -12,12 +12,15 @@ import pluralize from 'pluralize';
import { generateSchema } from './schema.js';
import type { FileData, Table, Field, Type, GeneratedType, TableWithType } from './types.js';
import {
type ParenthesesMatch,
findFirstParentheses,
normalizeWhitespaces,
parseType,
removeUnrecognizedComments,
splitTableFieldDefinitions,
stripLeadingJsDocComments as stripComments,
stripLeadingJsDocComments as stripLeadingJsDocumentComments,
getLeadingJsDocComments as getLeadingJsDocumentComments,
} from './utils.js';
const directory = 'tables';
@ -50,14 +53,18 @@ const generate = async () => {
// Parse Table statements
const tables = statements
.filter((value) => value.toLowerCase().startsWith('create table'))
.map((value) => findFirstParentheses(value))
// eslint-disable-next-line unicorn/prefer-native-coercion-functions
.filter((value): value is NonNullable<typeof value> => Boolean(value))
.map<Table>(({ prefix, body }) => {
.filter((value) =>
stripLeadingJsDocumentComments(value).toLowerCase().startsWith('create table')
)
.map(
(value) => [findFirstParentheses(stripLeadingJsDocumentComments(value)), value] as const
)
.filter((value): value is NonNullable<[ParenthesesMatch, string]> => Boolean(value[0]))
.map<Table>(([{ prefix, body }, raw]) => {
const name = normalizeWhitespaces(prefix).split(' ')[2];
assert(name, 'Missing table name: ' + prefix);
const comments = getLeadingJsDocumentComments(raw);
const fields = splitTableFieldDefinitions(body)
.map((value) => normalizeWhitespaces(value))
.filter((value) =>
@ -70,7 +77,7 @@ const generate = async () => {
)
.map<Field>((value) => parseType(value));
return { name, fields };
return { name, comments, fields };
});
// Parse enum statements

View file

@ -1,23 +1,54 @@
// LOG-88: Refactor '@logto/schemas' type gen
import { conditionalString } from '@silverhand/essentials';
import { condArray, condString, conditionalString } from '@silverhand/essentials';
import camelcase from 'camelcase';
import pluralize from 'pluralize';
import type { TableWithType } from './types.js';
const createTypeRemark = (originalType: string) => [
'',
'@remarks This is a type for database creation.',
'@see {@link ' + originalType + '} for the original type.',
];
// Tenant ID should be optional for create types since it'll be generated by the database trigger
const tenantId = 'tenant_id';
export const generateSchema = ({ name, fields }: TableWithType) => {
type PrintCommentsOptions = {
tabSize?: number;
newLine?: boolean;
};
const printComments = (
comments?: string | string[],
{ tabSize = 2, newLine = true }: PrintCommentsOptions = {}
) =>
condString(
comments &&
condArray<string>(
' '.repeat(tabSize),
Array.isArray(comments)
? ['/**', ...comments.map((comment) => ` * ${comment}`), ' */'].join(
'\n' + ' '.repeat(tabSize)
)
: `/** ${comments} */`,
newLine && '\n'
).join('')
);
export const generateSchema = ({ name, comments, fields }: TableWithType) => {
const modelName = pluralize(camelcase(name, { pascalCase: true }), 1);
const databaseEntryType = `Create${modelName}`;
return [
printComments([...condArray(comments), ...createTypeRemark(modelName)], {
tabSize: 0,
newLine: false,
}),
`export type ${databaseEntryType} = {`,
...fields.map(
({ name, comments, type, isArray, nullable, hasDefaultValue }) =>
conditionalString(comments && ` /**${comments}*/\n`) +
printComments(comments) +
` ${camelcase(name)}${conditionalString(
(nullable || hasDefaultValue || name === tenantId) && '?'
)}: ${type}${conditionalString(isArray && '[]')}${conditionalString(
@ -26,10 +57,11 @@ export const generateSchema = ({ name, fields }: TableWithType) => {
),
'};',
'',
printComments(comments, { tabSize: 0, newLine: false }),
`export type ${modelName} = {`,
...fields.map(
({ name, comments, type, isArray, nullable, hasDefaultValue }) =>
conditionalString(comments && ` /**${comments}*/\n`) +
printComments(comments) +
` ${camelcase(name)}: ${type}${conditionalString(isArray && '[]')}${
nullable && !hasDefaultValue ? ' | null' : ''
};`

View file

@ -26,11 +26,12 @@ export type GeneratedType = Type & {
export type Table = {
name: string;
/** The JSDoc comment for the table. */
comments?: string;
fields: Field[];
};
export type TableWithType = {
name: string;
export type TableWithType = Omit<Table, 'fields'> & {
fields: FieldWithType[];
};

View file

@ -7,14 +7,14 @@ export const normalizeWhitespaces = (string: string): string =>
string.replaceAll(/\s+/g, ' ').trim();
// eslint-disable-next-line unicorn/prevent-abbreviations -- JSDoc is a term
const leadingJsDocRegex = /^\s*\/\*\*([^*]*?)\*\//;
const leadingJsDocRegex = /^\s*\/\*\* *([^*]*?) *\*\//;
// eslint-disable-next-line unicorn/prevent-abbreviations -- JSDoc is a term
export const stripLeadingJsDocComments = (string: string): string =>
string.replace(leadingJsDocRegex, '').trim();
// eslint-disable-next-line unicorn/prevent-abbreviations -- JSDoc is a term
const getLeadingJsDocComments = (string: string): Optional<string> =>
export const getLeadingJsDocComments = (string: string): Optional<string> =>
leadingJsDocRegex.exec(string)?.[1];
// Remove all comments not start with @