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:
parent
f3ec4c6734
commit
059a06ea7e
4 changed files with 54 additions and 14 deletions
|
@ -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
|
||||
|
|
|
@ -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' : ''
|
||||
};`
|
||||
|
|
|
@ -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[];
|
||||
};
|
||||
|
||||
|
|
|
@ -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 @
|
||||
|
|
Loading…
Reference in a new issue