UNPKG

@mwcp/kmore

Version:

midway component for knex, supports declarative transaction and OpenTelemetry

135 lines (106 loc) 4.83 kB
import assert from 'node:assert' import type { DecoratorExecutorParamBase, DecoratorMetaDataPayload } from '@mwcp/share' import { deepmerge } from '@mwcp/share' import { genError } from '@waiting/shared-core' import { PropagationType } from 'kmore' import { initRowLockOptions } from '##/lib/config.js' import { genCallerKey } from '##/lib/propagation/trx-status.helper.js' import type { CallerKey, RegisterTrxPropagateOptions } from '##/lib/propagation/trx-status.types.js' import type { TrxStatusService } from '##/lib/trx-status.service.js' import { ConfigKey, Msg } from '##/lib/types.js' import type { DecoratorExecutorOptions, GenDecoratorExecutorOptionsExt, TransactionalOptions } from './transactional.types.js' export async function genDecoratorExecutorOptionsAsync<T extends object>( optionsBase: DecoratorExecutorParamBase<T>, optionsExt: GenDecoratorExecutorOptionsExt, ): Promise<DecoratorExecutorOptions> { const { mergedDecoratorParam } = optionsBase assert(optionsExt.propagationConfig, Msg.propagationConfigIsUndefined) const initArgs: TransactionalOptions = { dbSourceName: void 0, propagationType: PropagationType.REQUIRED, rowLockOptions: { ...initRowLockOptions, }, } assert(optionsExt.trxStatusSvc, 'genDecoratorExecutorOptionsAsync() optionsExt.trxStatusSvc is undefined') const args = deepmerge.all([ initArgs, mergedDecoratorParam ?? {}, ]) as DecoratorMetaDataPayload<TransactionalOptions> const ret: DecoratorExecutorOptions = { ...optionsBase, ...optionsExt, mergedDecoratorParam: args, } const { dbSourceName } = args if (! dbSourceName) { const dbInstanceCount = ret.trxStatusSvc.getDbInstanceCount() assert(dbInstanceCount <= 1, 'genDecoratorExecutorOptionsAsync(): dbSourceName is undefined, but multiple dbSources found') } return ret } export async function before(options: DecoratorExecutorOptions): Promise<void> { const { instanceName, mergedDecoratorParam, methodName, trxStatusSvc, } = options assert(mergedDecoratorParam?.rowLockOptions, 'mergedDecoratorParam.rowLockOptions is undefined') const type = mergedDecoratorParam.propagationType assert(type, 'propagationType is undefined') const { readRowLockLevel, writeRowLockLevel } = mergedDecoratorParam.rowLockOptions assert(readRowLockLevel, 'readRowLockLevel is undefined') assert(writeRowLockLevel, 'writeRowLockLevel is undefined') const className = instanceName assert(className, 'instance.constructor.name is undefined') const opts: RegisterTrxPropagateOptions = { dbSourceName: mergedDecoratorParam.dbSourceName, // can be undefined if only one dbSource scope: options.scope, type, className, funcName: methodName, readRowLockLevel, writeRowLockLevel, } assert(opts.type, 'opts.type propagationType is undefined') assert(opts.readRowLockLevel, 'opts.readRowLockLevel is undefined') assert(opts.writeRowLockLevel, 'opts.writeRowLockLevel is undefined') let callerKey: CallerKey | undefined = void 0 try { callerKey = trxStatusSvc.registerPropagation(opts) options.callerKey = callerKey } catch (ex) { // console.error(msg, ex) const error = genError({ error: ex, altMessage: Msg.registerPropagationFailed }) const key = genCallerKey(opts.className, opts.funcName) options.callerKey = key options.error = error throw error } assert(callerKey, `[@mwcp/${ConfigKey.namespace}] generate callerKey failed`) } export async function afterReturn(options: DecoratorExecutorOptions): Promise<void> { const { trxStatusSvc, callerKey, mergedDecoratorParam } = options if (! callerKey) { return } assert(! options.error, `[@mwcp/${ConfigKey.namespace}] options.error is not undefined in afterAsync(). It should be handled in after() lifecycle redirect to afterThrow() with errorExt. Error: ${options.error?.message}`) await trxStatusSvc.tryCommitTrxIfKeyIsEntryTop(mergedDecoratorParam?.dbSourceName, options.scope, callerKey) } export function afterThrow(options: DecoratorExecutorOptions, trxStatusService: TrxStatusService): never | Promise<never> { const { error } = options assert(error instanceof Error, `[@mwcp/${ConfigKey.namespace}] ${ConfigKey.Transactional}() afterThrow error is not instance of Error`) const { callerKey, mergedDecoratorParam } = options const key = callerKey ?? genCallerKey(options.instanceName, options.methodName) const tkey = trxStatusService.retrieveUniqueTopCallerKey(mergedDecoratorParam?.dbSourceName, options.scope, key) if (tkey && tkey === key) { if (options.methodIsAsyncFunction) { return trxStatusService.trxRollbackEntry(mergedDecoratorParam?.dbSourceName, options.scope, key).then(() => { throw error }) } } throw error }