From d0a44e93f8a1b36b38e16d0e43652776b88fbbe1 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Thu, 14 Sep 2023 22:37:55 +0800 Subject: [PATCH] refactor(schemas): support field JSDoc comments --- packages/schemas/src/gen/index.ts | 6 ++++- packages/schemas/src/gen/schema.ts | 6 +++-- packages/schemas/src/gen/types.ts | 2 ++ packages/schemas/src/gen/utils.ts | 23 ++++++++++++++++--- .../schemas/tables/sentinel_activities.sql | 10 ++++++-- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/schemas/src/gen/index.ts b/packages/schemas/src/gen/index.ts index f2abccca6..d7d21c5a8 100644 --- a/packages/schemas/src/gen/index.ts +++ b/packages/schemas/src/gen/index.ts @@ -17,6 +17,7 @@ import { parseType, removeUnrecognizedComments, splitTableFieldDefinitions, + stripLeadingJsDocComments as stripComments, } from './utils.js'; const directory = 'tables'; @@ -61,7 +62,10 @@ const generate = async () => { .map((value) => normalizeWhitespaces(value)) .filter((value) => constrainedKeywords.every( - (constraint) => !value.toLowerCase().startsWith(constraint + ' ') + (constraint) => + !stripComments(value) + .toLowerCase() + .startsWith(constraint + ' ') ) ) .map((value) => parseType(value)); diff --git a/packages/schemas/src/gen/schema.ts b/packages/schemas/src/gen/schema.ts index 9171fdb56..5c55b72c0 100644 --- a/packages/schemas/src/gen/schema.ts +++ b/packages/schemas/src/gen/schema.ts @@ -16,7 +16,8 @@ export const generateSchema = ({ name, fields }: TableWithType) => { return [ `export type ${databaseEntryType} = {`, ...fields.map( - ({ name, type, isArray, nullable, hasDefaultValue }) => + ({ name, comments, type, isArray, nullable, hasDefaultValue }) => + conditionalString(comments && ` /** ${comments} */\n`) + ` ${camelcase(name)}${conditionalString( (nullable || hasDefaultValue || name === tenantId) && '?' )}: ${type}${conditionalString(isArray && '[]')}${conditionalString( @@ -27,7 +28,8 @@ export const generateSchema = ({ name, fields }: TableWithType) => { '', `export type ${modelName} = {`, ...fields.map( - ({ name, type, isArray, nullable, hasDefaultValue }) => + ({ name, comments, type, isArray, nullable, hasDefaultValue }) => + conditionalString(comments && ` /** ${comments} */\n`) + ` ${camelcase(name)}: ${type}${conditionalString(isArray && '[]')}${ nullable && !hasDefaultValue ? ' | null' : '' };` diff --git a/packages/schemas/src/gen/types.ts b/packages/schemas/src/gen/types.ts index 33763dcef..5f751562b 100644 --- a/packages/schemas/src/gen/types.ts +++ b/packages/schemas/src/gen/types.ts @@ -1,5 +1,7 @@ export type Field = { name: string; + /** The JSDoc comment for the field. */ + comments?: string; type?: string; customType?: string; tsType?: string; diff --git a/packages/schemas/src/gen/utils.ts b/packages/schemas/src/gen/utils.ts index 8cabd502e..0192212e0 100644 --- a/packages/schemas/src/gen/utils.ts +++ b/packages/schemas/src/gen/utils.ts @@ -6,6 +6,14 @@ import type { Field } from './types.js'; export const normalizeWhitespaces = (string: string): string => 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 => + /^\s*\/\*\*([^*]+)\*\//g.exec(string)?.[1]?.trim(); + // Remove all comments not start with @ export const removeUnrecognizedComments = (string: string): string => string.replaceAll(/\/\*(?!\s@)[^*]+\*\//g, ''); @@ -73,7 +81,12 @@ export const splitTableFieldDefinitions = (value: string) => ({ result, count: previousCount }, 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 { result: [...result, ''], count, @@ -169,9 +182,12 @@ const parseStringMaxLength = (rawType: string) => { }; 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 type = typeRaw.toLowerCase(); @@ -198,6 +214,7 @@ export const parseType = (tableFieldDefinition: string): Field => { return { name, + comments, type: primitiveType, isString, isArray, diff --git a/packages/schemas/tables/sentinel_activities.sql b/packages/schemas/tables/sentinel_activities.sql index 84e84fb58..f4fbab197 100644 --- a/packages/schemas/tables/sentinel_activities.sql +++ b/packages/schemas/tables/sentinel_activities.sql @@ -4,16 +4,22 @@ create table sentinel_activities ( tenant_id varchar(21) not null references tenants (id) on update cascade on delete cascade, id varchar(21) not null, + /** The subject (actor) that performed the action. */ subject_type varchar(32) /* @use SentinelActivitySubjectType */ not null, + /** The target that the action was performed on. */ target_type varchar(32) /* @use SentinelActivityTargetType */ not null, + /** The target identifier. */ target_id varchar(21) not null references users (id) on update cascade on delete cascade, + /** The related log id if any. */ log_id varchar(21) references logs (id) on update cascade on delete cascade, + /** The action name that was performed. */ action varchar(64) /* @use SentinelActivityAction */ not null, + /** If the action was successful or not. */ 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()), primary key (id) ); -ex