UNPKG

erdia

Version:

CLI to generate mermaid.js ER diagram using TypeORM entity

4 lines 191 kB
{ "version": 3, "sources": ["../../src/index.ts", "../../src/common/getColumnHash.ts", "../../src/common/getDatabaseName.ts", "../../src/common/getEntityHash.ts", "../../src/common/getFileVersion.ts", "../../src/common/getIndexHash.ts", "../../src/common/getPackageName.ts", "../../src/configs/const-enum/CE_PROJECT_NAME_FROM.ts", "../../src/common/getProjectName.ts", "../../src/configs/const-enum/CE_DEFAULT_VALUE.ts", "../../src/configs/modules/getCwd.ts", "../../src/modules/files/getFindFile.ts", "../../src/modules/files/betterMkdir.ts", "../../src/modules/files/getOutputDirPath.ts", "../../src/common/getVersion.ts", "../../src/modules/containers/container.ts", "../../src/modules/containers/keys/SymbolDataSource.ts", "../../src/common/getMetadata.ts", "../../src/common/getPlainRelationType.ts", "../../src/common/getRelationHash.ts", "../../src/creators/applyPretter.ts", "../../src/configs/const-enum/CE_OUTPUT_COMPONENT.ts", "../../src/modules/containers/keys/SymbolTemplateRenderer.ts", "../../src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts", "../../src/creators/createHtml.ts", "../../src/creators/createImageHtml.ts", "../../src/creators/createMarkdown.ts", "../../src/creators/createPdfHtml.ts", "../../src/configs/const-enum/CE_OUTPUT_FORMAT.ts", "../../src/databases/const-enum/CE_RECORD_KIND.ts", "../../src/modules/getSlashEndRoutePath.ts", "../../src/creators/getRenderData.ts", "../../src/modules/getPuppeteerConfig.ts", "../../src/creators/writeToImage.ts", "../../src/creators/writeToPdf.ts", "../../src/databases/const-enum/CE_CHANGE_KIND.ts", "../../src/databases/compareDatabase.ts", "../../src/databases/flushDatabase.ts", "../../src/databases/openDatabase.ts", "../../src/databases/processDatabase.ts", "../../src/templates/TemplateRenderer.ts", "../../src/typeorm/loadDataSource.ts", "../../src/typeorm/getDataSource.ts", "../../src/cli/builders/buildOptionBuilder.ts", "../../src/cli/builders/outputOptionBuilder.ts", "../../src/cli/builders/commonOptionBuilder.ts", "../../src/configs/const-enum/CE_ENTITY_VERSION_FROM.ts", "../../src/configs/const-enum/CE_MERMAID_THEME.ts", "../../src/cli/builders/documentOptionBuilder.ts", "../../src/modules/containers/keys/SymbolDefaultTemplate.ts", "../../src/modules/containers/keys/SymbolLogger.ts", "../../src/modules/containers/keys/SymbolTemplate.ts", "../../src/modules/loggers/Logger.ts", "../../src/modules/loggers/createLogger.ts", "../../src/templates/modules/configTemplate.ts", "../../src/templates/modules/getTemplateModulePath.ts", "../../src/templates/modules/getTemplatePath.ts", "../../src/modules/files/getGlobFiles.ts", "../../src/modules/scopes/defaultExclude.ts", "../../src/templates/modules/getTemplate.ts", "../../src/templates/modules/getTemplates.ts", "../../src/templates/modules/loadTemplates.ts", "../../src/configs/const-enum/CE_COLUMN_ATTRIBUTE.ts", "../../src/creators/columns/getColumnWeight.ts", "../../src/typeorm/columns/getColumnAttributeKey.ts", "../../src/typeorm/columns/getIsNullable.ts", "../../src/typeorm/columns/getColumnType.ts", "../../src/typeorm/columns/getComment.ts", "../../src/typeorm/entities/getEntityName.ts", "../../src/typeorm/columns/getColumnRecord.ts", "../../src/typeorm/entities/getEntityRecord.ts", "../../src/typeorm/entities/getEntityRecords.ts", "../../src/typeorm/indices/getIndexRecord.ts", "../../src/typeorm/indices/getIndexRecords.ts", "../../src/typeorm/relations/dedupeManyToManyRelationRecord.ts", "../../src/typeorm/entities/getEntityPropertyName.ts", "../../src/typeorm/relations/getInverseRelationMetadata.ts", "../../src/typeorm/relations/getManyToManyJoinColumn.ts", "../../src/typeorm/relations/getManyToOneJoinColumn.ts", "../../src/typeorm/relations/getJoinColumn.ts", "../../src/typeorm/relations/getManyToManyEntityMetadata.ts", "../../src/typeorm/relations/getRelationRecord.ts", "../../src/typeorm/relations/getRelationRecords.ts", "../../src/modules/commands/building.ts", "../../src/cli/commands/buildDocumentCommandHandler.ts", "../../src/modules/commands/cleaning.ts", "../../src/cli/commands/cleanDocumentCommandHandler.ts", "../../src/configs/const-enum/CE_IMAGE_FORMAT.ts", "../../src/configs/modules/getAutoCompleteSource.ts", "../../src/modules/files/getTemplateDirPath.ts", "../../src/modules/commands/ejecting.ts", "../../src/configs/modules/getConfigContent.ts", "../../src/modules/commands/initializing.ts", "../../src/cli/commands/initConfigCommandHandler.ts", "../../src/cli/commands/templateEjectCommandHandler.ts", "../../src/configs/const-enum/CE_COMMAND_LIST.ts", "../../src/configs/modules/getConfigFilePath.ts", "../../src/configs/modules/preLoadConfig.ts"], "sourcesContent": ["export * from './common/getColumnHash';\nexport * from './common/getDatabaseName';\nexport * from './common/getEntityHash';\nexport * from './common/getFileVersion';\nexport * from './common/getIndexHash';\nexport * from './common/getMetadata';\nexport * from './common/getPackageName';\nexport * from './common/getPlainRelationType';\nexport * from './common/getProjectName';\nexport * from './common/getRelationHash';\nexport * from './common/getVersion';\nexport * from './creators/applyPretter';\nexport * from './creators/createHtml';\nexport * from './creators/createImageHtml';\nexport * from './creators/createMarkdown';\nexport * from './creators/createPdfHtml';\nexport * from './creators/getRenderData';\nexport * from './creators/writeToImage';\nexport * from './creators/writeToPdf';\nexport * from './databases/compareDatabase';\nexport * from './databases/flushDatabase';\nexport * from './databases/openDatabase';\nexport * from './databases/processDatabase';\nexport * from './modules/getPuppeteerConfig';\nexport * from './modules/getSlashEndRoutePath';\nexport * from './templates/TemplateRenderer';\nexport * from './typeorm/getDataSource';\nexport * from './typeorm/loadDataSource';\nexport * from './cli/builders/buildOptionBuilder';\nexport * from './cli/builders/commonOptionBuilder';\nexport * from './cli/builders/documentOptionBuilder';\nexport * from './cli/builders/outputOptionBuilder';\nexport * from './cli/commands/buildDocumentCommandHandler';\nexport * from './cli/commands/cleanDocumentCommandHandler';\nexport * from './cli/commands/initConfigCommandHandler';\nexport * from './cli/commands/templateEjectCommandHandler';\nexport * from './configs/const-enum/CE_COLUMN_ATTRIBUTE';\nexport * from './configs/const-enum/CE_COMMAND_LIST';\nexport * from './configs/const-enum/CE_DEFAULT_VALUE';\nexport * from './configs/const-enum/CE_ENTITY_VERSION_FROM';\nexport * from './configs/const-enum/CE_IMAGE_FORMAT';\nexport * from './configs/const-enum/CE_MERMAID_THEME';\nexport * from './configs/const-enum/CE_OUTPUT_COMPONENT';\nexport * from './configs/const-enum/CE_OUTPUT_FORMAT';\nexport * from './configs/const-enum/CE_PROJECT_NAME_FROM';\nexport * from './configs/interfaces/IBuildCommandOption';\nexport * from './configs/interfaces/ICommonOption';\nexport * from './configs/interfaces/IDocumentOption';\nexport * from './configs/interfaces/InquirerAnswer';\nexport * from './configs/interfaces/TBuildCommandOptionWithVersion';\nexport * from './configs/modules/getAutoCompleteSource';\nexport * from './configs/modules/getConfigContent';\nexport * from './configs/modules/getConfigFilePath';\nexport * from './configs/modules/getCwd';\nexport * from './configs/modules/preLoadConfig';\nexport * from './creators/columns/getColumnWeight';\nexport * from './creators/interfaces/IErdiaDocument';\nexport * from './creators/interfaces/IReason';\nexport * from './databases/const-enum/CE_CHANGE_KIND';\nexport * from './databases/const-enum/CE_RECORD_KIND';\nexport * from './databases/interfaces/IBaseRecord';\nexport * from './databases/interfaces/IColumnRecord';\nexport * from './databases/interfaces/IEntityRecord';\nexport * from './databases/interfaces/IEntityWithColumnAndRelationAndIndex';\nexport * from './databases/interfaces/IIndexRecord';\nexport * from './databases/interfaces/IRecordMetadata';\nexport * from './databases/interfaces/IRelationRecord';\nexport * from './databases/interfaces/IRenderData';\nexport * from './databases/interfaces/TDatabaseRecord';\nexport * from './modules/commands/building';\nexport * from './modules/commands/cleaning';\nexport * from './modules/commands/ejecting';\nexport * from './modules/commands/initializing';\nexport * from './modules/containers/container';\nexport * from './modules/files/betterMkdir';\nexport * from './modules/files/getFindFile';\nexport * from './modules/files/getGlobFiles';\nexport * from './modules/files/getOutputDirPath';\nexport * from './modules/files/getTemplateDirPath';\nexport * from './modules/loggers/createLogger';\nexport * from './modules/loggers/Logger';\nexport * from './modules/scopes/defaultExclude';\nexport * from './templates/cosnt-enum/CE_TEMPLATE_NAME';\nexport * from './templates/interfaces/ITemplate';\nexport * from './templates/modules/configTemplate';\nexport * from './templates/modules/getTemplate';\nexport * from './templates/modules/getTemplateModulePath';\nexport * from './templates/modules/getTemplatePath';\nexport * from './templates/modules/getTemplates';\nexport * from './templates/modules/loadTemplates';\nexport * from './typeorm/columns/getColumnAttributeKey';\nexport * from './typeorm/columns/getColumnRecord';\nexport * from './typeorm/columns/getColumnType';\nexport * from './typeorm/columns/getComment';\nexport * from './typeorm/columns/getIsNullable';\nexport * from './typeorm/entities/getEntityName';\nexport * from './typeorm/entities/getEntityPropertyName';\nexport * from './typeorm/entities/getEntityRecord';\nexport * from './typeorm/entities/getEntityRecords';\nexport * from './typeorm/indices/getIndexRecord';\nexport * from './typeorm/indices/getIndexRecords';\nexport * from './typeorm/relations/dedupeManyToManyRelationRecord';\nexport * from './typeorm/relations/getInverseRelationMetadata';\nexport * from './typeorm/relations/getJoinColumn';\nexport * from './typeorm/relations/getManyToManyEntityMetadata';\nexport * from './typeorm/relations/getManyToManyJoinColumn';\nexport * from './typeorm/relations/getManyToOneJoinColumn';\nexport * from './typeorm/relations/getRelationRecord';\nexport * from './typeorm/relations/getRelationRecords';\nexport * from './modules/containers/keys/SymbolDataSource';\nexport * from './modules/containers/keys/SymbolDefaultTemplate';\nexport * from './modules/containers/keys/SymbolLogger';\nexport * from './modules/containers/keys/SymbolTemplate';\nexport * from './modules/containers/keys/SymbolTemplateRenderer';\n", "import type { IColumnRecord } from '#/databases/interfaces/IColumnRecord';\n\nexport function getColumnHash(column: Pick<IColumnRecord, 'entity' | 'dbName'>): string {\n const baseHash = [column.entity, column.dbName].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "import { TextDecoder } from 'util';\n\nexport function getDatabaseName(options: { database?: string | Uint8Array | undefined }): string {\n const name = options.database;\n\n if (typeof name === 'string') {\n return name;\n }\n\n if (name instanceof Uint8Array) {\n return new TextDecoder().decode(name);\n }\n\n return 'default';\n}\n", "import type { IEntityRecord } from '#/databases/interfaces/IEntityRecord';\n\nexport function getEntityHash(entity: Pick<IEntityRecord, 'entity' | 'dbName'>): string {\n const baseHash = [entity.entity, entity.dbName].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "import { parse } from 'jsonc-parser';\nimport semver from 'semver';\n\nexport function getFileVersion(buf: Buffer): string {\n const rawJson = buf.toString();\n\n if (semver.valid(rawJson)) {\n return rawJson;\n }\n\n const parsed = parse(rawJson) as Record<string, string>;\n const { version } = parsed;\n\n if (version == null || version === '') {\n throw new Error(`invalid version file: ${rawJson}`);\n }\n\n return version;\n}\n", "import type { IIndexRecord } from '#/databases/interfaces/IIndexRecord';\n\nexport function getIndexHash(column: Pick<IIndexRecord, 'entity' | 'dbName'>): string {\n const baseHash = [column.entity, column.dbName].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "export function getPackageName(json: Record<string, unknown>): string {\n const { name } = json;\n\n if (typeof name === 'string' && name !== '') {\n return name;\n }\n\n throw new Error('Cannot get project name from package.json');\n}\n", "/**\n * determine whether project name will come from the name in `package.json` or database name\n *\n * - db: database name from TypeORM\n * - app: application name from package.json\n */\nexport const CE_PROJECT_NAME_FROM = {\n DATABASE: 'db',\n APPLICATION: 'app',\n} as const;\n\nexport type CE_PROJECT_NAME_FROM = (typeof CE_PROJECT_NAME_FROM)[keyof typeof CE_PROJECT_NAME_FROM];\n", "import { getDatabaseName } from '#/common/getDatabaseName';\nimport { getPackageName } from '#/common/getPackageName';\nimport { CE_PROJECT_NAME_FROM } from '#/configs/const-enum/CE_PROJECT_NAME_FROM';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\n\nexport async function getProjectName(\n dataSource: { options: { database?: string | Uint8Array | undefined } },\n json: Record<string, unknown>,\n option: Pick<IBuildCommandOption, 'projectName'>,\n) {\n if (option.projectName === CE_PROJECT_NAME_FROM.DATABASE) {\n if (dataSource.options.database != null) {\n const databaseName = getDatabaseName(dataSource.options);\n return databaseName;\n }\n\n const name = getPackageName(json);\n return name;\n }\n\n const name = getPackageName(json);\n return name;\n}\n", "export const CE_DEFAULT_VALUE = {\n CONFIG_FILE_NAME: '.erdiarc',\n TSCONFIG_FILE_NAME: 'tsconfig.json',\n\n HTML_INDEX_FILENAME: 'index.html',\n HTML_MERMAID_FILENAME: 'mermaid.html',\n\n MARKDOWN_FILENAME: 'erdia.md',\n\n DATABASE_FILENAME: 'erdiadb.json',\n VERSION_FILENAME: '.erdiaverrc',\n\n TEMPLATES_PATH: 'templates',\n\n DATA_SOURCE_FILE_FUZZY_SCORE_LIMIT: 50,\n OUTPUT_DIRECTORY_FUZZY_SCORE_LIMIT: 50,\n} as const;\n\nexport type CE_DEFAULT_VALUE = (typeof CE_DEFAULT_VALUE)[keyof typeof CE_DEFAULT_VALUE];\n", "export function getCwd(env: NodeJS.ProcessEnv): string {\n if ((env.USE_INIT_CWD ?? 'false') === 'false') {\n return process.cwd();\n }\n\n if (env.INIT_CWD != null) {\n return env.INIT_CWD;\n }\n\n return process.cwd();\n}\n", "import findUp from 'find-up';\n\nexport async function getFindFile(\n filename: string | readonly string[],\n option?: findUp.Options,\n): Promise<string | undefined> {\n const finded = await findUp(filename, option);\n return finded;\n}\n", "import { isFalse } from 'my-easy-fp';\nimport { exists, getDirname } from 'my-node-fp';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\n\nexport async function betterMkdir(filePath: string) {\n const isFilePathExist = await exists(filePath);\n\n if (isFalse(isFilePathExist)) {\n const extname = pathe.extname(filePath);\n const hasExtname = extname !== '' && extname.length > 0;\n\n if (hasExtname) {\n const dirPath = await getDirname(filePath);\n await fs.promises.mkdir(dirPath, { recursive: true });\n } else {\n await fs.promises.mkdir(filePath, { recursive: true });\n }\n }\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport { isFalse } from 'my-easy-fp';\nimport { exists, getDirname, isDirectory } from 'my-node-fp';\nimport pathe from 'pathe';\n\nexport async function getOutputDirPath(option: Pick<IBuildCommandOption, 'output'>, cwd: string) {\n const outputDirPath = option.output ?? cwd;\n const resolvedOutputDirPath = pathe.resolve(outputDirPath);\n\n if (isFalse(await exists(resolvedOutputDirPath))) {\n await betterMkdir(pathe.join(resolvedOutputDirPath));\n return resolvedOutputDirPath;\n }\n\n if (isFalse(await isDirectory(outputDirPath))) {\n return pathe.resolve(await getDirname(outputDirPath));\n }\n\n return pathe.resolve(outputDirPath);\n}\n", "import { getFileVersion } from '#/common/getFileVersion';\nimport { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { getCwd } from '#/configs/modules/getCwd';\nimport { getFindFile } from '#/modules/files/getFindFile';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport dayjs from 'dayjs';\nimport fs from 'fs';\nimport pathe from 'pathe';\n\nasync function getVersionFilename(\n option: Pick<IBuildCommandOption, 'versionFrom' | 'versionPath'>,\n versionFilename: string,\n) {\n if (option.versionPath != null) {\n const filename = await getFindFile(\n pathe.join(await getOutputDirPath({ output: option.versionPath }, getCwd(process.env)), versionFilename),\n { cwd: getCwd(process.env) },\n );\n\n return filename;\n }\n\n const filename = await getFindFile(versionFilename, { cwd: getCwd(process.env) });\n return filename;\n}\n\nexport async function getVersion(\n json: Record<string, unknown>,\n option: Pick<IBuildCommandOption, 'versionFrom' | 'versionPath'>,\n): Promise<{ version: string }> {\n if (option.versionFrom === 'package.json') {\n const { version } = json;\n\n if (!(typeof version === 'string') || version == null) {\n throw new Error(`Cannot found version field in package.json`);\n }\n\n return { version };\n }\n\n if (option.versionFrom === 'file') {\n const getVersionFile = async () => {\n const filename = await getVersionFilename(option, CE_DEFAULT_VALUE.VERSION_FILENAME);\n\n if (filename != null) {\n return filename;\n }\n\n const fromConfig = await getVersionFilename(option, CE_DEFAULT_VALUE.CONFIG_FILE_NAME);\n return fromConfig;\n };\n\n const versionFilename = await getVersionFile();\n\n if (versionFilename == null) {\n throw new Error(`Cannot found version file: ${CE_DEFAULT_VALUE.VERSION_FILENAME}`);\n }\n\n const versionBuf = await fs.promises.readFile(versionFilename);\n const version = getFileVersion(versionBuf);\n return { version: version.trim() };\n }\n\n return { version: `${dayjs().valueOf()}` };\n}\n", "import { createContainer } from 'awilix';\n\nexport const container = createContainer();\n", "export const SymbolDataSource = Symbol('data-source');\n", "import { getProjectName } from '#/common/getProjectName';\nimport { getVersion } from '#/common/getVersion';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport { container } from '#/modules/containers/container';\nimport { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource';\nimport dayjs from 'dayjs';\nimport filenamify from 'filenamify';\nimport readPkg from 'read-pkg';\nimport type { DataSource } from 'typeorm';\n\nexport async function getMetadata(\n option: Pick<IBuildCommandOption, 'projectName' | 'versionFrom' | 'versionPath' | 'title'>,\n): Promise<IRecordMetadata> {\n const dataSource = container.resolve<DataSource>(SymbolDataSource);\n const json = await readPkg({ normalize: false });\n const rawName = await getProjectName(dataSource, json, option);\n const name = filenamify(rawName, { replacement: '_' });\n const { version } = await getVersion(json, option);\n\n return {\n name,\n title: option.title,\n version,\n createdAt: dayjs().format(),\n updatedAt: dayjs().format(),\n };\n}\n", "import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord';\n\nexport function getPlainRelationType(relationType: IRelationRecord['relationType']) {\n if (relationType === 'one-to-one') {\n return 'one-to-one';\n }\n\n if (relationType === 'many-to-many') {\n return 'many-to-many';\n }\n\n return 'one-to-many';\n}\n", "import { getPlainRelationType } from '#/common/getPlainRelationType';\nimport type { IRelationRecord } from '#/databases/interfaces/IRelationRecord';\n\nexport function getRelationHash(\n relation: Pick<IRelationRecord, 'entity' | 'inverseEntityName' | 'relationType'>,\n): string {\n const entities = [relation.entity, relation.inverseEntityName].sort((l, r) => l.localeCompare(r));\n\n const plainRelationType = getPlainRelationType(relation.relationType);\n const baseHash = [...entities, plainRelationType].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "import consola from 'consola';\nimport { isError } from 'my-easy-fp';\n\nexport async function applyPrettier(\n document: string,\n format: 'html' | 'md' | 'json',\n configPath?: string,\n): Promise<string> {\n try {\n const prettier = (await import('prettier')).default;\n const prettierConfig = await prettier.resolveConfig(configPath ?? '.');\n\n const formatted = await prettier.format(document, {\n ...(prettierConfig ?? {}),\n parser: format === 'md' ? 'markdown' : format,\n });\n\n return formatted;\n } catch (caught) {\n const err = isError(caught, new Error('unknown error raised from prettier appling function'));\n\n consola.error(err.message);\n consola.error(err.stack);\n\n return document;\n }\n}\n", "/**\n * ER Diagram output component\n *\n * - table: Entity spec.\n * - er: ER diagram\n */\nexport const CE_OUTPUT_COMPONENT = {\n TABLE: 'table',\n ER: 'er',\n} as const;\n\nexport type CE_OUTPUT_COMPONENT = (typeof CE_OUTPUT_COMPONENT)[keyof typeof CE_OUTPUT_COMPONENT];\n", "export const SymbolTemplateRenderer = Symbol('template-renderer');\n", "export const CE_TEMPLATE_NAME = {\n HTML_DOCUMENT_TOC: 'html-document-toc',\n HTML_DOCUMENT: 'html-document',\n HTML_MERMAID: 'html-mermaid',\n HTML_MERMAID_TOC: 'html-mermaid-toc',\n HTML_MERMAID_DIAGRAM: 'html-mermaid-diagram',\n HTML_STYLE: 'html-style',\n HTML_TABLE: 'html-table',\n\n IMAGE_DOCUMENT: 'image-document',\n IMAGE_MERMAID_DIAGRAM: 'image-mermaid-diagram',\n IMAGE_STYLE: 'image-style',\n\n MARKDOWN_DOCUMENT: 'markdown-document',\n MARKDOWN_MERMAID_DIAGRAM: 'markdown-mermaid-diagram',\n MARKDOWN_TABLE: 'markdown-table',\n MARKDOWN_TOC: 'markdown-toc',\n\n PDF_DOCUMENT_TOC: 'pdf-document-toc',\n PDF_DOCUMENT: 'pdf-document',\n PDF_MERMAID_DIAGRAM: 'pdf-mermaid-diagram',\n PDF_STYLE: 'pdf-style',\n PDF_TABLE: 'pdf-table',\n\n CONFIG_JSON: 'config-json',\n} as const;\n\nexport type CE_TEMPLATE_NAME = (typeof CE_TEMPLATE_NAME)[keyof typeof CE_TEMPLATE_NAME];\n", "import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport { CE_OUTPUT_COMPONENT } from '#/configs/const-enum/CE_OUTPUT_COMPONENT';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { getCwd } from '#/configs/modules/getCwd';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport consola from 'consola';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nasync function getTables(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n outputDir: string,\n): Promise<IErdiaDocument[]> {\n if (!option.components.includes(CE_OUTPUT_COMPONENT.TABLE)) {\n return [];\n }\n\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawTables = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_DOCUMENT, renderData);\n const prettiedTables = await applyPrettier(rawTables, 'html', option.prettierConfig);\n const tablesFileName = pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME);\n return [\n {\n dirname: pathe.resolve(outputDir),\n filename: pathe.resolve(tablesFileName),\n content: prettiedTables,\n },\n ];\n}\n\nasync function getDiagram(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n outputDir: string,\n): Promise<IErdiaDocument[]> {\n if (!option.components.includes(CE_OUTPUT_COMPONENT.ER)) {\n return [];\n }\n\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawDiagram = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_MERMAID, renderData);\n const prettiedDiagram = await applyPrettier(rawDiagram, 'html', option.prettierConfig);\n const diagramFileName = option.components.includes(CE_OUTPUT_COMPONENT.TABLE)\n ? pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME)\n : pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME);\n\n return [\n {\n dirname: pathe.resolve(outputDir),\n filename: pathe.resolve(diagramFileName),\n content: prettiedDiagram,\n },\n ];\n}\n\nexport async function createHtml(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n) {\n const outputDir = await getOutputDirPath(option, getCwd(process.env));\n\n consola.info(`export component: ${option.components.join(', ')}`);\n\n const documents = (\n await Promise.all(\n option.components.map(async (component) => {\n if (component === CE_OUTPUT_COMPONENT.TABLE) {\n return getTables(option, renderData, outputDir);\n }\n\n if (component === CE_OUTPUT_COMPONENT.ER) {\n return getDiagram(option, renderData, outputDir);\n }\n\n return [];\n }),\n )\n ).flat();\n\n return documents;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport { getDirname } from 'my-node-fp';\nimport { randomUUID } from 'node:crypto';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function createImageHtml(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n): Promise<IErdiaDocument> {\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.IMAGE_DOCUMENT, {\n ...renderData,\n option: { ...renderData.option, width: '200vw' },\n });\n\n const prettiedHtml = await applyPrettier(rawHtml, 'html', option.prettierConfig);\n const outputDirPath = option.output != null ? pathe.resolve(option.output) : process.cwd();\n await betterMkdir(outputDirPath);\n const tempFileName = pathe.join(outputDirPath, `${randomUUID()}.html`);\n\n return {\n dirname: await getDirname(outputDirPath),\n content: prettiedHtml,\n filename: pathe.resolve(tempFileName),\n } satisfies IErdiaDocument;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { getCwd } from '#/configs/modules/getCwd';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function createMarkdown(\n option: Pick<IBuildCommandOption, 'output' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n): Promise<IErdiaDocument> {\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawMarkdown = await renderer.evaluate(CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT, renderData);\n const prettiedMarkdown = await applyPrettier(rawMarkdown, 'md', option.prettierConfig);\n const markdownFileName = `${renderData.metadata.name}.md`;\n const outputDir = await getOutputDirPath(option, getCwd(process.env));\n\n return {\n filename: pathe.resolve(pathe.join(outputDir, markdownFileName)),\n dirname: pathe.resolve(outputDir),\n content: prettiedMarkdown,\n } satisfies IErdiaDocument;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport { getDirname } from 'my-node-fp';\nimport { randomUUID } from 'node:crypto';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function createPdfHtml(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n) {\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.PDF_DOCUMENT, renderData);\n const prettiedHtml = await applyPrettier(rawHtml, 'html', option.prettierConfig);\n const outputDirPath = option.output != null ? pathe.resolve(option.output) : process.cwd();\n await betterMkdir(outputDirPath);\n\n const tempFileName = pathe.join(outputDirPath, `${randomUUID()}.html`);\n\n return {\n dirname: await getDirname(outputDirPath),\n content: prettiedHtml,\n filename: pathe.resolve(tempFileName),\n } satisfies IErdiaDocument;\n}\n", "export const CE_OUTPUT_FORMAT = {\n HTML: 'html',\n MARKDOWN: 'md',\n PDF: 'pdf',\n IMAGE: 'image',\n} as const;\n\nexport type CE_OUTPUT_FORMAT = (typeof CE_OUTPUT_FORMAT)[keyof typeof CE_OUTPUT_FORMAT];\n", "export const CE_RECORD_KIND = {\n COLUMN: 'column',\n ENTITY: 'entity',\n RELATION: 'relation',\n INDEX: 'index',\n} as const;\n\nexport type CE_RECORD_KIND = (typeof CE_RECORD_KIND)[keyof typeof CE_RECORD_KIND];\n", "export function getSlashEndRoutePath(basePath: string): string {\n if (basePath.endsWith('/')) {\n return basePath;\n }\n\n return `${basePath}/`;\n}\n", "import { CE_OUTPUT_FORMAT } from '#/configs/const-enum/CE_OUTPUT_FORMAT';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { CE_RECORD_KIND } from '#/databases/const-enum/CE_RECORD_KIND';\nimport type { IColumnRecord } from '#/databases/interfaces/IColumnRecord';\nimport type { IEntityRecord } from '#/databases/interfaces/IEntityRecord';\nimport type { IEntityWithColumnAndRelationAndIndex } from '#/databases/interfaces/IEntityWithColumnAndRelationAndIndex';\nimport type { IIndexRecord } from '#/databases/interfaces/IIndexRecord';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport type { IRelationRecord } from '#/databases/interfaces/IRelationRecord';\nimport type { IRenderData } from '#/databases/interfaces/IRenderData';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { getSlashEndRoutePath } from '#/modules/getSlashEndRoutePath';\nimport alasql from 'alasql';\nimport { compareVersions } from 'compare-versions';\n\nexport async function getRenderData(\n records: TDatabaseRecord[],\n metadata: IRecordMetadata,\n option: Omit<IBuildCommandOption, 'config'>,\n): Promise<IRenderData> {\n const versionRows = (await alasql.promise('SELECT DISTINCT version FROM ?', [records])) as {\n version: string;\n }[];\n\n const unSortedVersions = versionRows.map((version) => version.version);\n const versions =\n option.versionFrom === 'timestamp'\n ? unSortedVersions.sort((l, r) => r.localeCompare(l))\n : unSortedVersions.sort((l, r) => compareVersions(r, l));\n\n const renderDatas = await Promise.all(\n versions.map(async (version) => {\n const entities = (await alasql.promise(`SELECT * FROM ? WHERE [$kind] = ? AND version = ?`, [\n records,\n 'entity',\n version,\n ])) as IEntityRecord[];\n\n const renderData: IEntityWithColumnAndRelationAndIndex[] = await Promise.all(\n entities.map(async (entity) => {\n const [columns, relations, indices] = await Promise.all([\n (await alasql.promise('SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?', [\n records,\n CE_RECORD_KIND.COLUMN,\n entity.entity,\n version,\n ])) as IColumnRecord[],\n\n (await alasql.promise('SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?', [\n records,\n CE_RECORD_KIND.RELATION,\n entity.entity,\n version,\n ])) as IRelationRecord[],\n\n (await alasql.promise('SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?', [\n records,\n CE_RECORD_KIND.INDEX,\n entity.entity,\n version,\n ])) as IIndexRecord[],\n ]);\n\n return { ...entity, columns, relations, indices } satisfies IEntityWithColumnAndRelationAndIndex;\n }),\n );\n\n return { version, entities: renderData, latest: version === metadata.version };\n }),\n );\n\n if (option.format === CE_OUTPUT_FORMAT.HTML) {\n return {\n versions: renderDatas,\n option: {\n ...option,\n routeBasePath: option.routeBasePath != null ? getSlashEndRoutePath(option.routeBasePath) : undefined,\n },\n metadata,\n };\n }\n\n return { versions: renderDatas, option, metadata };\n}\n", "import fs from 'fs';\nimport { parse } from 'jsonc-parser';\nimport { exists } from 'my-node-fp';\nimport type puppeteer from 'puppeteer';\n\nexport async function getPuppeteerConfig(confgFilePath?: string): Promise<Parameters<typeof puppeteer.launch>[0]> {\n try {\n if (confgFilePath == null) {\n return {};\n }\n\n if (await exists(confgFilePath)) {\n const buf = await fs.promises.readFile(confgFilePath);\n const option = parse(buf.toString()) as Parameters<typeof puppeteer.launch>[0];\n return option;\n }\n\n return {};\n } catch {\n return {};\n }\n}\n", "/* eslint-disable no-param-reassign, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any */\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport { getPuppeteerConfig } from '#/modules/getPuppeteerConfig';\nimport consola from 'consola';\nimport del from 'del';\nimport { isError } from 'my-easy-fp';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\nimport * as puppeteer from 'puppeteer';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function writeToImage(\n document: IErdiaDocument,\n option: Pick<\n IBuildCommandOption,\n 'output' | 'components' | 'prettierConfig' | 'viewportWidth' | 'viewportHeight' | 'backgroundColor' | 'imageFormat'\n >,\n renderData: AsyncReturnType<typeof getRenderData>,\n) {\n let localBrowser: puppeteer.Browser | undefined;\n let localPage: puppeteer.Page | undefined;\n\n try {\n const puppeteerConfig = await getPuppeteerConfig(option.prettierConfig);\n const browser = await puppeteer.launch({ ...puppeteerConfig, headless: true });\n const page = await browser.newPage();\n const puppeteerGotoOption: Parameters<typeof page.goto>[1] = {\n waitUntil: 'domcontentloaded',\n timeout: 60_000,\n };\n\n localBrowser = browser;\n localPage = page;\n\n await betterMkdir(document.filename);\n await fs.promises.writeFile(document.filename, document.content);\n await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 });\n await page.goto(`file://${document.filename}`, puppeteerGotoOption);\n\n consola.debug(`file write start: ${document.filename}`);\n\n await page.$eval(\n 'body',\n (body, backgroundColor) => {\n body.style.background = backgroundColor;\n },\n option.backgroundColor ?? 'white',\n );\n\n if (option.imageFormat === 'svg') {\n // this source code from [mermaid-cli](https://github.com/mermaidjs/mermaid.cli/blob/46185413d75384cd7bceed802d187db6852f5190/index.js#L110)\n const svg = await page.$eval('#mermaid-diagram-container', (container) => container.innerHTML);\n\n if (svg == null) {\n await del(document.filename);\n throw new Error('invalid image html document template');\n }\n\n await fs.promises.writeFile(pathe.join(document.dirname, `${renderData.metadata.name}.svg`), svg);\n consola.debug('file write end');\n\n await del(document.filename);\n consola.info(`Component ER diagram successfully write on ${renderData.metadata.name}.svg`);\n\n return [pathe.join(document.dirname, `${renderData.metadata.name}.svg`)];\n }\n\n // this source code from [mermaid-cli](https://github.com/mermaidjs/mermaid.cli/blob/46185413d75384cd7bceed802d187db6852f5190/index.js#L113-L117)\n const clip = await page.$eval('svg', (htmlSvgElement: any) => {\n const react = htmlSvgElement.getBoundingClientRect();\n return { x: react.left, y: react.top, width: react.width, height: react.height };\n });\n\n await page.screenshot({\n path: pathe.join(document.dirname, `${renderData.metadata.name}.png`),\n clip,\n omitBackground: false,\n });\n\n consola.debug('file write end');\n\n await del(document.filename);\n consola.info(`Component ER diagram successfully write on ${renderData.metadata.name}.png`);\n\n return [pathe.join(document.dirname, `${renderData.metadata.name}.png`)];\n } catch (caught) {\n const err = isError(caught, new Error('unknown error raised from writeToImage'));\n\n consola.error(err.message);\n consola.error(err.stack);\n\n return false;\n } finally {\n consola.debug('Start page, brower close');\n\n if (localPage !== undefined && localPage !== null) {\n await localPage.close();\n }\n\n if (localBrowser !== undefined && localBrowser !== null) {\n await localBrowser.close();\n }\n }\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { getPuppeteerConfig } from '#/modules/getPuppeteerConfig';\nimport consola from 'consola';\nimport del from 'del';\nimport fs from 'fs';\nimport { isError } from 'my-easy-fp';\nimport pathe from 'pathe';\nimport * as puppeteer from 'puppeteer';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function writeToPdf(\n document: IErdiaDocument,\n option: Pick<\n IBuildCommandOption,\n | 'output'\n | 'components'\n | 'prettierConfig'\n | 'puppeteerConfig'\n | 'viewportWidth'\n | 'viewportHeight'\n | 'backgroundColor'\n >,\n renderData: AsyncReturnType<typeof getRenderData>,\n): Promise<string[]> {\n let localBrowser: puppeteer.Browser | undefined;\n let localPage: puppeteer.Page | undefined;\n\n try {\n const puppeteerConfig = await getPuppeteerConfig(option.puppeteerConfig);\n const browser = await puppeteer.launch({ ...puppeteerConfig, headless: true });\n const page = await browser.newPage();\n const puppeteerGotoOption: Parameters<typeof page.goto>[1] = {\n waitUntil: 'domcontentloaded',\n timeout: 60_000,\n };\n\n localBrowser = browser;\n localPage = page;\n\n consola.info('filename: ', document.filename);\n\n await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 });\n await fs.promises.writeFile(document.filename, document.content);\n await page.goto(`file://${document.filename}`, puppeteerGotoOption);\n await page.pdf({\n path: pathe.join(document.dirname, `${renderData.metadata.name}.pdf`),\n printBackground: option.backgroundColor !== 'transparent',\n });\n\n await del(document.filename);\n\n return [pathe.join(document.dirname, `${renderData.metadata.name}.pdf`)];\n } catch (caught) {\n const err = isError(caught, new Error('unknown error raised from writeToPdf'));\n\n consola.error(err.message);\n consola.error(err.stack);\n\n return [];\n } finally {\n if (localPage !== undefined && localPage !== null) {\n consola.debug('Session Closed');\n await localPage.close();\n }\n\n if (localBrowser !== undefined && localBrowser !== null) {\n await localBrowser.close();\n }\n }\n}\n", "export const CE_CHANGE_KIND = {\n CHANGE: 'change',\n ADD: 'add',\n DELETE: 'delete',\n NONE: 'none',\n} as const;\n\nexport type CE_CHANGE_KIND = (typeof CE_CHANGE_KIND)[keyof typeof CE_CHANGE_KIND];\n", "import { getColumnHash } from '#/common/getColumnHash';\nimport { getEntityHash } from '#/common/getEntityHash';\nimport { getIndexHash } from '#/common/getIndexHash';\nimport { getRelationHash } from '#/common/getRelationHash';\nimport { CE_CHANGE_KIND } from '#/databases/const-enum/CE_CHANGE_KIND';\nimport { CE_RECORD_KIND } from '#/databases/const-enum/CE_RECORD_KIND';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { detailedDiff } from 'deep-object-diff';\nimport { settify } from 'my-easy-fp';\n\nexport function compareDatabase(\n metadata: IRecordMetadata,\n next: TDatabaseRecord[],\n prev: TDatabaseRecord[],\n): TDatabaseRecord[] {\n if (prev.length <= 0) {\n return next.map((record) => ({ ...record, change: CE_CHANGE_KIND.NONE }));\n }\n\n const nextMap = next.reduce<Record<string, TDatabaseRecord>>((aggregation, record) => {\n switch (record.$kind) {\n case CE_RECORD_KIND.ENTITY:\n return { ...aggregation, [getEntityHash(record)]: record };\n case CE_RECORD_KIND.COLUMN:\n return { ...aggregation, [getColumnHash(record)]: record };\n case CE_RECORD_KIND.RELATION:\n return { ...aggregation, [getRelationHash(record)]: record };\n case CE_RECORD_KIND.INDEX:\n return { ...aggregation, [getIndexHash(record)]: record };\n default:\n return aggregation;\n }\n }, {});\n\n const prevMap = prev.reduce<Record<string, TDatabaseRecord>>((aggregation, record) => {\n switch (record.$kind) {\n case CE_RECORD_KIND.ENTITY:\n return { ...aggregation, [getEntityHash(record)]: record };\n case CE_RECORD_KIND.COLUMN:\n return { ...aggregation, [getColumnHash(record)]: record };\n case CE_RECORD_KIND.RELATION:\n return { ...aggregation, [getRelationHash(record)]: record };\n case CE_RECORD_KIND.INDEX:\n return { ...aggregation, [getIndexHash(record)]: record };\n default:\n return aggregation;\n }\n }, {});\n\n const compared = settify([...Object.keys(nextMap), ...Object.keys(prevMap)]).map((key): TDatabaseRecord => {\n const fromNext = nextMap[key];\n const fromPrev = prevMap[key];\n // add\n if (fromNext != null && fromPrev == null) {\n return { ...fromNext, change: CE_CHANGE_KIND.ADD } satisfies TDatabaseRecord;\n }\n\n // delete\n if (fromNext == null && fromPrev != null) {\n return { ...fromPrev, change: CE_CHANGE_KIND.DELETE, version: metadata.version } satisfies TDatabaseRecord;\n }\n\n const forCompareNext: TDatabaseRecord = {\n ...fromNext,\n title: fromNext.title ?? '',\n change: CE_CHANGE_KIND.NONE,\n createdAt: '',\n updatedAt: '',\n version: '',\n };\n const forComparePrev: TDatabaseRecord = {\n ...fromPrev,\n title: fromPrev.title ?? '',\n change: CE_CHANGE_KIND.NONE,\n createdAt: '',\n updatedAt: '',\n version: '',\n };\n const diffed = detailedDiff(forCompareNext, forComparePrev);\n\n // none\n if (\n Object.keys(diffed.added).length <= 0 &&\n Object.keys(diffed.updated).length <= 0 &&\n Object.keys(diffed.deleted).length <= 0\n ) {\n return { ...fromNext, change: CE_CHANGE_KIND.NONE } satisfies TDatabaseRecord;\n }\n\n // change\n return { ...fromNext, change: CE_CHANGE_KIND.CHANGE, prev: fromPrev } satisfies TDatabaseRecord;\n });\n\n return compared;\n}\n", "import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\n\nexport async function flushDatabase(\n option: Pick<IBuildCommandOption, 'databasePath'>,\n records: TDatabaseRecord[],\n): Promise<TDatabaseRecord[]> {\n const dirname = await getOutputDirPath({ output: option.databasePath }, process.cwd());\n const filename = pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME);\n\n if (filename == null) {\n throw new Error(`invalid database name: undefined`);\n }\n\n await fs.promises.writeFile(\n pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME),\n JSON.stringify(records, undefined, 2),\n );\n\n return records;\n}\n", "import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport { parse } from 'jsonc-parser';\nimport { isFalse } from 'my-easy-fp';\nimport { exists } from 'my-node-fp';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\n\nexport async function openDatabase(option: Pick<IBuildCommandOption, 'databasePath'>): Promise<TDatabaseRecord[]> {\n const dirname = await getOutputDirPath({ output: option.databasePath }, process.cwd());\n const filename = pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME);\n\n if (filename == null) {\n return [];\n }\n\n if (isFalse(await exists(filename))) {\n return [];\n }\n\n const db = parse((await fs.promises.readFile(filename)).toString()) as TDatabaseRecord[];\n return db;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport alasql from 'alasql';\nimport { compareVersions } from 'compare-versions';\nimport { atOrThrow } from 'my-easy-fp';\n\nexport async function processDatabase(\n metadata: IRecordMetadata,\n db: TDatabaseRecord[],\n option: Pick<IBuildCommandOption, 'versionFrom'>,\n): Promise<{ next: TDatabaseRecord[]; deleted: TDatabaseRecord[]; prev: TDatabaseRecord[] }> {\n // case 01. empty database, first create database\n if (db.length <= 0) {\n return { next: [], deleted: [], prev: [] };\n }\n\n const currentVersion = metadata.version;\n const versions = (await alasql.promise('SELECT DISTINCT version FROM ?', [db])) as {\n version: string;\n }[];\n\n const sortedVersions =\n option.versionFrom === 'timestamp'\n ? versions.sort((l, r) => r.version.localeCompare(l.version))\n : versions.sort((l, r) => compareVersions(r.version, l.version));\n\n const firstVersionFromDb = atOrThrow(sortedVersions, 0).version;\n\n // case 02. different version between current and latest from database\n if (currentVersion !== firstVersionFromDb) {\n const latestRecords = (await alasql.promise('SELECT * FROM ? WHERE version = ?', [\n db,\n firstVersionFromDb,\n ])) as TDatabaseRecord[];\n\n return { next: db, deleted: [], prev: latestRecords };\n }\n\n // case 03. database have only one version, but one version same cur