Comparing version 33.3.0 to 34.0.0
@@ -10,2 +10,3 @@ export const defaultPropDescriptor = { | ||
identifier: void 0, | ||
kmoreQueryId: void 0, | ||
kUid: '', | ||
@@ -12,0 +13,0 @@ queryUid: '', |
@@ -1,8 +0,27 @@ | ||
import type { Knex } from 'knex'; | ||
import { EventCallbacks, OnQueryData, OnQueryErrorData, OnQueryErrorErr, OnQueryRespRaw, QueryResponse } from './types.js'; | ||
export declare function callCbOnStart<Ctx = unknown>(ctx: Ctx | undefined, dbId: string, cbs: EventCallbacks<Ctx> | undefined, identifier: unknown, builder: Knex.QueryBuilder): Promise<void>; | ||
export declare function callCbOnQuery<Ctx = unknown>(ctx: Ctx | undefined, dbId: string, cbs: EventCallbacks<Ctx> | undefined, identifier: unknown, data: OnQueryData): Promise<void>; | ||
export declare function callCbOnQueryResp<Ctx = unknown>(ctx: Ctx | undefined, dbId: string, cbs: EventCallbacks<Ctx> | undefined, identifier: unknown, _resp: QueryResponse, // not used | ||
respRaw: OnQueryRespRaw): Promise<void>; | ||
export declare function callCbOnQueryError<Ctx = unknown>(ctx: Ctx | undefined, dbId: string, cbs: EventCallbacks<Ctx> | undefined, identifier: unknown, err: OnQueryErrorErr, data: OnQueryErrorData): Promise<void>; | ||
import { EventCallbacks, KmoreQueryBuilder, OnQueryData, OnQueryErrorData, OnQueryErrorErr, OnQueryRespRaw, QueryResponse } from './types.js'; | ||
export interface CallCbOptionsBase<Ctx = any> { | ||
ctx: Ctx | undefined; | ||
dbId: string; | ||
cbs: EventCallbacks<Ctx> | undefined; | ||
identifier: unknown; | ||
kmoreQueryId: symbol; | ||
} | ||
export interface CallCbOnStartOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
builder: KmoreQueryBuilder; | ||
} | ||
export declare function callCbOnStart(options: CallCbOnStartOptions): Promise<void>; | ||
export interface CallCbOnQueryOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
data: OnQueryData; | ||
} | ||
export declare function callCbOnQuery(options: CallCbOnQueryOptions): Promise<void>; | ||
export interface CallCbOnQueryRespOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
_resp: QueryResponse; | ||
respRaw: OnQueryRespRaw; | ||
} | ||
export declare function callCbOnQueryResp(options: CallCbOnQueryRespOptions): Promise<void>; | ||
export interface CallCbOnQueryErrorOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
err: OnQueryErrorErr; | ||
data: OnQueryErrorData; | ||
} | ||
export declare function callCbOnQueryError(options: CallCbOnQueryErrorOptions): Promise<void>; | ||
//# sourceMappingURL=event.d.ts.map |
@@ -0,61 +1,61 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { initKmoreEvent } from './config.js'; | ||
export async function callCbOnStart(ctx, dbId, cbs, identifier, builder) { | ||
const cb = cbs?.start; | ||
export async function callCbOnStart(options) { | ||
const cb = options.cbs?.start; | ||
if (typeof cb === 'function') { | ||
const event = processKnexOnEvent({ | ||
type: 'start', | ||
identifier, | ||
identifier: options.identifier, | ||
data: void 0, | ||
queryUid: '', | ||
queryBuilder: builder, | ||
queryBuilder: options.builder, | ||
}); | ||
event.dbId = dbId; | ||
await cb(event, ctx); | ||
event.dbId = options.dbId; | ||
await cb(event, options.ctx); | ||
} | ||
} | ||
export async function callCbOnQuery(ctx, dbId, cbs, identifier, data) { | ||
const cb = cbs?.query; | ||
export async function callCbOnQuery(options) { | ||
const cb = options.cbs?.query; | ||
if (typeof cb === 'function') { | ||
const queryUid = pickQueryUidFrom(data); | ||
const queryUid = pickQueryUidFrom(options.data); | ||
const event = processKnexOnEvent({ | ||
type: 'query', | ||
identifier, | ||
data, | ||
identifier: options.identifier, | ||
data: options.data, | ||
queryUid, | ||
queryBuilder: void 0, | ||
}); | ||
event.dbId = dbId; | ||
await cb(event, ctx); | ||
event.dbId = options.dbId; | ||
await cb(event, options.ctx); | ||
} | ||
} | ||
export async function callCbOnQueryResp(ctx, dbId, cbs, identifier, _resp, // not used | ||
respRaw) { | ||
const cb = cbs?.queryResponse; | ||
export async function callCbOnQueryResp(options) { | ||
const cb = options.cbs?.queryResponse; | ||
if (typeof cb === 'function') { | ||
const queryUid = pickQueryUidFrom(respRaw); | ||
const queryUid = pickQueryUidFrom(options.respRaw); | ||
const event = processKnexOnEvent({ | ||
type: 'queryResponse', | ||
identifier, | ||
respRaw, | ||
identifier: options.identifier, | ||
respRaw: options.respRaw, | ||
queryUid, | ||
queryBuilder: void 0, | ||
}); | ||
event.dbId = dbId; | ||
await cb(event, ctx); | ||
event.dbId = options.dbId; | ||
await cb(event, options.ctx); | ||
} | ||
} | ||
export async function callCbOnQueryError(ctx, dbId, cbs, identifier, err, data) { | ||
const cb = cbs?.queryError; | ||
export async function callCbOnQueryError(options) { | ||
const cb = options.cbs?.queryError; | ||
if (typeof cb === 'function') { | ||
const queryUid = pickQueryUidFrom(data); | ||
const queryUid = pickQueryUidFrom(options.data); | ||
const event = processKnexOnEvent({ | ||
type: 'queryError', | ||
identifier, | ||
exError: err, | ||
exData: data, | ||
identifier: options.identifier, | ||
exError: options.err, | ||
exData: options.data, | ||
queryUid, | ||
queryBuilder: void 0, | ||
}); | ||
event.dbId = dbId; | ||
await cb(event, ctx); | ||
event.dbId = options.dbId; | ||
await cb(event, options.ctx); | ||
} | ||
@@ -62,0 +62,0 @@ } |
import type { DbDict } from 'kmore-types'; | ||
import type { Knex } from 'knex'; | ||
import { postProcessResponse } from './helper.js'; | ||
import { CaseType, DbQueryBuilder, EventCallbacks, KnexConfig, QueryContext, QuerySpanInfo } from './types.js'; | ||
import { CaseType, DbQueryBuilder, EventCallbacks, KmoreQueryBuilder, KmoreTransaction, KmoreTransactionConfig, KnexConfig, QueryContext, QuerySpanInfo } from './types.js'; | ||
export declare class Kmore<D = any, Context = any> { | ||
@@ -42,2 +42,15 @@ /** | ||
readonly postProcessResponseSet: Set<typeof postProcessResponse>; | ||
readonly trxActionOnError: KmoreTransactionConfig['trxActionOnError']; | ||
/** | ||
* kmoreTrxid => trx | ||
*/ | ||
readonly trxMap: Map<symbol, KmoreTransaction>; | ||
/** | ||
* kmoreTrxId => Set<kmoreQueryId> | ||
*/ | ||
readonly trxIdQueryMap: TrxIdQueryMap; | ||
/** | ||
* context => Set<kmoreTrxId> | ||
*/ | ||
readonly ctxTrxIdMap: WeakMap<object, Set<symbol>>; | ||
readonly config: KnexConfig; | ||
@@ -54,7 +67,14 @@ readonly dict: unknown extends D ? undefined : DbDict<D>; | ||
*/ | ||
transaction(id?: PropertyKey, config?: Knex.TransactionConfig): Promise<Knex.Transaction & { | ||
kmoreTrxId: symbol; | ||
}>; | ||
transaction(id?: PropertyKey, config?: KmoreTransactionConfig): Promise<KmoreTransaction>; | ||
getTrxByKmoreQueryId(kmoreQueryId: symbol): KmoreTransaction | undefined; | ||
getTrxByKmoreTrxId(id: symbol): KmoreTransaction | undefined; | ||
setCtxTrxIdMap(ctx: unknown, kmoreTrxId: symbol): void; | ||
getTrxSetByCtx(ctx: unknown): Set<KmoreTransaction>; | ||
doTrxActionOnError(trx: KmoreTransaction | undefined): Promise<void>; | ||
protected createTrxProxy(trx: KmoreTransaction): KmoreTransaction; | ||
protected createRefTables<P extends string>(prefix: P, caseConvert: CaseType): DbQueryBuilder<Context, D, P, CaseType>; | ||
protected extRefTableFnProperty(refName: string, caseConvert: CaseType, ctx: Context | undefined): Knex.QueryBuilder; | ||
protected extRefTableFnProperty(refName: string, caseConvert: CaseType, ctx: Context | undefined): KmoreQueryBuilder; | ||
protected extRefTableFnPropertyCallback(refTable: KmoreQueryBuilder, caseConvert: CaseType, ctx: Context | undefined, kmoreQueryId: symbol): KmoreQueryBuilder; | ||
protected extRefTableFnPropertyTransacting(refTable: KmoreQueryBuilder, ctx: Context | undefined): KmoreQueryBuilder; | ||
protected extRefTableFnPropertyThen(refTable: KmoreQueryBuilder): KmoreQueryBuilder; | ||
protected postProcessResponse(result: any, queryContext?: QueryContext): unknown; | ||
@@ -81,5 +101,16 @@ } | ||
wrapIdentifierCaseConvert?: CaseType | undefined; | ||
/** | ||
* Atuo trsaction action (rollback|commit|none) on error (Rejection or Exception), | ||
* @CAUTION **Will always rollback if query error in database even though this value set to 'commit'** | ||
* @default rollback | ||
*/ | ||
trxActionOnError?: KmoreTransactionConfig['trxActionOnError']; | ||
} | ||
export declare function KmoreFactory<D, Ctx = unknown>(options: KmoreFactoryOpts<D, Ctx>): Kmore<D, Ctx>; | ||
export declare function createDbh(knexConfig: KnexConfig): Knex; | ||
/** | ||
* kmoreTrxId => Set<kmoreQueryId> | ||
*/ | ||
declare type TrxIdQueryMap = Map<symbol, Set<symbol>>; | ||
export {}; | ||
//# sourceMappingURL=kmore.d.ts.map |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
@@ -7,3 +8,3 @@ /* eslint-disable import/no-extraneous-dependencies */ | ||
import { defaultPropDescriptor, initialConfig } from './config.js'; | ||
import { callCbOnQuery, callCbOnQueryError, callCbOnQueryResp, callCbOnStart } from './event.js'; | ||
import { callCbOnQuery, callCbOnQueryError, callCbOnQueryResp, callCbOnStart, } from './event.js'; | ||
import { postProcessResponse, wrapIdentifier } from './helper.js'; | ||
@@ -49,2 +50,15 @@ import { CaseType, } from './types.js'; | ||
postProcessResponseSet = new Set(); | ||
trxActionOnError = 'rollback'; | ||
/** | ||
* kmoreTrxid => trx | ||
*/ | ||
trxMap = new Map(); | ||
/** | ||
* kmoreTrxId => Set<kmoreQueryId> | ||
*/ | ||
trxIdQueryMap = new Map(); | ||
/** | ||
* context => Set<kmoreTrxId> | ||
*/ | ||
ctxTrxIdMap = new WeakMap(); | ||
config; | ||
@@ -93,2 +107,5 @@ dict; | ||
this.config.postProcessResponse = (result, queryContext) => this.postProcessResponse(result, queryContext); | ||
if (options.trxActionOnError) { | ||
this.trxActionOnError = options.trxActionOnError; | ||
} | ||
this.refTables = this.createRefTables('ref_', CaseType.none); | ||
@@ -107,4 +124,4 @@ this.camelTables = this.createRefTables('ref_', CaseType.camel); | ||
: id ? Symbol(id) : Symbol(Date.now()); | ||
const trx = await this.dbh.transaction(void 0, config); | ||
Object.defineProperty(trx, 'kmoreTrxId', { | ||
const tmp = await this.dbh.transaction(void 0, config); | ||
Object.defineProperty(tmp, 'kmoreTrxId', { | ||
...defaultPropDescriptor, | ||
@@ -114,4 +131,108 @@ enumerable: false, | ||
}); | ||
const trxActionOnError = config?.trxActionOnError | ||
?? this.trxActionOnError ?? 'rollback'; | ||
Object.defineProperty(tmp, 'trxActionOnError', { | ||
...defaultPropDescriptor, | ||
enumerable: false, | ||
value: trxActionOnError, | ||
}); | ||
if (trxActionOnError === 'none') { | ||
return tmp; | ||
} | ||
const trx = this.createTrxProxy(tmp); | ||
this.trxMap.set(kmoreTrxId, trx); | ||
this.trxIdQueryMap.set(kmoreTrxId, new Set()); | ||
return trx; | ||
} | ||
getTrxByKmoreQueryId(kmoreQueryId) { | ||
for (const [trxId, queryIdSet] of this.trxIdQueryMap.entries()) { | ||
if (queryIdSet.has(kmoreQueryId)) { | ||
const trx = this.trxMap.get(trxId); | ||
if (trx) { | ||
return trx; | ||
} | ||
} | ||
} | ||
} | ||
getTrxByKmoreTrxId(id) { | ||
return this.trxMap.get(id); | ||
} | ||
setCtxTrxIdMap(ctx, kmoreTrxId) { | ||
if (!ctx || !kmoreTrxId || typeof ctx !== 'object') { | ||
return; | ||
} | ||
if (!this.ctxTrxIdMap.get(ctx)) { | ||
this.ctxTrxIdMap.set(ctx, new Set()); | ||
} | ||
this.ctxTrxIdMap.get(ctx)?.add(kmoreTrxId); | ||
} | ||
getTrxSetByCtx(ctx) { | ||
const ret = new Set(); | ||
if (!ctx || typeof ctx !== 'object') { | ||
return ret; | ||
} | ||
const trxIdMap = this.ctxTrxIdMap.get(ctx); | ||
trxIdMap?.forEach((kmoreTrxId) => { | ||
const trx = this.trxMap.get(kmoreTrxId); | ||
if (trx) { | ||
ret.add(trx); | ||
} | ||
}); | ||
return ret; | ||
} | ||
async doTrxActionOnError(trx) { | ||
if (!trx) { | ||
return; | ||
} | ||
if (!trx.isCompleted()) { | ||
this.trxMap.delete(trx.kmoreTrxId); | ||
} | ||
switch (trx.trxActionOnError) { | ||
case 'rollback': { | ||
await trx.rollback(); | ||
this.trxMap.delete(trx.kmoreTrxId); | ||
this.trxIdQueryMap.delete(trx.kmoreTrxId); | ||
break; | ||
} | ||
case 'commit': { | ||
await trx.commit(); | ||
this.trxMap.delete(trx.kmoreTrxId); | ||
this.trxIdQueryMap.delete(trx.kmoreTrxId); | ||
break; | ||
} | ||
default: | ||
break; | ||
} | ||
} | ||
createTrxProxy(trx) { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const commit = new Proxy(trx.commit, { | ||
apply: (target, ctx, args) => { | ||
this.trxIdQueryMap.delete(ctx.kmoreTrxId); | ||
this.trxMap.delete(ctx.kmoreTrxId); | ||
return Reflect.apply(target, ctx, args); | ||
}, | ||
}); | ||
Object.defineProperty(trx, 'commit', { | ||
enumerable: true, | ||
writable: true, | ||
configurable: false, | ||
value: commit, | ||
}); | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const rollback = new Proxy(trx.rollback, { | ||
apply: (target, ctx, args) => { | ||
this.trxIdQueryMap.delete(ctx.kmoreTrxId); | ||
this.trxMap.delete(ctx.kmoreTrxId); | ||
return Reflect.apply(target, ctx, args); | ||
}, | ||
}); | ||
Object.defineProperty(trx, 'rollback', { | ||
enumerable: true, | ||
writable: true, | ||
configurable: false, | ||
value: rollback, | ||
}); | ||
return trx; | ||
} | ||
createRefTables(prefix, caseConvert) { | ||
@@ -141,14 +262,98 @@ const rb = {}; | ||
assert(caseConvert, 'caseConvert must be defined'); | ||
const opts = { | ||
const kmoreQueryId = Symbol(`${this.dbId}-${Date.now()}`); | ||
let refTable = this.dbh(refName); | ||
refTable = this.extRefTableFnPropertyCallback(refTable, caseConvert, ctx, kmoreQueryId); | ||
refTable = this.extRefTableFnPropertyTransacting(refTable, ctx); | ||
refTable = this.extRefTableFnPropertyThen(refTable); | ||
void Object.defineProperty(refTable, 'kmoreQueryId', { | ||
...defaultPropDescriptor, | ||
value: kmoreQueryId, | ||
}); | ||
return refTable; | ||
} | ||
extRefTableFnPropertyCallback(refTable, caseConvert, ctx, kmoreQueryId) { | ||
assert(caseConvert, 'caseConvert must be defined'); | ||
const queryCtxOpts = { | ||
wrapIdentifierCaseConvert: this.wrapIdentifierCaseConvert, | ||
postProcessResponseCaseConvert: caseConvert, | ||
kmoreQueryId, | ||
}; | ||
const refTable = this.dbh(refName) | ||
.queryContext(opts) | ||
.on('start', async (builder) => callCbOnStart(ctx, this.dbId, this.eventCallbacks, this.instanceId, builder)) | ||
.on('query', async (data) => callCbOnQuery(ctx, this.dbId, this.eventCallbacks, this.instanceId, data)) | ||
.on('query-response', async (resp, respRaw) => callCbOnQueryResp(ctx, this.dbId, this.eventCallbacks, this.instanceId, resp, respRaw)) | ||
.on('query-error', async (err, data) => callCbOnQueryError(ctx, this.dbId, this.eventCallbacks, this.instanceId, err, data)); | ||
const opts = { | ||
ctx, | ||
dbId: this.dbId, | ||
cbs: this.eventCallbacks, | ||
identifier: this.instanceId, | ||
kmoreQueryId, | ||
}; | ||
const refTable2 = refTable | ||
.queryContext(queryCtxOpts) | ||
.on('start', async (builder) => callCbOnStart({ | ||
...opts, | ||
builder, | ||
})) | ||
.on('query', async (data) => callCbOnQuery({ | ||
...opts, | ||
data, | ||
})) | ||
.on('query-response', async (resp, respRaw) => callCbOnQueryResp({ | ||
...opts, | ||
_resp: resp, | ||
respRaw, | ||
})) | ||
.on('query-error', async (err, data) => { | ||
const trx = this.getTrxByKmoreQueryId(kmoreQueryId); | ||
await this.doTrxActionOnError(trx); | ||
return callCbOnQueryError({ | ||
...opts, | ||
err, | ||
data, | ||
}); | ||
}); | ||
return refTable2; | ||
} | ||
extRefTableFnPropertyTransacting(refTable, ctx) { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const ts = new Proxy(refTable.transacting, { | ||
apply: (target, ctx2, args) => { | ||
const [trx] = args; | ||
assert(trx?.isTransaction === true, 'trx must be a transaction'); | ||
const { kmoreTrxId } = trx; | ||
const qid = ctx2.kmoreQueryId; | ||
if (qid && kmoreTrxId) { | ||
this.trxIdQueryMap.get(kmoreTrxId)?.add(qid); | ||
this.setCtxTrxIdMap(ctx, kmoreTrxId); | ||
} | ||
return Reflect.apply(target, ctx2, args); | ||
}, | ||
}); | ||
void Object.defineProperty(refTable, 'transacting', { | ||
...defaultPropDescriptor, | ||
value: ts, | ||
}); | ||
return refTable; | ||
} | ||
extRefTableFnPropertyThen(refTable) { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const pm = new Proxy(refTable.then, { | ||
apply: (target, ctx2, args) => { | ||
const qid = ctx2.kmoreQueryId; | ||
const trx = this.getTrxByKmoreQueryId(qid); | ||
if (!trx) { | ||
return Reflect.apply(target, ctx2, args); | ||
} | ||
return Reflect.apply(target, ctx2, args) | ||
.catch(async (err) => { | ||
const trx2 = this.getTrxByKmoreQueryId(qid); | ||
await this.doTrxActionOnError(trx2); | ||
return Promise.reject(err); | ||
}); | ||
}, | ||
}); | ||
void Object.defineProperty(refTable, 'then', { | ||
...defaultPropDescriptor, | ||
configurable: true, | ||
value: pm, | ||
}); | ||
return refTable; | ||
} | ||
postProcessResponse(result, queryContext) { | ||
@@ -155,0 +360,0 @@ let ret = result; |
@@ -5,2 +5,22 @@ import { RecordCamelKeys, RecordPascalKeys, RecordSnakeKeys } from '@waiting/shared-types'; | ||
export declare type KnexConfig = Knex.Config; | ||
export declare type KmoreTransaction = Knex.Transaction & { | ||
kmoreTrxId: symbol; | ||
/** | ||
* Atuo trsaction action (rollback|commit|none) on error (Rejection or Exception), | ||
* @CAUTION **Will always rollback if query error in database even though this value set to 'commit'** | ||
* @default rollback | ||
*/ | ||
trxActionOnError: NonNullable<KmoreTransactionConfig['trxActionOnError']>; | ||
}; | ||
export declare type KmoreTransactionConfig = Knex.TransactionConfig & { | ||
/** | ||
* Atuo trsaction action (rollback|commit|none) on error (Rejection or Exception), | ||
* @CAUTION **Will always rollback if query error in database even though this value set to 'commit'** | ||
* @default rollback | ||
*/ | ||
trxActionOnError?: 'commit' | 'rollback' | 'none'; | ||
}; | ||
export declare type KmoreQueryBuilder<TRecord extends {} = any, TResult = any> = Knex.QueryBuilder<TRecord, TResult> & { | ||
kmoreQueryId: symbol; | ||
}; | ||
export declare enum EnumClient { | ||
@@ -27,3 +47,3 @@ pg = "pg", | ||
} | ||
export declare type TbQueryBuilder<TRecord, Context> = (ctx?: Context) => Knex.QueryBuilder<TRecord, TRecord[]>; | ||
export declare type TbQueryBuilder<TRecord, Context> = (ctx?: Context) => KmoreQueryBuilder<TRecord, TRecord[]>; | ||
export declare type EventType = 'query' | 'queryError' | 'queryResponse' | 'start' | 'unknown'; | ||
@@ -39,2 +59,3 @@ export interface KmoreEvent<T = unknown> { | ||
queryUid: string; | ||
kmoreQueryId: symbol | undefined; | ||
/** | ||
@@ -61,2 +82,3 @@ * @description Note: may keep value of the latest transaction id, | ||
postProcessResponseCaseConvert: CaseType; | ||
kmoreQueryId: symbol; | ||
} | ||
@@ -63,0 +85,0 @@ export interface OnQueryData { |
{ | ||
"name": "kmore", | ||
"author": "waiting", | ||
"version": "33.3.0", | ||
"version": "34.0.0", | ||
"description": "A SQL query builder based on knex with powerful TypeScript type support", | ||
@@ -45,3 +45,3 @@ "keywords": [ | ||
"cross-env": "7", | ||
"kmore-types": "^33.1.1", | ||
"kmore-types": "^34.0.0", | ||
"knex": "^2.2.0", | ||
@@ -77,3 +77,3 @@ "pg": "^8.7.3" | ||
}, | ||
"gitHead": "a435ff4a6d65356f44f034b68895225bd421f498" | ||
"gitHead": "29051a2cc393ff5d25a4faadf67b90889b404121" | ||
} |
@@ -15,2 +15,3 @@ import type { KmoreEvent, KnexConfig } from './types.js' | ||
identifier: void 0, | ||
kmoreQueryId: void 0, | ||
kUid: '', | ||
@@ -17,0 +18,0 @@ queryUid: '', |
@@ -1,3 +0,2 @@ | ||
import type { Knex } from 'knex' | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { initKmoreEvent } from './config.js' | ||
@@ -7,2 +6,3 @@ import { | ||
KmoreEvent, | ||
KmoreQueryBuilder, | ||
OnQueryData, | ||
@@ -16,93 +16,90 @@ OnQueryErrorData, | ||
export async function callCbOnStart<Ctx = unknown>( | ||
ctx: Ctx | undefined, | ||
dbId: string, | ||
cbs: EventCallbacks<Ctx> | undefined, | ||
identifier: unknown, | ||
builder: Knex.QueryBuilder, | ||
): Promise<void> { | ||
export interface CallCbOptionsBase<Ctx = any> { | ||
ctx: Ctx | undefined | ||
dbId: string | ||
cbs: EventCallbacks<Ctx> | undefined | ||
identifier: unknown | ||
kmoreQueryId: symbol | ||
} | ||
const cb: EventCallbacks<Ctx>['start'] = cbs?.start | ||
export interface CallCbOnStartOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
builder: KmoreQueryBuilder | ||
} | ||
export async function callCbOnStart(options: CallCbOnStartOptions): Promise<void> { | ||
const cb: EventCallbacks['start'] = options.cbs?.start | ||
if (typeof cb === 'function') { | ||
const event = processKnexOnEvent({ | ||
type: 'start', | ||
identifier, | ||
identifier: options.identifier, | ||
data: void 0, | ||
queryUid: '', | ||
queryBuilder: builder, | ||
queryBuilder: options.builder, | ||
}) | ||
event.dbId = dbId | ||
await cb(event, ctx) | ||
event.dbId = options.dbId | ||
await cb(event, options.ctx) | ||
} | ||
} | ||
export async function callCbOnQuery<Ctx = unknown>( | ||
ctx: Ctx | undefined, | ||
dbId: string, | ||
cbs: EventCallbacks<Ctx> | undefined, | ||
identifier: unknown, | ||
data: OnQueryData, | ||
): Promise<void> { | ||
export interface CallCbOnQueryOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
data: OnQueryData | ||
} | ||
const cb: EventCallbacks<Ctx>['query'] = cbs?.query | ||
export async function callCbOnQuery(options: CallCbOnQueryOptions): Promise<void> { | ||
const cb: EventCallbacks['query'] = options.cbs?.query | ||
if (typeof cb === 'function') { | ||
const queryUid = pickQueryUidFrom(data) | ||
const queryUid = pickQueryUidFrom(options.data) | ||
const event = processKnexOnEvent({ | ||
type: 'query', | ||
identifier, | ||
data, | ||
identifier: options.identifier, | ||
data: options.data, | ||
queryUid, | ||
queryBuilder: void 0, | ||
}) | ||
event.dbId = dbId | ||
await cb(event, ctx) | ||
event.dbId = options.dbId | ||
await cb(event, options.ctx) | ||
} | ||
} | ||
export async function callCbOnQueryResp<Ctx = unknown>( | ||
ctx: Ctx | undefined, | ||
dbId: string, | ||
cbs: EventCallbacks<Ctx> | undefined, | ||
identifier: unknown, | ||
_resp: QueryResponse, // not used | ||
respRaw: OnQueryRespRaw, | ||
): Promise<void> { | ||
export interface CallCbOnQueryRespOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
_resp: QueryResponse | ||
respRaw: OnQueryRespRaw | ||
} | ||
const cb: EventCallbacks<Ctx>['queryResponse'] = cbs?.queryResponse | ||
export async function callCbOnQueryResp(options: CallCbOnQueryRespOptions): Promise<void> { | ||
const cb: EventCallbacks['queryResponse'] = options.cbs?.queryResponse | ||
if (typeof cb === 'function') { | ||
const queryUid = pickQueryUidFrom(respRaw) | ||
const queryUid = pickQueryUidFrom(options.respRaw) | ||
const event = processKnexOnEvent({ | ||
type: 'queryResponse', | ||
identifier, | ||
respRaw, | ||
identifier: options.identifier, | ||
respRaw: options.respRaw, | ||
queryUid, | ||
queryBuilder: void 0, | ||
}) | ||
event.dbId = dbId | ||
await cb(event, ctx) | ||
event.dbId = options.dbId | ||
await cb(event, options.ctx) | ||
} | ||
} | ||
export async function callCbOnQueryError<Ctx = unknown>( | ||
ctx: Ctx | undefined, | ||
dbId: string, | ||
cbs: EventCallbacks<Ctx> | undefined, | ||
identifier: unknown, | ||
err: OnQueryErrorErr, | ||
data: OnQueryErrorData, | ||
): Promise<void> { | ||
export interface CallCbOnQueryErrorOptions<Ctx = any> extends CallCbOptionsBase<Ctx> { | ||
err: OnQueryErrorErr | ||
data: OnQueryErrorData | ||
} | ||
const cb: EventCallbacks<Ctx>['queryError'] = cbs?.queryError | ||
export async function callCbOnQueryError(options: CallCbOnQueryErrorOptions): Promise<void> { | ||
const cb: EventCallbacks['queryError'] = options.cbs?.queryError | ||
if (typeof cb === 'function') { | ||
const queryUid = pickQueryUidFrom(data) | ||
const queryUid = pickQueryUidFrom(options.data) | ||
const event = processKnexOnEvent({ | ||
type: 'queryError', | ||
identifier, | ||
exError: err, | ||
exData: data, | ||
identifier: options.identifier, | ||
exError: options.err, | ||
exData: options.data, | ||
queryUid, | ||
queryBuilder: void 0, | ||
}) | ||
event.dbId = dbId | ||
await cb(event, ctx) | ||
event.dbId = options.dbId | ||
await cb(event, options.ctx) | ||
} | ||
@@ -109,0 +106,0 @@ } |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
@@ -11,3 +12,9 @@ /* eslint-disable import/no-extraneous-dependencies */ | ||
import { defaultPropDescriptor, initialConfig } from './config.js' | ||
import { callCbOnQuery, callCbOnQueryError, callCbOnQueryResp, callCbOnStart } from './event.js' | ||
import { | ||
callCbOnQuery, | ||
callCbOnQueryError, | ||
callCbOnQueryResp, | ||
callCbOnStart, | ||
CallCbOptionsBase, | ||
} from './event.js' | ||
import { PostProcessInput, postProcessResponse, wrapIdentifier } from './helper.js' | ||
@@ -18,2 +25,5 @@ import { | ||
EventCallbacks, | ||
KmoreQueryBuilder, | ||
KmoreTransaction, | ||
KmoreTransactionConfig, | ||
KnexConfig, | ||
@@ -75,10 +85,23 @@ OnQueryData, | ||
readonly postProcessResponseSet = new Set<typeof postProcessResponse>() | ||
readonly trxActionOnError: KmoreTransactionConfig['trxActionOnError'] = 'rollback' | ||
/** | ||
* kmoreTrxid => trx | ||
*/ | ||
readonly trxMap = new Map<symbol, KmoreTransaction>() | ||
/** | ||
* kmoreTrxId => Set<kmoreQueryId> | ||
*/ | ||
readonly trxIdQueryMap: TrxIdQueryMap = new Map() | ||
/** | ||
* context => Set<kmoreTrxId> | ||
*/ | ||
readonly ctxTrxIdMap = new WeakMap<object, Set<symbol>>() | ||
public readonly config: KnexConfig | ||
public readonly dict: unknown extends D ? undefined : DbDict<D> | ||
public readonly dbId: string | ||
public readonly dbh: Knex | ||
public readonly instanceId: string | symbol | ||
public readonly eventCallbacks: EventCallbacks<Context> | undefined | ||
public readonly wrapIdentifierCaseConvert: CaseType | ||
readonly config: KnexConfig | ||
readonly dict: unknown extends D ? undefined : DbDict<D> | ||
readonly dbId: string | ||
readonly dbh: Knex | ||
readonly instanceId: string | symbol | ||
readonly eventCallbacks: EventCallbacks<Context> | undefined | ||
readonly wrapIdentifierCaseConvert: CaseType | ||
@@ -131,2 +154,6 @@ | ||
if (options.trxActionOnError) { | ||
this.trxActionOnError = options.trxActionOnError | ||
} | ||
this.refTables = this.createRefTables<'ref_'>('ref_', CaseType.none) | ||
@@ -146,4 +173,4 @@ this.camelTables = this.createRefTables<'ref_'>('ref_', CaseType.camel) | ||
id?: PropertyKey, | ||
config?: Knex.TransactionConfig, | ||
): Promise<Knex.Transaction & { kmoreTrxId: symbol }> { | ||
config?: KmoreTransactionConfig, | ||
): Promise<KmoreTransaction> { | ||
@@ -154,5 +181,5 @@ const kmoreTrxId = typeof id === 'symbol' | ||
const trx = await this.dbh.transaction(void 0, config) | ||
const tmp = await this.dbh.transaction(void 0, config) | ||
Object.defineProperty(trx, 'kmoreTrxId', { | ||
Object.defineProperty(tmp, 'kmoreTrxId', { | ||
...defaultPropDescriptor, | ||
@@ -162,6 +189,129 @@ enumerable: false, | ||
}) | ||
return trx as Knex.Transaction & { kmoreTrxId: symbol } | ||
const trxActionOnError: KmoreTransactionConfig['trxActionOnError'] = config?.trxActionOnError | ||
?? this.trxActionOnError ?? 'rollback' | ||
Object.defineProperty(tmp, 'trxActionOnError', { | ||
...defaultPropDescriptor, | ||
enumerable: false, | ||
value: trxActionOnError, | ||
}) | ||
if (trxActionOnError === 'none') { | ||
return tmp as KmoreTransaction | ||
} | ||
const trx = this.createTrxProxy(tmp as KmoreTransaction) | ||
this.trxMap.set(kmoreTrxId, trx) | ||
this.trxIdQueryMap.set(kmoreTrxId, new Set()) | ||
return trx | ||
} | ||
getTrxByKmoreQueryId(kmoreQueryId: symbol): KmoreTransaction | undefined { | ||
for (const [trxId, queryIdSet] of this.trxIdQueryMap.entries()) { | ||
if (queryIdSet.has(kmoreQueryId)) { | ||
const trx = this.trxMap.get(trxId) | ||
if (trx) { | ||
return trx | ||
} | ||
} | ||
} | ||
} | ||
getTrxByKmoreTrxId(id: symbol): KmoreTransaction | undefined { | ||
return this.trxMap.get(id) | ||
} | ||
setCtxTrxIdMap( | ||
ctx: unknown, | ||
kmoreTrxId: symbol, | ||
): void { | ||
if (! ctx || ! kmoreTrxId || typeof ctx !== 'object') { return } | ||
if (! this.ctxTrxIdMap.get(ctx)) { | ||
this.ctxTrxIdMap.set(ctx, new Set()) | ||
} | ||
this.ctxTrxIdMap.get(ctx)?.add(kmoreTrxId) | ||
} | ||
getTrxSetByCtx( | ||
ctx: unknown, | ||
): Set<KmoreTransaction> { | ||
const ret = new Set<KmoreTransaction>() | ||
if (! ctx || typeof ctx !== 'object') { | ||
return ret | ||
} | ||
const trxIdMap = this.ctxTrxIdMap.get(ctx) | ||
trxIdMap?.forEach((kmoreTrxId) => { | ||
const trx = this.trxMap.get(kmoreTrxId) | ||
if (trx) { | ||
ret.add(trx) | ||
} | ||
}) | ||
return ret | ||
} | ||
async doTrxActionOnError(trx: KmoreTransaction | undefined): Promise<void> { | ||
if (! trx) { return } | ||
if (! trx.isCompleted()) { | ||
this.trxMap.delete(trx.kmoreTrxId) | ||
} | ||
switch (trx.trxActionOnError) { | ||
case 'rollback': { | ||
await trx.rollback() | ||
this.trxMap.delete(trx.kmoreTrxId) | ||
this.trxIdQueryMap.delete(trx.kmoreTrxId) | ||
break | ||
} | ||
case 'commit': { | ||
await trx.commit() | ||
this.trxMap.delete(trx.kmoreTrxId) | ||
this.trxIdQueryMap.delete(trx.kmoreTrxId) | ||
break | ||
} | ||
default: | ||
break | ||
} | ||
} | ||
protected createTrxProxy(trx: KmoreTransaction): KmoreTransaction { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const commit = new Proxy(trx.commit, { | ||
apply: (target: typeof trx.commit, ctx: KmoreTransaction, args: unknown[]) => { | ||
this.trxIdQueryMap.delete(ctx.kmoreTrxId) | ||
this.trxMap.delete(ctx.kmoreTrxId) | ||
return Reflect.apply(target, ctx, args) | ||
}, | ||
}) | ||
Object.defineProperty(trx, 'commit', { | ||
enumerable: true, | ||
writable: true, | ||
configurable: false, | ||
value: commit, | ||
}) | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const rollback = new Proxy(trx.rollback, { | ||
apply: (target: typeof trx.rollback, ctx: KmoreTransaction, args: unknown[]) => { | ||
this.trxIdQueryMap.delete(ctx.kmoreTrxId) | ||
this.trxMap.delete(ctx.kmoreTrxId) | ||
return Reflect.apply(target, ctx, args) | ||
}, | ||
}) | ||
Object.defineProperty(trx, 'rollback', { | ||
enumerable: true, | ||
writable: true, | ||
configurable: false, | ||
value: rollback, | ||
}) | ||
return trx | ||
} | ||
protected createRefTables<P extends string>( | ||
@@ -203,51 +353,144 @@ prefix: P, | ||
ctx: Context | undefined, | ||
): Knex.QueryBuilder { | ||
): KmoreQueryBuilder { | ||
assert(caseConvert, 'caseConvert must be defined') | ||
const opts: QueryContext = { | ||
const kmoreQueryId = Symbol(`${this.dbId}-${Date.now()}`) | ||
let refTable = this.dbh(refName) | ||
refTable = this.extRefTableFnPropertyCallback( | ||
refTable as KmoreQueryBuilder, | ||
caseConvert, | ||
ctx, | ||
kmoreQueryId, | ||
) | ||
refTable = this.extRefTableFnPropertyTransacting(refTable as KmoreQueryBuilder, ctx) | ||
refTable = this.extRefTableFnPropertyThen(refTable as KmoreQueryBuilder) | ||
void Object.defineProperty(refTable, 'kmoreQueryId', { | ||
...defaultPropDescriptor, | ||
value: kmoreQueryId, | ||
}) | ||
return refTable as KmoreQueryBuilder | ||
} | ||
protected extRefTableFnPropertyCallback( | ||
refTable: KmoreQueryBuilder, | ||
caseConvert: CaseType, | ||
ctx: Context | undefined, | ||
kmoreQueryId: symbol, | ||
): KmoreQueryBuilder { | ||
assert(caseConvert, 'caseConvert must be defined') | ||
const queryCtxOpts: QueryContext = { | ||
wrapIdentifierCaseConvert: this.wrapIdentifierCaseConvert, | ||
postProcessResponseCaseConvert: caseConvert, | ||
kmoreQueryId, | ||
} | ||
const refTable = this.dbh(refName) | ||
.queryContext(opts) | ||
.on('start', async (builder: Knex.QueryBuilder) => callCbOnStart<Context>( | ||
ctx, | ||
this.dbId, | ||
this.eventCallbacks, | ||
this.instanceId, | ||
builder, | ||
)) | ||
.on('query', async (data: OnQueryData) => callCbOnQuery<Context>( | ||
ctx, | ||
this.dbId, | ||
this.eventCallbacks, | ||
this.instanceId, | ||
data, | ||
)) | ||
const opts: CallCbOptionsBase = { | ||
ctx, | ||
dbId: this.dbId, | ||
cbs: this.eventCallbacks, | ||
identifier: this.instanceId, | ||
kmoreQueryId, | ||
} | ||
const refTable2 = refTable | ||
.queryContext(queryCtxOpts) | ||
.on( | ||
'start', | ||
async (builder: KmoreQueryBuilder) => callCbOnStart({ | ||
...opts, | ||
builder, | ||
}), | ||
) | ||
.on( | ||
'query', | ||
async (data: OnQueryData) => callCbOnQuery({ | ||
...opts, | ||
data, | ||
}), | ||
) | ||
.on( | ||
'query-response', | ||
async (resp: QueryResponse, respRaw: OnQueryRespRaw) => callCbOnQueryResp<Context>( | ||
ctx, | ||
this.dbId, | ||
this.eventCallbacks, | ||
this.instanceId, | ||
resp, | ||
async (resp: QueryResponse, respRaw: OnQueryRespRaw) => callCbOnQueryResp({ | ||
...opts, | ||
_resp: resp, | ||
respRaw, | ||
), | ||
}), | ||
) | ||
.on( | ||
'query-error', | ||
async (err: OnQueryErrorErr, data: OnQueryErrorData) => callCbOnQueryError<Context>( | ||
ctx, | ||
this.dbId, | ||
this.eventCallbacks, | ||
this.instanceId, | ||
err, | ||
data, | ||
), | ||
async (err: OnQueryErrorErr, data: OnQueryErrorData) => { | ||
const trx = this.getTrxByKmoreQueryId(kmoreQueryId) | ||
await this.doTrxActionOnError(trx) | ||
return callCbOnQueryError({ | ||
...opts, | ||
err, | ||
data, | ||
}) | ||
}, | ||
) | ||
return refTable | ||
return refTable2 as KmoreQueryBuilder | ||
} | ||
protected extRefTableFnPropertyTransacting( | ||
refTable: KmoreQueryBuilder, | ||
ctx: Context | undefined, | ||
): KmoreQueryBuilder { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const ts = new Proxy(refTable.transacting, { | ||
apply: (target: KmoreQueryBuilder['transacting'], ctx2: KmoreQueryBuilder, args: [KmoreTransaction]) => { | ||
const [trx] = args | ||
assert(trx?.isTransaction === true, 'trx must be a transaction') | ||
const { kmoreTrxId } = trx | ||
const qid = ctx2.kmoreQueryId as symbol | undefined | ||
if (qid && kmoreTrxId) { | ||
this.trxIdQueryMap.get(kmoreTrxId)?.add(qid) | ||
this.setCtxTrxIdMap(ctx, kmoreTrxId) | ||
} | ||
return Reflect.apply(target, ctx2, args) | ||
}, | ||
}) | ||
void Object.defineProperty(refTable, 'transacting', { | ||
...defaultPropDescriptor, | ||
value: ts, | ||
}) | ||
return refTable as KmoreQueryBuilder | ||
} | ||
protected extRefTableFnPropertyThen(refTable: KmoreQueryBuilder): KmoreQueryBuilder { | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
const pm = new Proxy(refTable.then, { | ||
apply: (target: () => Promise<unknown>, ctx2: KmoreQueryBuilder, args: unknown[]) => { | ||
const qid = ctx2.kmoreQueryId | ||
const trx = this.getTrxByKmoreQueryId(qid) | ||
if (! trx) { | ||
return Reflect.apply(target, ctx2, args) | ||
} | ||
return (Reflect.apply(target, ctx2, args) as Promise<unknown>) | ||
.catch(async (err: unknown) => { | ||
const trx2 = this.getTrxByKmoreQueryId(qid) | ||
await this.doTrxActionOnError(trx2) | ||
return Promise.reject(err) | ||
}) | ||
}, | ||
}) | ||
void Object.defineProperty(refTable, 'then', { | ||
...defaultPropDescriptor, | ||
configurable: true, | ||
value: pm, | ||
}) | ||
return refTable as KmoreQueryBuilder | ||
} | ||
protected postProcessResponse( | ||
@@ -264,2 +507,3 @@ result: any, | ||
} | ||
} | ||
@@ -286,2 +530,8 @@ | ||
wrapIdentifierCaseConvert?: CaseType | undefined | ||
/** | ||
* Atuo trsaction action (rollback|commit|none) on error (Rejection or Exception), | ||
* @CAUTION **Will always rollback if query error in database even though this value set to 'commit'** | ||
* @default rollback | ||
*/ | ||
trxActionOnError?: KmoreTransactionConfig['trxActionOnError'] | ||
} | ||
@@ -299,1 +549,7 @@ | ||
/** | ||
* kmoreTrxId => Set<kmoreQueryId> | ||
*/ | ||
type TrxIdQueryMap = Map<symbol, Set<symbol>> | ||
@@ -9,2 +9,22 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
export type KnexConfig = Knex.Config | ||
export type KmoreTransaction = Knex.Transaction & { | ||
kmoreTrxId: symbol, | ||
/** | ||
* Atuo trsaction action (rollback|commit|none) on error (Rejection or Exception), | ||
* @CAUTION **Will always rollback if query error in database even though this value set to 'commit'** | ||
* @default rollback | ||
*/ | ||
trxActionOnError: NonNullable<KmoreTransactionConfig['trxActionOnError']>, | ||
} | ||
export type KmoreTransactionConfig = Knex.TransactionConfig & { | ||
/** | ||
* Atuo trsaction action (rollback|commit|none) on error (Rejection or Exception), | ||
* @CAUTION **Will always rollback if query error in database even though this value set to 'commit'** | ||
* @default rollback | ||
*/ | ||
trxActionOnError?: 'commit' | 'rollback' | 'none', | ||
} | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
export type KmoreQueryBuilder<TRecord extends {} = any, TResult = any> = | ||
Knex.QueryBuilder<TRecord, TResult> & { kmoreQueryId: symbol } | ||
@@ -49,3 +69,3 @@ export enum EnumClient { | ||
export type TbQueryBuilder<TRecord, Context> = (ctx?: Context) => Knex.QueryBuilder<TRecord, TRecord[]> | ||
export type TbQueryBuilder<TRecord, Context> = (ctx?: Context) => KmoreQueryBuilder<TRecord, TRecord[]> | ||
// export type TbQueryBuilder<TRecord> | ||
@@ -81,2 +101,3 @@ // = <CaseConvert extends CaseType = CaseType.none>(caseConvert?: CaseConvert) | ||
queryUid: string // 'mXxtvuJLHkZI816UZic57' | ||
kmoreQueryId: symbol | undefined | ||
/** | ||
@@ -104,2 +125,3 @@ * @description Note: may keep value of the latest transaction id, | ||
postProcessResponseCaseConvert: CaseType | ||
kmoreQueryId: symbol | ||
} | ||
@@ -106,0 +128,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
169729
2821