diff --git a/apps/nestjs-backend/src/features/v2/v2-container.service.ts b/apps/nestjs-backend/src/features/v2/v2-container.service.ts index 88da37b59..89c8f9e2c 100644 --- a/apps/nestjs-backend/src/features/v2/v2-container.service.ts +++ b/apps/nestjs-backend/src/features/v2/v2-container.service.ts @@ -4,17 +4,23 @@ import { ConfigService } from '@nestjs/config'; import { createV2NodePgContainer } from '@teable/v2-container-node'; import { v2PostgresDbTokens } from '@teable/v2-db-postgres'; import type { DependencyContainer } from '@teable/v2-di'; +import { PinoLogger } from 'nestjs-pino'; +import { PinoLoggerAdapter } from './v2-logger.adapter'; @Injectable() export class V2ContainerService implements OnModuleDestroy { private containerPromise?: Promise; - constructor(private readonly configService: ConfigService) {} + constructor( + private readonly configService: ConfigService, + private readonly pinoLogger: PinoLogger + ) {} async getContainer(): Promise { if (!this.containerPromise) { const connectionString = this.configService.getOrThrow('PRISMA_DATABASE_URL'); - this.containerPromise = createV2NodePgContainer({ connectionString }); + const logger = new PinoLoggerAdapter(this.pinoLogger); + this.containerPromise = createV2NodePgContainer({ connectionString, logger }); } return this.containerPromise; diff --git a/apps/nestjs-backend/src/features/v2/v2-logger.adapter.ts b/apps/nestjs-backend/src/features/v2/v2-logger.adapter.ts new file mode 100644 index 000000000..017d2065e --- /dev/null +++ b/apps/nestjs-backend/src/features/v2/v2-logger.adapter.ts @@ -0,0 +1,38 @@ +import type { ILogger, LogContext } from '@teable/v2-core'; +import type { PinoLogger } from 'nestjs-pino'; + +export class PinoLoggerAdapter implements ILogger { + constructor(private readonly logger: PinoLogger) {} + + debug(message: string, context?: LogContext): void { + if (context) { + this.logger.debug(context, message); + return; + } + this.logger.debug(message); + } + + info(message: string, context?: LogContext): void { + if (context) { + this.logger.info(context, message); + return; + } + this.logger.info(message); + } + + warn(message: string, context?: LogContext): void { + if (context) { + this.logger.warn(context, message); + return; + } + this.logger.warn(message); + } + + error(message: string, context?: LogContext): void { + if (context) { + this.logger.error(context, message); + return; + } + this.logger.error(message); + } +} diff --git a/packages/v2/adapter-logger-console/.eslintrc.cjs b/packages/v2/adapter-logger-console/.eslintrc.cjs new file mode 100644 index 000000000..b1815ba8a --- /dev/null +++ b/packages/v2/adapter-logger-console/.eslintrc.cjs @@ -0,0 +1,29 @@ +/** + * Specific eslint rules for this workspace, learn how to compose + * @link https://github.com/teableio/teable/tree/main/packages/eslint-config-bases + */ +require('@teable/eslint-config-bases/patch/modern-module-resolution'); + +const { getDefaultIgnorePatterns } = require('@teable/eslint-config-bases/helpers'); + +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.eslint.json', + }, + ignorePatterns: [...getDefaultIgnorePatterns()], + extends: [ + '@teable/eslint-config-bases/typescript', + '@teable/eslint-config-bases/sonar', + '@teable/eslint-config-bases/regexp', + '@teable/eslint-config-bases/jest', + // Apply prettier and disable incompatible rules + '@teable/eslint-config-bases/prettier-plugin', + ], + rules: { + '@typescript-eslint/consistent-type-imports': 'off', + }, + overrides: [], +}; diff --git a/packages/v2/adapter-logger-console/.gitignore b/packages/v2/adapter-logger-console/.gitignore new file mode 100644 index 000000000..99f46c5d2 --- /dev/null +++ b/packages/v2/adapter-logger-console/.gitignore @@ -0,0 +1,12 @@ +# build +/dist + +# dependencies +node_modules + +# testing +/coverage + +# misc +.DS_Store +*.pem diff --git a/packages/v2/adapter-logger-console/package.json b/packages/v2/adapter-logger-console/package.json new file mode 100644 index 000000000..581e0ccce --- /dev/null +++ b/packages/v2/adapter-logger-console/package.json @@ -0,0 +1,39 @@ +{ + "name": "@teable/v2-adapter-logger-console", + "version": "0.0.0", + "private": true, + "license": "MIT", + "sideEffects": false, + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsdown --tsconfig tsconfig.build.json", + "dev": "tsdown --tsconfig tsconfig.build.json --watch", + "clean": "rimraf ./dist ./coverage ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./.eslintcache", + "lint": "eslint . --ext .ts,.js,.mjs,.cjs,.mts,.cts --cache --cache-location ../../../.cache/eslint/v2-adapter-logger-console.eslintcache", + "typecheck": "tsc --project ./tsconfig.json --noEmit", + "test-unit": "vitest run --silent", + "test-unit-cover": "pnpm test-unit --coverage", + "fix-all-files": "eslint . --ext .ts,.js,.mjs,.cjs,.mts,.cts --fix" + }, + "dependencies": { + "@teable/v2-core": "workspace:*" + }, + "devDependencies": { + "@teable/eslint-config-bases": "workspace:^", + "@teable/v2-tsdown-config": "workspace:*", + "@types/node": "22.18.0", + "@vitest/coverage-v8": "4.0.16", + "eslint": "8.57.0", + "prettier": "3.2.5", + "rimraf": "5.0.5", + "tsdown": "0.18.1", + "typescript": "5.4.3", + "vite-tsconfig-paths": "4.3.2", + "vitest": "4.0.16" + } +} diff --git a/packages/v2/adapter-logger-console/src/ConsoleLogger.ts b/packages/v2/adapter-logger-console/src/ConsoleLogger.ts new file mode 100644 index 000000000..a50d7ad99 --- /dev/null +++ b/packages/v2/adapter-logger-console/src/ConsoleLogger.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { ILogger, LogContext } from '@teable/v2-core'; + +type ConsoleLogFn = (...args: unknown[]) => void; + +export class ConsoleLogger implements ILogger { + private log(logFn: ConsoleLogFn, message: string, context?: LogContext): void { + if (context) { + logFn.call(console, message, context); + return; + } + logFn.call(console, message); + } + + debug(message: string, context?: LogContext): void { + this.log(console.debug, message, context); + } + + info(message: string, context?: LogContext): void { + this.log(console.info, message, context); + } + + warn(message: string, context?: LogContext): void { + this.log(console.warn, message, context); + } + + error(message: string, context?: LogContext): void { + this.log(console.error, message, context); + } +} diff --git a/packages/v2/adapter-logger-console/src/index.ts b/packages/v2/adapter-logger-console/src/index.ts new file mode 100644 index 000000000..18eb3ee2c --- /dev/null +++ b/packages/v2/adapter-logger-console/src/index.ts @@ -0,0 +1 @@ +export * from './ConsoleLogger'; diff --git a/packages/v2/adapter-logger-console/tsconfig.build.json b/packages/v2/adapter-logger-console/tsconfig.build.json new file mode 100644 index 000000000..1fea0a9a4 --- /dev/null +++ b/packages/v2/adapter-logger-console/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "paths": {} + }, + "exclude": ["dist", "**/__tests__/**", "**/*.spec.ts", "**/*.test.ts"], + "include": ["src"] +} diff --git a/packages/v2/adapter-logger-console/tsconfig.eslint.json b/packages/v2/adapter-logger-console/tsconfig.eslint.json new file mode 100644 index 000000000..a2942efa5 --- /dev/null +++ b/packages/v2/adapter-logger-console/tsconfig.eslint.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "allowJs": true + }, + "exclude": ["node_modules", "**/.*/*", "dist"], + "include": [ + ".eslintrc.*", + "**/*.ts", + "**/*.tsx", + "**/*.mts", + "**/*.js", + "**/*.cjs", + "**/*.mjs", + "**/*.jsx", + "**/*.json" + ] +} diff --git a/packages/v2/adapter-logger-console/tsconfig.json b/packages/v2/adapter-logger-console/tsconfig.json new file mode 100644 index 000000000..d875d6475 --- /dev/null +++ b/packages/v2/adapter-logger-console/tsconfig.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "@teable/v2-adapter-logger-console", + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "esnext", + "lib": ["esnext", "dom"], + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "noEmit": false, + "incremental": true, + "resolveJsonModule": true, + "declaration": true, + "declarationDir": "dist", + "composite": true, + "rootDir": "../", + "outDir": "dist", + "paths": { + "@teable/v2-core": ["../core/src"] + }, + "types": ["vitest/globals", "node"] + }, + "exclude": ["**/node_modules", "**/.*/", "./dist", "./coverage"], + "include": ["src", "../core/src"] +} diff --git a/packages/v2/adapter-logger-console/tsdown.config.ts b/packages/v2/adapter-logger-console/tsdown.config.ts new file mode 100644 index 000000000..32c631d34 --- /dev/null +++ b/packages/v2/adapter-logger-console/tsdown.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from 'tsdown'; + +import { v2TsdownBaseConfig } from '@teable/v2-tsdown-config'; + +export default defineConfig(v2TsdownBaseConfig); diff --git a/packages/v2/adapter-logger-console/vitest.config.ts b/packages/v2/adapter-logger-console/vitest.config.ts new file mode 100644 index 000000000..86f007a39 --- /dev/null +++ b/packages/v2/adapter-logger-console/vitest.config.ts @@ -0,0 +1,29 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig, configDefaults } from 'vitest/config'; + +const testFiles = ['./src/**/*.{test,spec}.{js,ts}']; + +export default defineConfig({ + plugins: [tsconfigPaths()], + cacheDir: '../../../.cache/vitest/v2-adapter-logger-console', + test: { + globals: true, + environment: 'node', + passWithNoTests: true, + typecheck: { + enabled: false, + }, + pool: 'forks', + fileParallelism: false, + coverage: { + provider: 'v8', + extension: ['.js', '.ts'], + include: ['src/**/*'], + }, + clearMocks: true, + mockReset: true, + restoreMocks: true, + include: testFiles, + exclude: [...configDefaults.exclude, '**/.next/**'], + }, +}); diff --git a/packages/v2/adapter-postgres-state/src/config.ts b/packages/v2/adapter-postgres-state/src/config.ts index 92d343d14..1a182e268 100644 --- a/packages/v2/adapter-postgres-state/src/config.ts +++ b/packages/v2/adapter-postgres-state/src/config.ts @@ -1,6 +1,5 @@ -import { z } from 'zod'; - import { v2PostgresDbConfigSchema } from '@teable/v2-db-postgres'; +import { z } from 'zod'; export const v2PostgresStateAdapterConfigSchema = v2PostgresDbConfigSchema.extend({ ensureSchema: z.boolean().optional(), diff --git a/packages/v2/container-browser/src/index.ts b/packages/v2/container-browser/src/index.ts index 3191fefed..0b884db36 100644 --- a/packages/v2/container-browser/src/index.ts +++ b/packages/v2/container-browser/src/index.ts @@ -1,5 +1,6 @@ import { NoopEventPublisher, + NoopLogger, NoopTableRepository, NoopTableSchemaRepository, NoopUnitOfWork, @@ -23,6 +24,9 @@ export const registerV2BrowserNoopDependencies = ( c.register(v2CoreTokens.unitOfWork, NoopUnitOfWork, { lifecycle: Lifecycle.Singleton, }); + c.register(v2CoreTokens.logger, NoopLogger, { + lifecycle: Lifecycle.Singleton, + }); return c; }; diff --git a/packages/v2/container-node-test/package.json b/packages/v2/container-node-test/package.json index 841ad5ec0..e9c94317f 100644 --- a/packages/v2/container-node-test/package.json +++ b/packages/v2/container-node-test/package.json @@ -21,6 +21,7 @@ "fix-all-files": "eslint . --ext .ts,.js,.mjs,.cjs,.mts,.cts --fix" }, "dependencies": { + "@teable/v2-adapter-logger-console": "workspace:*", "@teable/v2-adapter-postgres-ddl": "workspace:*", "@teable/v2-adapter-postgres-state": "workspace:*", "@teable/v2-core": "workspace:*", diff --git a/packages/v2/container-node-test/src/index.ts b/packages/v2/container-node-test/src/index.ts index ea32beb73..51b2c9fbc 100644 --- a/packages/v2/container-node-test/src/index.ts +++ b/packages/v2/container-node-test/src/index.ts @@ -1,3 +1,4 @@ +import { ConsoleLogger } from '@teable/v2-adapter-logger-console'; import { registerV2PostgresDdlAdapter } from '@teable/v2-adapter-postgres-ddl'; import { registerV2PostgresStateAdapter } from '@teable/v2-adapter-postgres-state'; import type { ITableRepository } from '@teable/v2-core'; @@ -37,6 +38,7 @@ export const createV2NodeTestContainer = async (): Promise c.register(v2CoreTokens.unitOfWork, PostgresUnitOfWork, { lifecycle: Lifecycle.Singleton, }); + c.registerInstance(v2CoreTokens.logger, new ConsoleLogger()); const tableRepository = c.resolve(v2CoreTokens.tableRepository); const eventPublisher = new MemoryEventPublisher(); diff --git a/packages/v2/container-node-test/tsconfig.json b/packages/v2/container-node-test/tsconfig.json index c6bb4c724..b772a9052 100644 --- a/packages/v2/container-node-test/tsconfig.json +++ b/packages/v2/container-node-test/tsconfig.json @@ -20,6 +20,7 @@ "rootDir": "../", "outDir": "dist", "paths": { + "@teable/v2-adapter-logger-console": ["../adapter-logger-console/src"], "@teable/v2-adapter-postgres-ddl": ["../adapter-postgres-ddl/src"], "@teable/v2-adapter-postgres-state": ["../adapter-postgres-state/src"], "@teable/v2-core": ["../core/src"], @@ -31,6 +32,7 @@ "exclude": ["**/node_modules", "**/.*/", "./dist", "./coverage"], "include": [ "src", + "../adapter-logger-console/src", "../adapter-postgres-ddl/src", "../adapter-postgres-state/src", "../core/src", diff --git a/packages/v2/container-node/src/index.ts b/packages/v2/container-node/src/index.ts index 51169e95f..5e21947df 100644 --- a/packages/v2/container-node/src/index.ts +++ b/packages/v2/container-node/src/index.ts @@ -1,7 +1,7 @@ import { registerV2PostgresDdlAdapter } from '@teable/v2-adapter-postgres-ddl'; import type { IV2PostgresStateAdapterConfig } from '@teable/v2-adapter-postgres-state'; import { registerV2PostgresStateAdapter } from '@teable/v2-adapter-postgres-state'; -import { NoopEventPublisher, v2CoreTokens } from '@teable/v2-core'; +import { NoopEventPublisher, NoopLogger, v2CoreTokens, type ILogger } from '@teable/v2-core'; import { PostgresUnitOfWork } from '@teable/v2-db-postgres'; import type { DependencyContainer } from '@teable/v2-di'; import { Lifecycle, container } from '@teable/v2-di'; @@ -10,6 +10,7 @@ export interface IV2NodePgContainerOptions { connectionString?: string; ensureSchema?: boolean; seed?: Partial; + logger?: ILogger; } export const registerV2NodePgDependencies = async ( @@ -42,6 +43,14 @@ export const registerV2NodePgDependencies = async ( lifecycle: Lifecycle.Singleton, }); + if (options.logger) { + c.registerInstance(v2CoreTokens.logger, options.logger); + } else { + c.register(v2CoreTokens.logger, NoopLogger, { + lifecycle: Lifecycle.Singleton, + }); + } + return c; }; diff --git a/packages/v2/core/.eslintrc.cjs b/packages/v2/core/.eslintrc.cjs index 85c8092f7..2f93a9484 100644 --- a/packages/v2/core/.eslintrc.cjs +++ b/packages/v2/core/.eslintrc.cjs @@ -24,6 +24,7 @@ module.exports = { ], rules: { '@typescript-eslint/naming-convention': 'off', + 'no-console': 'error', }, overrides: [ { diff --git a/packages/v2/core/src/commands/CreateTableHandler.ts b/packages/v2/core/src/commands/CreateTableHandler.ts index 251b417d4..8611db394 100644 --- a/packages/v2/core/src/commands/CreateTableHandler.ts +++ b/packages/v2/core/src/commands/CreateTableHandler.ts @@ -5,12 +5,13 @@ import type { Result } from 'neverthrow'; import type { IDomainEvent } from '../domain/shared/DomainEvent'; import type { Table } from '../domain/table/Table'; import { Table as TableAggregate } from '../domain/table/Table'; +import { IEventPublisher } from '../ports/EventPublisher'; import type { IExecutionContext } from '../ports/ExecutionContext'; -import type { IEventPublisher } from '../ports/EventPublisher'; -import type { ITableRepository } from '../ports/TableRepository'; -import type { ITableSchemaRepository } from '../ports/TableSchemaRepository'; +import { ILogger } from '../ports/Logger'; +import { ITableRepository } from '../ports/TableRepository'; +import { ITableSchemaRepository } from '../ports/TableSchemaRepository'; import { v2CoreTokens } from '../ports/tokens'; -import type { IUnitOfWork } from '../ports/UnitOfWork'; +import { IUnitOfWork } from '../ports/UnitOfWork'; import type { CreateTableCommand } from './CreateTableCommand'; export class CreateTableResult { @@ -33,6 +34,8 @@ export class CreateTableHandler { private readonly tableSchemaRepository: ITableSchemaRepository, @inject(v2CoreTokens.eventPublisher) private readonly eventPublisher: IEventPublisher, + @inject(v2CoreTokens.logger) + private readonly logger: ILogger, @inject(v2CoreTokens.unitOfWork) private readonly unitOfWork: IUnitOfWork ) {} @@ -41,6 +44,14 @@ export class CreateTableHandler { context: IExecutionContext, command: CreateTableCommand ): Promise> { + this.logger.info('CreateTableHandler.start', { + actorId: context.actorId.toString(), + baseId: command.baseId.toString(), + tableName: command.tableName.toString(), + fieldCount: command.fields.length, + viewCount: command.views.length, + }); + const tableResult = this.buildTable(command); if (tableResult.isErr()) return err(tableResult.error); const table = tableResult.value; @@ -64,6 +75,12 @@ export class CreateTableHandler { const publishResult = this.eventPublisher.publishMany(context, events); if (publishResult.isErr()) return err(publishResult.error); + this.logger.info('CreateTableHandler.success', { + baseId: command.baseId.toString(), + tableId: table.id().toString(), + eventCount: events.length, + }); + return ok(CreateTableResult.create(table, events)); } diff --git a/packages/v2/core/src/index.ts b/packages/v2/core/src/index.ts index ca282cb92..7ca809a1e 100644 --- a/packages/v2/core/src/index.ts +++ b/packages/v2/core/src/index.ts @@ -76,6 +76,7 @@ export type { PluginView } from './domain/table/views/types/PluginView'; export * from './ports/EventPublisher'; export * from './ports/ExecutionContext'; +export * from './ports/Logger'; export * from './ports/TableRepository'; export * from './ports/TableSchemaRepository'; export * from './ports/UnitOfWork'; diff --git a/packages/v2/core/src/ports/Logger.ts b/packages/v2/core/src/ports/Logger.ts new file mode 100644 index 000000000..b4ff082b8 --- /dev/null +++ b/packages/v2/core/src/ports/Logger.ts @@ -0,0 +1,8 @@ +export type LogContext = Readonly>; + +export interface ILogger { + debug(message: string, context?: LogContext): void; + info(message: string, context?: LogContext): void; + warn(message: string, context?: LogContext): void; + error(message: string, context?: LogContext): void; +} diff --git a/packages/v2/core/src/ports/defaults/NoopLogger.ts b/packages/v2/core/src/ports/defaults/NoopLogger.ts new file mode 100644 index 000000000..5a461b011 --- /dev/null +++ b/packages/v2/core/src/ports/defaults/NoopLogger.ts @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { ILogger, LogContext } from '../Logger'; + +export class NoopLogger implements ILogger { + debug(_: string, __?: LogContext): void {} + + info(_: string, __?: LogContext): void {} + + warn(_: string, __?: LogContext): void {} + + error(_: string, __?: LogContext): void {} +} diff --git a/packages/v2/core/src/ports/defaults/index.ts b/packages/v2/core/src/ports/defaults/index.ts index caa0c55c3..5a3ff9993 100644 --- a/packages/v2/core/src/ports/defaults/index.ts +++ b/packages/v2/core/src/ports/defaults/index.ts @@ -1,4 +1,5 @@ export * from './NoopEventPublisher'; +export * from './NoopLogger'; export * from './NoopTableRepository'; export * from './NoopTableSchemaRepository'; export * from './NoopUnitOfWork'; diff --git a/packages/v2/core/src/ports/tokens.ts b/packages/v2/core/src/ports/tokens.ts index 15a583d62..ec9a9df34 100644 --- a/packages/v2/core/src/ports/tokens.ts +++ b/packages/v2/core/src/ports/tokens.ts @@ -3,4 +3,5 @@ export const v2CoreTokens = { tableSchemaRepository: Symbol('v2.core.tableSchemaRepository'), eventPublisher: Symbol('v2.core.eventPublisher'), unitOfWork: Symbol('v2.core.unitOfWork'), + logger: Symbol('v2.core.logger'), } as const; diff --git a/packages/v2/test-node/src/commands/CreateTableHandler.spec.ts b/packages/v2/test-node/src/commands/CreateTableHandler.spec.ts index 89f89b4b9..4c9952f98 100644 --- a/packages/v2/test-node/src/commands/CreateTableHandler.spec.ts +++ b/packages/v2/test-node/src/commands/CreateTableHandler.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-empty-function */ import { ActorId, CreateTableCommand, @@ -9,7 +11,7 @@ import { v2CoreTokens, } from '@teable/v2-core'; import { err } from 'neverthrow'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { getV2NodeTestContainer } from '../testkit/v2NodeTestContainer'; @@ -18,57 +20,81 @@ describe('CreateTableHandler', () => { const { container, tableRepository, eventPublisher, baseId } = getV2NodeTestContainer(); const handler = container.resolve(CreateTableHandler); - const commandResult = CreateTableCommand.create({ - baseId: baseId.toString(), - name: 'Projects', - fields: [ - { type: 'singleLineText', name: 'Name', options: { defaultValue: 'Project' } }, - { - type: 'rating', - name: 'Priority', - options: { max: 5, icon: 'star', color: 'yellowBright' }, - }, - { - type: 'singleSelect', - name: 'Status', - options: { - choices: [ - { name: 'Todo', color: 'blue' }, - { name: 'Doing', color: 'yellow' }, - { name: 'Done', color: 'green' }, - ], + const infoSpy = vi.spyOn(console, 'info').mockImplementation(() => {}); + try { + const commandResult = CreateTableCommand.create({ + baseId: baseId.toString(), + name: 'Projects', + fields: [ + { type: 'singleLineText', name: 'Name', options: { defaultValue: 'Project' } }, + { + type: 'rating', + name: 'Priority', + options: { max: 5, icon: 'star', color: 'yellowBright' }, }, - }, - ], - }); + { + type: 'singleSelect', + name: 'Status', + options: { + choices: [ + { name: 'Todo', color: 'blue' }, + { name: 'Doing', color: 'yellow' }, + { name: 'Done', color: 'green' }, + ], + }, + }, + ], + }); - expect(commandResult.isOk()).toBe(true); - if (commandResult.isErr()) return; + expect(commandResult.isOk()).toBe(true); + if (commandResult.isErr()) return; - const actorIdResult = ActorId.create('system'); - expect(actorIdResult.isOk()).toBe(true); - if (actorIdResult.isErr()) return; + const actorIdResult = ActorId.create('system'); + expect(actorIdResult.isOk()).toBe(true); + if (actorIdResult.isErr()) return; - const context = { actorId: actorIdResult.value }; - const result = await handler.handle(context, commandResult.value); - expect(result.isOk()).toBe(true); - if (result.isErr()) return; + const context = { actorId: actorIdResult.value }; + const result = await handler.handle(context, commandResult.value); + expect(result.isOk()).toBe(true); + if (result.isErr()) return; - expect(eventPublisher.events().some((e) => e instanceof TableCreated)).toBe(true); - expect(result.value.table.primaryFieldId().equals(result.value.table.fields()[0].id())).toBe( - true - ); - expect(result.value.table.baseId().equals(baseId)).toBe(true); + expect(infoSpy).toHaveBeenCalledWith( + 'CreateTableHandler.start', + expect.objectContaining({ + actorId: 'system', + baseId: baseId.toString(), + tableName: 'Projects', + fieldCount: 3, + viewCount: 1, + }) + ); + expect(infoSpy).toHaveBeenCalledWith( + 'CreateTableHandler.success', + expect.objectContaining({ + baseId: baseId.toString(), + tableId: result.value.table.id().toString(), + eventCount: result.value.events.length, + }) + ); - const specResult = Table.specs(baseId).byId(result.value.table.id()).build(); - expect(specResult.isOk()).toBe(true); - if (specResult.isErr()) return; - const savedResult = await tableRepository.findOne(context, specResult.value); - expect(savedResult.isOk()).toBe(true); - if (savedResult.isOk()) { - expect(savedResult.value.primaryFieldId().equals(result.value.table.primaryFieldId())).toBe( + expect(eventPublisher.events().some((e) => e instanceof TableCreated)).toBe(true); + expect(result.value.table.primaryFieldId().equals(result.value.table.fields()[0].id())).toBe( true ); + expect(result.value.table.baseId().equals(baseId)).toBe(true); + + const specResult = Table.specs(baseId).byId(result.value.table.id()).build(); + expect(specResult.isOk()).toBe(true); + if (specResult.isErr()) return; + const savedResult = await tableRepository.findOne(context, specResult.value); + expect(savedResult.isOk()).toBe(true); + if (savedResult.isOk()) { + expect(savedResult.value.primaryFieldId().equals(result.value.table.primaryFieldId())).toBe( + true + ); + } + } finally { + infoSpy.mockRestore(); } });