@mwcp/kmore
Version:
midway component for knex, supports declarative transaction and OpenTelemetry
234 lines (196 loc) • 7.44 kB
text/typescript
import assert from 'node:assert'
import { relative } from 'node:path'
import {
App,
ApplicationContext,
IMidwayContainer,
Inject,
Singleton,
} from '@midwayjs/core'
import {
AttrNames,
Attributes,
Trace,
TraceLog,
TraceScopeType,
TraceService,
} from '@mwcp/otel'
import { Application, Context, MConfig, getWebContext } from '@mwcp/share'
import {
type HookReturn,
type KmoreTransaction,
type TransactionHookOptions,
type TransactionPreHookOptions,
genKmoreTrxId,
} from 'kmore'
import { genCommonAttr } from '../trace.helper.js'
import { TrxStatusService } from '../trx-status.service.js'
import { ConfigKey, DbConfig, KmoreAttrNames, KmoreSourceConfig } from '../types.js'
import { type ProcessTrxCommitAndRollbackData, processTrxCommitAndRollback } from './db-hook.trx.helper.js'
()
export class DbHookTrx<SourceName extends string = string> {
(ConfigKey.config) private readonly sourceConfig: KmoreSourceConfig<SourceName>
() readonly app: Application
() readonly applicationContext: IMidwayContainer
() readonly appDir: string
() readonly baseDir: string
() readonly trxStatusSvc: TrxStatusService
() readonly traceService: TraceService
getDbConfigByDbId(dbId: SourceName): DbConfig | undefined {
assert(dbId)
const dbConfig = this.sourceConfig.dataSource[dbId]
return dbConfig
}
getWebContext(): Context | undefined {
return getWebContext(this.applicationContext)
}
getTraceScopeByTrx(transaction: KmoreTransaction): TraceScopeType {
return transaction.kmoreTrxId
}
// #region transactionPreHook
<DbHookTrx['transactionPreHook']>({
autoEndSpan: false, // end span in DbHook.afterCommitHook/afterRollbackHook
spanName([options]) {
const { kmore, config } = options
if (! config.kmoreTrxId) {
const entryKey = config.trxPropagateOptions?.entryKey ?? ''
const kmoreTrxId = genKmoreTrxId(`trx-${kmore.dbId}-`, entryKey)
config.kmoreTrxId = kmoreTrxId
}
const dbSourceName = options.kmore.dbId
return `Kmore ${dbSourceName} transaction`
},
before([options], decoratorContext) {
options.traceContext = decoratorContext.traceContext ?? this.traceService.getActiveContext() // necessary for update trace context correctly
return null
},
after([options]: [TransactionPreHookOptions], _res, decoratorContext) {
const { kmore, config } = options
if (! config.kmoreTrxId) {
const entryKey = config.trxPropagateOptions?.entryKey ?? ''
const kmoreTrxId = genKmoreTrxId(`trx-${kmore.dbId}-`, entryKey)
config.kmoreTrxId = kmoreTrxId
}
const { kmoreTrxId } = config
assert(config.kmoreTrxId, 'transactionPreHook() after: kmoreTrxId is empty')
const activeTraceCtx = decoratorContext.traceContext ?? this.traceService.getActiveContext()
assert(activeTraceCtx, 'transactionPostHook() before: activeTraceCtx is empty')
this.trxStatusSvc.setTraceContextByScope(kmoreTrxId, activeTraceCtx)
if (! config.scope) {
config.scope = this.getWebContext() ?? this.app
}
const events = genCommonAttr(KmoreAttrNames.TrxCreateStart)
const attrs: Attributes = {
dbId: kmore.dbId,
kmoreTrxId: kmoreTrxId.toString(),
}
return { attrs, events }
},
})
async transactionPreHook(this: DbHookTrx, options: TransactionPreHookOptions): Promise<HookReturn | undefined> {
const { traceContext } = options
if (traceContext) {
return { traceContext } // necessary for update trace context correctly
}
return
}
// #region transactionPostHook
<DbHookTrx['transactionPostHook']>({
before([options]) {
const { kmore, config, trx } = options
const { kmoreTrxId } = config
assert(kmoreTrxId, 'transactionPostHook() kmoreTrxId is empty')
let attrs: Attributes = {
dbId: kmore.dbId,
kmoreTrxId: kmoreTrxId.toString(),
}
const { trxPropagateOptions } = trx
if (trxPropagateOptions) {
const path = trxPropagateOptions.path
? relative(this.appDir, trxPropagateOptions.path.replace(/^file:\/{3}/u, '')).replaceAll('\\', '/')
: ''
const attrs2 = {
[AttrNames.TrxPropagationType]: trxPropagateOptions.type,
[AttrNames.TrxPropagationClass]: trxPropagateOptions.className,
[AttrNames.TrxPropagationFunc]: trxPropagateOptions.funcName,
[AttrNames.TrxPropagationPath]: path,
[AttrNames.TrxPropagationReadRowLockLevel]: trxPropagateOptions.readRowLockLevel,
[AttrNames.TrxPropagationWriteRowLockLevel]: trxPropagateOptions.writeRowLockLevel,
}
attrs = Object.assign(attrs, attrs2)
}
const events = genCommonAttr(KmoreAttrNames.TrxCreateEnd)
return { attrs, events }
},
})
async transactionPostHook(this: DbHookTrx, options: TransactionHookOptions): Promise<void> {
const { trx } = options
assert(trx.scope, 'transactionPostHook() trx.scope is empty')
}
// #region beforeCommit
<DbHookTrx['beforeCommitHook']>({
after([options]) {
const activeTraceCtx = this.traceService.getActiveContext()
void activeTraceCtx
const { kmore, trx } = options
const events = genCommonAttr(KmoreAttrNames.TrxCommitStart, {
dbId: kmore.dbId,
kmoreTrxId: trx.kmoreTrxId.toString(),
})
return { events }
},
})
async beforeCommitHook(this: DbHookTrx<SourceName>, options: TransactionHookOptions): Promise<void> {
void options
}
// #region afterCommit
<DbHookTrx['afterCommitHook']>({
before([options], decoratorContext) {
if (! decoratorContext.traceScope) {
decoratorContext.traceScope = this.getTraceScopeByTrx(options.trx)
}
const data: ProcessTrxCommitAndRollbackData = {
eventName: KmoreAttrNames.TrxCommitEnd,
hook: 'afterCommitHook',
stage: 'before',
op: 'commit',
}
return processTrxCommitAndRollback(options, decoratorContext, data)
},
})
async afterCommitHook(this: DbHookTrx<SourceName>, options: TransactionHookOptions): Promise<void> {
void options
}
// #region beforeRollback
<DbHookTrx['beforeRollbackHook']>({
after([options]) {
const { kmore, trx } = options
const events = genCommonAttr(KmoreAttrNames.TrxRollbackStart, {
dbId: kmore.dbId,
kmoreTrxId: trx.kmoreTrxId.toString(),
})
return { events }
},
})
async beforeRollbackHook(this: DbHookTrx<SourceName>, options: TransactionHookOptions): Promise<void> {
void options
}
// #region afterRollback
<DbHookTrx['afterRollbackHook']>({
before([options], decoratorContext) {
if (! decoratorContext.traceScope) {
decoratorContext.traceScope = this.getTraceScopeByTrx(options.trx)
}
const data: ProcessTrxCommitAndRollbackData = {
eventName: KmoreAttrNames.TrxRollbackEnd,
hook: 'afterRollbackHook',
stage: 'before',
op: 'rollback',
}
return processTrxCommitAndRollback(options, decoratorContext, data)
},
})
async afterRollbackHook(this: DbHookTrx<SourceName>, options: TransactionHookOptions): Promise<void> {
void options
}
}