0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

refactor(schemas): support field JSDoc comments

This commit is contained in:
Gao Sun 2023-09-14 22:37:55 +08:00
parent f702cc24a3
commit d0a44e93f8
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
5 changed files with 39 additions and 8 deletions

View file

@ -17,6 +17,7 @@ import {
parseType, parseType,
removeUnrecognizedComments, removeUnrecognizedComments,
splitTableFieldDefinitions, splitTableFieldDefinitions,
stripLeadingJsDocComments as stripComments,
} from './utils.js'; } from './utils.js';
const directory = 'tables'; const directory = 'tables';
@ -61,7 +62,10 @@ const generate = async () => {
.map((value) => normalizeWhitespaces(value)) .map((value) => normalizeWhitespaces(value))
.filter((value) => .filter((value) =>
constrainedKeywords.every( constrainedKeywords.every(
(constraint) => !value.toLowerCase().startsWith(constraint + ' ') (constraint) =>
!stripComments(value)
.toLowerCase()
.startsWith(constraint + ' ')
) )
) )
.map<Field>((value) => parseType(value)); .map<Field>((value) => parseType(value));

View file

@ -16,7 +16,8 @@ export const generateSchema = ({ name, fields }: TableWithType) => {
return [ return [
`export type ${databaseEntryType} = {`, `export type ${databaseEntryType} = {`,
...fields.map( ...fields.map(
({ name, type, isArray, nullable, hasDefaultValue }) => ({ name, comments, type, isArray, nullable, hasDefaultValue }) =>
conditionalString(comments && ` /** ${comments} */\n`) +
` ${camelcase(name)}${conditionalString( ` ${camelcase(name)}${conditionalString(
(nullable || hasDefaultValue || name === tenantId) && '?' (nullable || hasDefaultValue || name === tenantId) && '?'
)}: ${type}${conditionalString(isArray && '[]')}${conditionalString( )}: ${type}${conditionalString(isArray && '[]')}${conditionalString(
@ -27,7 +28,8 @@ export const generateSchema = ({ name, fields }: TableWithType) => {
'', '',
`export type ${modelName} = {`, `export type ${modelName} = {`,
...fields.map( ...fields.map(
({ name, type, isArray, nullable, hasDefaultValue }) => ({ name, comments, type, isArray, nullable, hasDefaultValue }) =>
conditionalString(comments && ` /** ${comments} */\n`) +
` ${camelcase(name)}: ${type}${conditionalString(isArray && '[]')}${ ` ${camelcase(name)}: ${type}${conditionalString(isArray && '[]')}${
nullable && !hasDefaultValue ? ' | null' : '' nullable && !hasDefaultValue ? ' | null' : ''
};` };`

View file

@ -1,5 +1,7 @@
export type Field = { export type Field = {
name: string; name: string;
/** The JSDoc comment for the field. */
comments?: string;
type?: string; type?: string;
customType?: string; customType?: string;
tsType?: string; tsType?: string;

View file

@ -6,6 +6,14 @@ import type { Field } from './types.js';
export const normalizeWhitespaces = (string: string): string => export const normalizeWhitespaces = (string: string): string =>
string.replaceAll(/\s+/g, ' ').trim(); string.replaceAll(/\s+/g, ' ').trim();
// eslint-disable-next-line unicorn/prevent-abbreviations -- JSDoc is a term
export const stripLeadingJsDocComments = (string: string): string =>
string.replace(/^\s*\/\*\*[^*]+\*\//, '').trim();
// eslint-disable-next-line unicorn/prevent-abbreviations -- JSDoc is a term
const getLeadingJsDocComments = (string: string): Optional<string> =>
/^\s*\/\*\*([^*]+)\*\//g.exec(string)?.[1]?.trim();
// Remove all comments not start with @ // Remove all comments not start with @
export const removeUnrecognizedComments = (string: string): string => export const removeUnrecognizedComments = (string: string): string =>
string.replaceAll(/\/\*(?!\s@)[^*]+\*\//g, ''); string.replaceAll(/\/\*(?!\s@)[^*]+\*\//g, '');
@ -73,7 +81,12 @@ export const splitTableFieldDefinitions = (value: string) =>
({ result, count: previousCount }, current) => { ({ result, count: previousCount }, current) => {
const count = previousCount + getCountDelta(current); const count = previousCount + getCountDelta(current);
if (count === 0 && current === ',') { if (
count === 0 &&
current === ',' &&
// Ignore commas in JSDoc comments
!stripLeadingJsDocComments(result.at(-1) ?? '').includes('/**')
) {
return { return {
result: [...result, ''], result: [...result, ''],
count, count,
@ -169,9 +182,12 @@ const parseStringMaxLength = (rawType: string) => {
}; };
export const parseType = (tableFieldDefinition: string): Field => { export const parseType = (tableFieldDefinition: string): Field => {
const [nameRaw, typeRaw, ...rest] = tableFieldDefinition.split(' '); const normalized = stripLeadingJsDocComments(tableFieldDefinition);
const comments = getLeadingJsDocComments(tableFieldDefinition);
assert(nameRaw && typeRaw, new Error('Missing field name or type: ' + tableFieldDefinition)); const [nameRaw, typeRaw, ...rest] = normalized.split(' ');
assert(nameRaw && typeRaw, new Error('Missing field name or type: ' + normalized));
const name = nameRaw.toLowerCase(); const name = nameRaw.toLowerCase();
const type = typeRaw.toLowerCase(); const type = typeRaw.toLowerCase();
@ -198,6 +214,7 @@ export const parseType = (tableFieldDefinition: string): Field => {
return { return {
name, name,
comments,
type: primitiveType, type: primitiveType,
isString, isString,
isArray, isArray,

View file

@ -4,16 +4,22 @@ create table sentinel_activities (
tenant_id varchar(21) not null tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade, references tenants (id) on update cascade on delete cascade,
id varchar(21) not null, id varchar(21) not null,
/** The subject (actor) that performed the action. */
subject_type varchar(32) /* @use SentinelActivitySubjectType */ not null, subject_type varchar(32) /* @use SentinelActivitySubjectType */ not null,
/** The target that the action was performed on. */
target_type varchar(32) /* @use SentinelActivityTargetType */ not null, target_type varchar(32) /* @use SentinelActivityTargetType */ not null,
/** The target identifier. */
target_id varchar(21) not null target_id varchar(21) not null
references users (id) on update cascade on delete cascade, references users (id) on update cascade on delete cascade,
/** The related log id if any. */
log_id varchar(21) log_id varchar(21)
references logs (id) on update cascade on delete cascade, references logs (id) on update cascade on delete cascade,
/** The action name that was performed. */
action varchar(64) /* @use SentinelActivityAction */ not null, action varchar(64) /* @use SentinelActivityAction */ not null,
/** If the action was successful or not. */
result sentinel_activity_result not null, result sentinel_activity_result not null,
payload jsonb /* @use LogContextPayload */ not null, /** Additional payload data if any. */
payload jsonb /* @use SentinelActivityPayload */ not null,
created_at timestamptz not null default(now()), created_at timestamptz not null default(now()),
primary key (id) primary key (id)
); );
ex