@open-mercato/shared 0.6.8-develop.6924.1.a8d208fcdc → 0.6.8-develop.6925.1.cefa200fa4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/commands/command-bus.js +19 -0
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/commands/__tests__/command-bus.test.ts +143 -0
- package/src/lib/commands/command-bus.ts +24 -0
- package/src/lib/commands/command-interceptor.ts +9 -1
|
@@ -213,6 +213,25 @@ class CommandBus {
|
|
|
213
213
|
const snapshotsWithAfter = { ...snapshots, after: afterSnapshot };
|
|
214
214
|
const logMeta = await this.buildLog(handler, effectiveOptions, result, snapshotsWithAfter);
|
|
215
215
|
let mergedMeta = this.mergeMetadata(effectiveOptions.metadata, logMeta);
|
|
216
|
+
let interceptorContextMerged = {};
|
|
217
|
+
for (const meta of interceptorMetadata.values()) {
|
|
218
|
+
const logContextRecord = asRecord(asRecord(meta)?.logContext);
|
|
219
|
+
if (!logContextRecord) continue;
|
|
220
|
+
interceptorContextMerged = {
|
|
221
|
+
...interceptorContextMerged,
|
|
222
|
+
...logContextRecord
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const baseContext = asRecord(effectiveOptions.metadata?.context) ?? {};
|
|
226
|
+
const logMetaContext = asRecord(logMeta?.context) ?? {};
|
|
227
|
+
if (Object.keys(interceptorContextMerged).length > 0 || Object.keys(baseContext).length > 0 || Object.keys(logMetaContext).length > 0) {
|
|
228
|
+
mergedMeta = mergedMeta ?? {};
|
|
229
|
+
mergedMeta.context = {
|
|
230
|
+
...baseContext,
|
|
231
|
+
...interceptorContextMerged,
|
|
232
|
+
...logMetaContext
|
|
233
|
+
};
|
|
234
|
+
}
|
|
216
235
|
const undoable = this.isUndoable(handler);
|
|
217
236
|
if (undoable) {
|
|
218
237
|
mergedMeta = mergedMeta ?? {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/commands/command-bus.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogCreateInput } from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { commandRegistry } from './registry'\nimport type {\n BulkImportSuppression,\n CommandExecutionOptions,\n CommandExecuteResult,\n CommandHandler,\n CommandLogBuilderArgs,\n CommandLogMetadata,\n CommandRuntimeContext,\n} from './types'\nimport { defaultUndoToken } from './types'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { AwilixContainer } from 'awilix'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport {\n canonicalizeResourceTag,\n deriveResourceFromCommandId,\n invalidateCrudCache,\n pickFirstIdentifier,\n isCrudCacheDebugEnabled,\n} from '@open-mercato/shared/lib/crud/cache'\nimport { normalizeCustomFieldKey } from '@open-mercato/shared/lib/custom-fields/keys'\nimport { getAllCommandInterceptorInstances } from './command-interceptor-store'\nimport {\n runCommandInterceptorsBefore,\n runCommandInterceptorsAfter,\n runCommandInterceptorsBeforeUndo,\n runCommandInterceptorsAfterUndo,\n} from './command-interceptor-runner'\nimport type { CommandInterceptorContext } from './command-interceptor'\nimport { CommandInterceptorError } from './errors'\nimport { isReadProjectionAlwaysConsistent } from '@open-mercato/shared/lib/data/consistency'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\nconst SKIPPED_ACTION_LOG_RESOURCE_KINDS = new Set<string>([\n 'audit_logs.access',\n 'audit_logs.action',\n 'dashboards.layout',\n 'dashboards.user_widgets',\n 'dashboards.role_widgets',\n])\n\nfunction asRecord(input: unknown): Record<string, unknown> | null {\n if (!input || typeof input !== 'object' || Array.isArray(input)) return null\n return input as Record<string, unknown>\n}\n\n/** Command handlers often return domain keys (e.g. warehouseId) without `id`; cache invalidation must still resolve the record. */\nfunction extractPrimaryIdFromCommandResult(result: unknown): string | null {\n const r = asRecord(result)\n if (!r) return null\n const direct = pickFirstIdentifier(r.id, r.entityId, r.recordId)\n if (direct) return direct\n for (const key of [\n 'warehouseId',\n 'zoneId',\n 'locationId',\n 'lotId',\n 'reservationId',\n 'profileId',\n 'movementId',\n 'balanceId',\n ]) {\n const v = r[key]\n if (typeof v === 'string' && v.trim().length > 0) return v.trim()\n }\n return null\n}\n\nfunction toISOString(value: unknown): string | null {\n if (value instanceof Date) {\n const iso = value.toISOString()\n return Number.isNaN(value.getTime()) ? null : iso\n }\n if (typeof value === 'string') {\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean {\n if (Object.is(a, b)) return true\n if (a instanceof Date || b instanceof Date) {\n const aIso = toISOString(a)\n const bIso = toISOString(b)\n if (aIso != null && bIso != null) return aIso === bIso\n return false\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((value, index) => deepEqual(value, b[index], seen))\n }\n if (a && b && typeof a === 'object' && typeof b === 'object') {\n if (!seen) seen = new Set()\n if (seen.has(a) || seen.has(b)) return false\n seen.add(a)\n seen.add(b)\n const aRec = a as Record<string, unknown>\n const bRec = b as Record<string, unknown>\n const keysA = Object.keys(aRec)\n const keysB = Object.keys(bRec)\n if (keysA.length !== keysB.length) return false\n return keysA.every((key) => deepEqual(aRec[key], bRec[key], seen))\n }\n return false\n}\n\nconst CUSTOM_FIELD_CONTAINER_KEYS = new Set(['custom', 'customFields', 'customValues', 'cf'])\nconst SKIPPED_CHANGE_KEYS = new Set(['updatedAt', 'updated_at'])\n\nfunction appendCustomFieldChanges(\n changes: Record<string, { from: unknown; to: unknown }>,\n before: unknown,\n after: unknown\n): boolean {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec && !afterRec) return false\n const left = beforeRec ?? {}\n const right = afterRec ?? {}\n const keys = new Set([...Object.keys(left), ...Object.keys(right)])\n for (const key of keys) {\n const from = left[key]\n const to = right[key]\n if (!deepEqual(from, to)) {\n changes[normalizeCustomFieldKey(key)] = { from, to }\n }\n }\n return true\n}\n\nfunction buildRecordChanges(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n return buildRecordChangesDeep(before, after)\n}\n\nfunction buildRecordChangesDeep(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n prefix?: string,\n seen?: Set<unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n const changes: Record<string, { from: unknown; to: unknown }> = {}\n if (!seen) seen = new Set()\n if (seen.has(before) || seen.has(after)) return changes\n seen.add(before)\n seen.add(after)\n const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n for (const key of keys) {\n if (SKIPPED_CHANGE_KEYS.has(key)) continue\n if (CUSTOM_FIELD_CONTAINER_KEYS.has(key)) {\n const handled = appendCustomFieldChanges(changes, before[key], after[key])\n if (handled) continue\n }\n const from = before[key]\n const to = after[key]\n const path = prefix ? `${prefix}.${key}` : key\n const fromRec = asRecord(from)\n const toRec = asRecord(to)\n if (fromRec && toRec) {\n const nested = buildRecordChangesDeep(fromRec, toRec, path, seen)\n if (Object.keys(nested).length) {\n Object.assign(changes, nested)\n continue\n }\n }\n if (!deepEqual(from, to)) {\n changes[path] = { from, to }\n }\n }\n return changes\n}\n\nfunction deriveChangesFromSnapshots(\n before: unknown,\n after: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec || !afterRec) return null\n const changes = buildRecordChanges(beforeRec, afterRec)\n return Object.keys(changes).length ? changes : null\n}\n\nfunction invertRecordedChanges(\n changes: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const source = asRecord(changes)\n if (!source) return null\n const inverted: Record<string, { from: unknown; to: unknown }> = {}\n for (const [key, value] of Object.entries(source)) {\n const entry = asRecord(value)\n if (!entry || (!('from' in entry) && !('to' in entry))) continue\n inverted[key] = {\n from: entry.to,\n to: entry.from,\n }\n }\n return Object.keys(inverted).length ? inverted : null\n}\n\nfunction extractAliasList(source: unknown): string[] {\n if (!source || typeof source !== 'object' || Array.isArray(source)) return []\n const record = source as Record<string, unknown>\n const raw = record.cacheAliases\n if (!Array.isArray(raw)) return []\n const aliases = new Set<string>()\n for (const value of raw) {\n if (typeof value !== 'string') continue\n const normalized = canonicalizeResourceTag(value)\n if (normalized) aliases.add(normalized)\n }\n return Array.from(aliases)\n}\n\nexport class CommandBus {\n async execute<TInput = unknown, TResult = unknown>(\n commandId: string,\n options: CommandExecutionOptions<TInput>\n ): Promise<CommandExecuteResult<TResult>> {\n const handler = await this.resolveHandler<TInput, TResult>(commandId)\n\n // Run beforeExecute command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let interceptorMetadata = new Map<string, Record<string, unknown>>()\n let effectiveOptions = options\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(options.ctx)\n : []\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: options.ctx.auth ?? null,\n selectedOrganizationId: options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null,\n container: options.ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBefore(\n allInterceptors, commandId, options.input, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n const blocked = beforeResult.error!\n throw new CommandInterceptorError(blocked.message, { status: blocked.status, body: blocked.body })\n }\n interceptorMetadata = beforeResult.metadataByInterceptor\n if (beforeResult.modifiedInput) {\n effectiveOptions = {\n ...options,\n input: { ...(options.input as object), ...beforeResult.modifiedInput } as TInput,\n }\n }\n }\n\n const snapshots = await this.prepareSnapshots(handler, effectiveOptions)\n const redoLogEntry = effectiveOptions.redoLogEntry ?? null\n const result =\n redoLogEntry && typeof handler.redo === 'function'\n ? await handler.redo({ input: effectiveOptions.input, ctx: effectiveOptions.ctx, logEntry: redoLogEntry })\n : await handler.execute(effectiveOptions.input, effectiveOptions.ctx)\n const afterSnapshot = await this.captureAfter(handler, effectiveOptions, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, effectiveOptions, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(effectiveOptions.metadata, logMeta)\n const undoable = this.isUndoable(handler)\n if (undoable) {\n mergedMeta = mergedMeta ?? {}\n if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken()\n if (mergedMeta.actorUserId === undefined) mergedMeta.actorUserId = effectiveOptions.ctx.auth?.sub ?? null\n }\n if (afterSnapshot !== undefined && afterSnapshot !== null) {\n if (!mergedMeta) {\n mergedMeta = { snapshotAfter: afterSnapshot }\n } else if (!mergedMeta.snapshotAfter) {\n mergedMeta.snapshotAfter = afterSnapshot\n }\n }\n if (snapshots.before) {\n if (!mergedMeta) {\n mergedMeta = { snapshotBefore: snapshots.before }\n } else if (!mergedMeta.snapshotBefore) {\n mergedMeta.snapshotBefore = snapshots.before\n }\n }\n if (mergedMeta?.snapshotBefore !== undefined && mergedMeta?.snapshotAfter !== undefined) {\n const currentChanges = mergedMeta.changes\n const shouldInfer =\n currentChanges === undefined ||\n currentChanges === null ||\n (typeof currentChanges === 'object' && !Array.isArray(currentChanges) && Object.keys(currentChanges).length === 0)\n if (shouldInfer) {\n const inferred = deriveChangesFromSnapshots(mergedMeta.snapshotBefore, mergedMeta.snapshotAfter)\n if (inferred) mergedMeta.changes = inferred\n }\n }\n const logEntry = await this.persistLog(commandId, effectiveOptions, mergedMeta)\n\n // Run afterExecute command interceptors\n let finalResult = result\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: effectiveOptions.ctx.auth ?? null,\n selectedOrganizationId: effectiveOptions.ctx.selectedOrganizationId ?? effectiveOptions.ctx.auth?.orgId ?? null,\n container: effectiveOptions.ctx.container,\n }\n const afterResult = await runCommandInterceptorsAfter(\n allInterceptors, commandId, effectiveOptions.input, result, interceptorCtx,\n userFeatures, interceptorMetadata,\n )\n if (afterResult.modifiedResult && typeof result === 'object' && result) {\n finalResult = { ...(result as object), ...afterResult.modifiedResult } as Awaited<TResult>\n }\n }\n\n if (!effectiveOptions.skipCacheInvalidation) {\n await this.invalidateCacheAfterExecute(commandId, effectiveOptions, finalResult, mergedMeta)\n }\n // Bulk-import backfills defer heavy per-record side effects: the ctx flags are read here and\n // threaded as a local into the flush (never stored on the shared dataEngine), so a concurrent\n // command with different flags can't observe them. Reindex is restored by the caller's\n // end-of-run `query_index rebuild`. Mirrors `skipCacheInvalidation` above.\n await this.flushCrudSideEffects(effectiveOptions.ctx.container, effectiveOptions.ctx?.bulkImport)\n return { result: finalResult, logEntry }\n }\n\n async undo(undoToken: string, ctx: CommandRuntimeContext): Promise<void> {\n const service = (ctx.container.resolve('actionLogService') as ActionLogService)\n const log = await service.findByUndoToken(undoToken)\n if (!log) throw new Error('Undo token expired or not found')\n const handler = await this.resolveHandler(log.commandId)\n if (!handler.undo || this.isUndoable(handler) === false) {\n throw new Error(`Command ${log.commandId} is not undoable`)\n }\n\n // Atomically claim the action-log row before running any undo side effects.\n // Two concurrent requests holding the same undo token can both pass\n // findByUndoToken/executionState checks; the compare-and-set below ensures\n // only one transitions `done` -> `undoing` and proceeds, the other bails out.\n const claimed = await service.claimForUndo(log.id)\n if (!claimed) throw new Error('Undo token already consumed')\n\n try {\n // Run beforeUndo command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let undoInterceptorMetadata = new Map<string, Record<string, unknown>>()\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(ctx)\n : []\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBeforeUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n const blocked = beforeResult.error!\n throw new CommandInterceptorError(blocked.message, { status: blocked.status, body: blocked.body })\n }\n undoInterceptorMetadata = beforeResult.metadataByInterceptor\n }\n\n await handler.undo({\n input: log.commandPayload as Parameters<NonNullable<typeof handler.undo>>[0]['input'],\n ctx,\n logEntry: log,\n })\n await service.markUndone(log.id, this.buildUndoTraceLog(log, ctx))\n\n // Run afterUndo command interceptors\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n await runCommandInterceptorsAfterUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx,\n userFeatures, undoInterceptorMetadata,\n )\n }\n\n await this.invalidateCacheAfterUndo(log, ctx)\n await this.flushCrudSideEffects(ctx.container)\n } catch (err) {\n // Undo failed after claiming the row \u2014 release the claim so the action\n // remains retryable instead of being stranded in the `undoing` state.\n await service.releaseUndoClaim(log.id).catch(() => {})\n throw err\n }\n }\n\n private buildUndoTraceLog(log: ActionLog, ctx: CommandRuntimeContext): ActionLogCreateInput | undefined {\n const snapshotBefore = log.snapshotAfter ?? null\n const snapshotAfter = log.snapshotBefore ?? null\n const changes =\n deriveChangesFromSnapshots(snapshotBefore, snapshotAfter)\n ?? invertRecordedChanges(log.changesJson)\n ?? undefined\n\n const baseContext = asRecord(log.contextJson) ?? {}\n const context = {\n ...baseContext,\n historyAction: 'undo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n\n return {\n tenantId: log.tenantId ?? ctx.auth?.tenantId ?? null,\n organizationId: log.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n actorUserId: ctx.auth?.sub ?? log.actorUserId ?? null,\n commandId: log.commandId,\n actionLabel: log.actionLabel ?? undefined,\n resourceKind: log.resourceKind ?? undefined,\n resourceId: log.resourceId ?? undefined,\n parentResourceKind: log.parentResourceKind ?? null,\n parentResourceId: log.parentResourceId ?? null,\n relatedResourceKind: log.relatedResourceKind ?? null,\n relatedResourceId: log.relatedResourceId ?? null,\n snapshotBefore,\n snapshotAfter,\n changes,\n context,\n }\n }\n\n private async resolveUserFeaturesForInterceptors(ctx: CommandRuntimeContext): Promise<string[]> {\n if (!ctx.auth) return []\n try {\n type RbacLike = { getGrantedFeatures: (userId: string, opts: { tenantId: string | null; organizationId: string | null }) => Promise<string[]> }\n const rbac = ctx.container.resolve('rbacService') as RbacLike | undefined\n if (rbac?.getGrantedFeatures) {\n return await rbac.getGrantedFeatures(ctx.auth.sub, {\n tenantId: ctx.auth.tenantId,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId,\n })\n }\n } catch {\n // Intentional: rbacService is not registered in all runtime contexts (CLI, tests, bootstrap).\n // Falling through to return [] is safe \u2014 interceptors without feature gating still run.\n }\n return []\n }\n\n private async resolveHandler<TInput, TResult>(commandId: string): Promise<CommandHandler<TInput, TResult>> {\n const handler =\n commandRegistry.get<TInput, TResult>(commandId) ??\n ((await commandRegistry.load(commandId)) as CommandHandler<TInput, TResult> | null)\n if (!handler) {\n const moduleName = commandId.split('.')[0]\n const registered = commandRegistry.list()\n const sameModule = registered.filter((id) => id.split('.')[0] === moduleName)\n const registeredLoaders = commandRegistry.listLoaders()\n const sameModuleLoaders = registeredLoaders.filter((id) => id === commandId || id.startsWith(`${moduleName}:`))\n const hint = sameModule.length > 0\n ? ` Registered commands for module \"${moduleName}\": [${sameModule.join(', ')}].`\n : sameModuleLoaders.length > 0\n ? ` Command loaders for module \"${moduleName}\" were registered but none loaded \"${commandId}\".`\n : ` No commands or command loaders registered for module \"${moduleName}\". Ensure the command file is imported or generated lazy command loaders are registered.`\n throw new Error(`Command handler not registered for id ${commandId}.${hint}`)\n }\n return handler\n }\n\n private async prepareSnapshots<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>\n ): Promise<{ before?: unknown }> {\n if (!handler.prepare) return {}\n try {\n return (await handler.prepare(options.input, options.ctx)) || {}\n } catch (err) {\n throw err\n }\n }\n\n private async captureAfter<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult\n ): Promise<unknown> {\n if (!handler.captureAfter) return undefined\n return handler.captureAfter(options.input, result, options.ctx)\n }\n\n private async buildLog<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult,\n snapshots: { before?: unknown; after?: unknown }\n ): Promise<CommandLogMetadata | null> {\n if (!handler.buildLog) return null\n const args: CommandLogBuilderArgs<TInput, TResult> = {\n input: options.input,\n result,\n ctx: options.ctx,\n snapshots,\n }\n return (await handler.buildLog(args)) || null\n }\n\n private mergeMetadata(primary?: CommandLogMetadata | null, secondary?: CommandLogMetadata | null): CommandLogMetadata | null {\n if (!primary && !secondary) return null\n return {\n skipLog: secondary?.skipLog ?? primary?.skipLog ?? false,\n tenantId: secondary?.tenantId ?? primary?.tenantId ?? null,\n organizationId: secondary?.organizationId ?? primary?.organizationId ?? null,\n actorUserId: secondary?.actorUserId ?? primary?.actorUserId ?? null,\n actionLabel: secondary?.actionLabel ?? primary?.actionLabel ?? null,\n resourceKind: secondary?.resourceKind ?? primary?.resourceKind ?? null,\n resourceId: secondary?.resourceId ?? primary?.resourceId ?? null,\n parentResourceKind: secondary?.parentResourceKind ?? primary?.parentResourceKind ?? null,\n parentResourceId: secondary?.parentResourceId ?? primary?.parentResourceId ?? null,\n relatedResourceKind: secondary?.relatedResourceKind ?? primary?.relatedResourceKind ?? null,\n relatedResourceId: secondary?.relatedResourceId ?? primary?.relatedResourceId ?? null,\n undoToken: secondary?.undoToken ?? primary?.undoToken ?? null,\n payload: secondary?.payload ?? primary?.payload ?? null,\n snapshotBefore: secondary?.snapshotBefore ?? primary?.snapshotBefore ?? null,\n snapshotAfter: secondary?.snapshotAfter ?? primary?.snapshotAfter ?? null,\n changes: secondary?.changes ?? primary?.changes ?? null,\n context: secondary?.context ?? primary?.context ?? null,\n }\n }\n\n private async persistLog<TInput>(\n commandId: string,\n options: CommandExecutionOptions<TInput>,\n metadata: CommandLogMetadata | null\n ): Promise<ActionLog | null> {\n if (!metadata) return null\n if (metadata.skipLog) return null\n const resourceKind =\n typeof metadata.resourceKind === 'string' ? metadata.resourceKind : null\n if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {\n return null\n }\n let service: ActionLogService | null = null\n try {\n service = (options.ctx.container.resolve('actionLogService') as ActionLogService)\n } catch {\n service = null\n }\n if (!service) return null\n\n const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null\n const organizationId =\n metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null\n const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n commandId,\n }\n\n if (metadata) {\n if ('actionLabel' in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel\n if ('resourceKind' in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind\n if ('resourceId' in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId\n if ('parentResourceKind' in metadata && metadata.parentResourceKind != null) payload.parentResourceKind = metadata.parentResourceKind\n if ('parentResourceId' in metadata && metadata.parentResourceId != null) payload.parentResourceId = metadata.parentResourceId\n if ('relatedResourceKind' in metadata && metadata.relatedResourceKind != null) payload.relatedResourceKind = metadata.relatedResourceKind\n if ('relatedResourceId' in metadata && metadata.relatedResourceId != null) payload.relatedResourceId = metadata.relatedResourceId\n if ('undoToken' in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken\n if ('payload' in metadata && metadata.payload !== undefined) payload.commandPayload = metadata.payload\n if ('snapshotBefore' in metadata && metadata.snapshotBefore !== undefined) payload.snapshotBefore = metadata.snapshotBefore\n if ('snapshotAfter' in metadata && metadata.snapshotAfter !== undefined) payload.snapshotAfter = metadata.snapshotAfter\n if ('changes' in metadata && metadata.changes !== undefined && metadata.changes !== null) payload.changes = metadata.changes\n if ('context' in metadata && metadata.context !== undefined && metadata.context !== null) payload.context = metadata.context\n }\n\n const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, options.input)\n payload.commandPayload = redoEnvelope\n\n return await service.log(payload as ActionLogCreateInput)\n }\n\n private isUndoable(handler: CommandHandler<unknown, unknown>): boolean {\n return handler.isUndoable !== false && typeof handler.undo === 'function'\n }\n\n private async invalidateCacheAfterExecute<TResult>(\n commandId: string,\n options: CommandExecutionOptions<unknown>,\n result: TResult,\n metadata: CommandLogMetadata | null\n ): Promise<void> {\n const resource = typeof metadata?.resourceKind === 'string' ? metadata.resourceKind : null\n if (!resource) return\n try {\n const ctx = options.ctx\n const resultRecord = asRecord(result)\n const resultEntity = asRecord(resultRecord?.entity)\n const inputRecord = asRecord(options.input)\n const inputEntity = asRecord(inputRecord?.entity)\n\n const recordId = pickFirstIdentifier(\n metadata?.resourceId,\n extractPrimaryIdFromCommandResult(result),\n resultRecord?.entityId,\n resultRecord?.id,\n resultRecord?.recordId,\n resultEntity?.id,\n inputRecord?.id,\n inputRecord?.entityId,\n inputRecord?.recordId,\n inputEntity?.id\n )\n\n const organizationId = pickFirstIdentifier(\n metadata?.organizationId,\n resultRecord?.organizationId,\n resultEntity?.organizationId,\n inputRecord?.organizationId,\n inputEntity?.organizationId,\n ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n )\n\n const tenantId = pickFirstIdentifier(\n metadata?.tenantId,\n resultRecord?.tenantId,\n resultEntity?.tenantId,\n inputRecord?.tenantId,\n inputEntity?.tenantId,\n ctx.auth?.tenantId ?? null\n )\n\n const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null)\n\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(metadata?.context ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${commandId}:execute`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache execute-invalidation failed', { commandId, err })\n } catch {}\n }\n }\n }\n\n private async invalidateCacheAfterUndo(log: ActionLog, ctx: CommandRuntimeContext): Promise<void> {\n const resource = typeof log.resourceKind === 'string' ? log.resourceKind : null\n if (!resource) return\n try {\n const recordId = pickFirstIdentifier(log.resourceId)\n const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(log.contextJson ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(log.commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${log.commandId}:undo`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache undo-invalidation failed', { commandId: log.commandId, err })\n } catch {}\n }\n }\n }\n\n private async flushCrudSideEffects(container: AwilixContainer, suppress?: BulkImportSuppression): Promise<void> {\n try {\n const dataEngine = (container.resolve('dataEngine') as DataEngine)\n await dataEngine.flushOrmEntityChanges(suppress)\n } catch (error) {\n if (isReadProjectionAlwaysConsistent()) {\n throw error\n }\n // best-effort: failures should not block command execution\n }\n }\n}\n\ntype RedoEnvelope = {\n __redoInput: unknown\n [key: string]: unknown\n}\n\nfunction wrapRedoPayload(existing: unknown, input: unknown): RedoEnvelope {\n if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {\n const envelope: RedoEnvelope = { __redoInput: input }\n if (existing !== undefined) envelope.value = existing\n return envelope\n }\n const current = existing as Record<string, unknown>\n if ('__redoInput' in current && current.__redoInput !== undefined) {\n return current as RedoEnvelope\n }\n return { __redoInput: input, ...current }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogCreateInput } from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { commandRegistry } from './registry'\nimport type {\n BulkImportSuppression,\n CommandExecutionOptions,\n CommandExecuteResult,\n CommandHandler,\n CommandLogBuilderArgs,\n CommandLogMetadata,\n CommandRuntimeContext,\n} from './types'\nimport { defaultUndoToken } from './types'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { AwilixContainer } from 'awilix'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport {\n canonicalizeResourceTag,\n deriveResourceFromCommandId,\n invalidateCrudCache,\n pickFirstIdentifier,\n isCrudCacheDebugEnabled,\n} from '@open-mercato/shared/lib/crud/cache'\nimport { normalizeCustomFieldKey } from '@open-mercato/shared/lib/custom-fields/keys'\nimport { getAllCommandInterceptorInstances } from './command-interceptor-store'\nimport {\n runCommandInterceptorsBefore,\n runCommandInterceptorsAfter,\n runCommandInterceptorsBeforeUndo,\n runCommandInterceptorsAfterUndo,\n} from './command-interceptor-runner'\nimport type { CommandInterceptorContext } from './command-interceptor'\nimport { CommandInterceptorError } from './errors'\nimport { isReadProjectionAlwaysConsistent } from '@open-mercato/shared/lib/data/consistency'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\nconst SKIPPED_ACTION_LOG_RESOURCE_KINDS = new Set<string>([\n 'audit_logs.access',\n 'audit_logs.action',\n 'dashboards.layout',\n 'dashboards.user_widgets',\n 'dashboards.role_widgets',\n])\n\nfunction asRecord(input: unknown): Record<string, unknown> | null {\n if (!input || typeof input !== 'object' || Array.isArray(input)) return null\n return input as Record<string, unknown>\n}\n\n/** Command handlers often return domain keys (e.g. warehouseId) without `id`; cache invalidation must still resolve the record. */\nfunction extractPrimaryIdFromCommandResult(result: unknown): string | null {\n const r = asRecord(result)\n if (!r) return null\n const direct = pickFirstIdentifier(r.id, r.entityId, r.recordId)\n if (direct) return direct\n for (const key of [\n 'warehouseId',\n 'zoneId',\n 'locationId',\n 'lotId',\n 'reservationId',\n 'profileId',\n 'movementId',\n 'balanceId',\n ]) {\n const v = r[key]\n if (typeof v === 'string' && v.trim().length > 0) return v.trim()\n }\n return null\n}\n\nfunction toISOString(value: unknown): string | null {\n if (value instanceof Date) {\n const iso = value.toISOString()\n return Number.isNaN(value.getTime()) ? null : iso\n }\n if (typeof value === 'string') {\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean {\n if (Object.is(a, b)) return true\n if (a instanceof Date || b instanceof Date) {\n const aIso = toISOString(a)\n const bIso = toISOString(b)\n if (aIso != null && bIso != null) return aIso === bIso\n return false\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((value, index) => deepEqual(value, b[index], seen))\n }\n if (a && b && typeof a === 'object' && typeof b === 'object') {\n if (!seen) seen = new Set()\n if (seen.has(a) || seen.has(b)) return false\n seen.add(a)\n seen.add(b)\n const aRec = a as Record<string, unknown>\n const bRec = b as Record<string, unknown>\n const keysA = Object.keys(aRec)\n const keysB = Object.keys(bRec)\n if (keysA.length !== keysB.length) return false\n return keysA.every((key) => deepEqual(aRec[key], bRec[key], seen))\n }\n return false\n}\n\nconst CUSTOM_FIELD_CONTAINER_KEYS = new Set(['custom', 'customFields', 'customValues', 'cf'])\nconst SKIPPED_CHANGE_KEYS = new Set(['updatedAt', 'updated_at'])\n\nfunction appendCustomFieldChanges(\n changes: Record<string, { from: unknown; to: unknown }>,\n before: unknown,\n after: unknown\n): boolean {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec && !afterRec) return false\n const left = beforeRec ?? {}\n const right = afterRec ?? {}\n const keys = new Set([...Object.keys(left), ...Object.keys(right)])\n for (const key of keys) {\n const from = left[key]\n const to = right[key]\n if (!deepEqual(from, to)) {\n changes[normalizeCustomFieldKey(key)] = { from, to }\n }\n }\n return true\n}\n\nfunction buildRecordChanges(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n return buildRecordChangesDeep(before, after)\n}\n\nfunction buildRecordChangesDeep(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n prefix?: string,\n seen?: Set<unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n const changes: Record<string, { from: unknown; to: unknown }> = {}\n if (!seen) seen = new Set()\n if (seen.has(before) || seen.has(after)) return changes\n seen.add(before)\n seen.add(after)\n const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n for (const key of keys) {\n if (SKIPPED_CHANGE_KEYS.has(key)) continue\n if (CUSTOM_FIELD_CONTAINER_KEYS.has(key)) {\n const handled = appendCustomFieldChanges(changes, before[key], after[key])\n if (handled) continue\n }\n const from = before[key]\n const to = after[key]\n const path = prefix ? `${prefix}.${key}` : key\n const fromRec = asRecord(from)\n const toRec = asRecord(to)\n if (fromRec && toRec) {\n const nested = buildRecordChangesDeep(fromRec, toRec, path, seen)\n if (Object.keys(nested).length) {\n Object.assign(changes, nested)\n continue\n }\n }\n if (!deepEqual(from, to)) {\n changes[path] = { from, to }\n }\n }\n return changes\n}\n\nfunction deriveChangesFromSnapshots(\n before: unknown,\n after: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec || !afterRec) return null\n const changes = buildRecordChanges(beforeRec, afterRec)\n return Object.keys(changes).length ? changes : null\n}\n\nfunction invertRecordedChanges(\n changes: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const source = asRecord(changes)\n if (!source) return null\n const inverted: Record<string, { from: unknown; to: unknown }> = {}\n for (const [key, value] of Object.entries(source)) {\n const entry = asRecord(value)\n if (!entry || (!('from' in entry) && !('to' in entry))) continue\n inverted[key] = {\n from: entry.to,\n to: entry.from,\n }\n }\n return Object.keys(inverted).length ? inverted : null\n}\n\nfunction extractAliasList(source: unknown): string[] {\n if (!source || typeof source !== 'object' || Array.isArray(source)) return []\n const record = source as Record<string, unknown>\n const raw = record.cacheAliases\n if (!Array.isArray(raw)) return []\n const aliases = new Set<string>()\n for (const value of raw) {\n if (typeof value !== 'string') continue\n const normalized = canonicalizeResourceTag(value)\n if (normalized) aliases.add(normalized)\n }\n return Array.from(aliases)\n}\n\nexport class CommandBus {\n async execute<TInput = unknown, TResult = unknown>(\n commandId: string,\n options: CommandExecutionOptions<TInput>\n ): Promise<CommandExecuteResult<TResult>> {\n const handler = await this.resolveHandler<TInput, TResult>(commandId)\n\n // Run beforeExecute command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let interceptorMetadata = new Map<string, Record<string, unknown>>()\n let effectiveOptions = options\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(options.ctx)\n : []\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: options.ctx.auth ?? null,\n selectedOrganizationId: options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null,\n container: options.ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBefore(\n allInterceptors, commandId, options.input, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n const blocked = beforeResult.error!\n throw new CommandInterceptorError(blocked.message, { status: blocked.status, body: blocked.body })\n }\n interceptorMetadata = beforeResult.metadataByInterceptor\n if (beforeResult.modifiedInput) {\n effectiveOptions = {\n ...options,\n input: { ...(options.input as object), ...beforeResult.modifiedInput } as TInput,\n }\n }\n }\n\n const snapshots = await this.prepareSnapshots(handler, effectiveOptions)\n const redoLogEntry = effectiveOptions.redoLogEntry ?? null\n const result =\n redoLogEntry && typeof handler.redo === 'function'\n ? await handler.redo({ input: effectiveOptions.input, ctx: effectiveOptions.ctx, logEntry: redoLogEntry })\n : await handler.execute(effectiveOptions.input, effectiveOptions.ctx)\n const afterSnapshot = await this.captureAfter(handler, effectiveOptions, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, effectiveOptions, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(effectiveOptions.metadata, logMeta)\n // Interceptors opt into audit-log enrichment with a reserved `logContext` key rather\n // than the generic `context` one, so the metadata an interceptor already passes to its\n // own afterExecute hook is never silently promoted into audit storage.\n // Map iteration order is interceptor priority order (see collectMatching), so a\n // later-priority interceptor overrides an earlier one on key collisions.\n let interceptorContextMerged: Record<string, unknown> = {}\n for (const meta of interceptorMetadata.values()) {\n const logContextRecord = asRecord(asRecord(meta)?.logContext)\n if (!logContextRecord) continue\n interceptorContextMerged = {\n ...interceptorContextMerged,\n ...logContextRecord,\n }\n }\n const baseContext = asRecord(effectiveOptions.metadata?.context) ?? {}\n const logMetaContext = asRecord(logMeta?.context) ?? {}\n if (Object.keys(interceptorContextMerged).length > 0 || Object.keys(baseContext).length > 0 || Object.keys(logMetaContext).length > 0) {\n mergedMeta = mergedMeta ?? {}\n mergedMeta.context = {\n ...baseContext,\n ...interceptorContextMerged,\n ...logMetaContext,\n }\n }\n const undoable = this.isUndoable(handler)\n if (undoable) {\n mergedMeta = mergedMeta ?? {}\n if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken()\n if (mergedMeta.actorUserId === undefined) mergedMeta.actorUserId = effectiveOptions.ctx.auth?.sub ?? null\n }\n if (afterSnapshot !== undefined && afterSnapshot !== null) {\n if (!mergedMeta) {\n mergedMeta = { snapshotAfter: afterSnapshot }\n } else if (!mergedMeta.snapshotAfter) {\n mergedMeta.snapshotAfter = afterSnapshot\n }\n }\n if (snapshots.before) {\n if (!mergedMeta) {\n mergedMeta = { snapshotBefore: snapshots.before }\n } else if (!mergedMeta.snapshotBefore) {\n mergedMeta.snapshotBefore = snapshots.before\n }\n }\n if (mergedMeta?.snapshotBefore !== undefined && mergedMeta?.snapshotAfter !== undefined) {\n const currentChanges = mergedMeta.changes\n const shouldInfer =\n currentChanges === undefined ||\n currentChanges === null ||\n (typeof currentChanges === 'object' && !Array.isArray(currentChanges) && Object.keys(currentChanges).length === 0)\n if (shouldInfer) {\n const inferred = deriveChangesFromSnapshots(mergedMeta.snapshotBefore, mergedMeta.snapshotAfter)\n if (inferred) mergedMeta.changes = inferred\n }\n }\n const logEntry = await this.persistLog(commandId, effectiveOptions, mergedMeta)\n\n // Run afterExecute command interceptors\n let finalResult = result\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: effectiveOptions.ctx.auth ?? null,\n selectedOrganizationId: effectiveOptions.ctx.selectedOrganizationId ?? effectiveOptions.ctx.auth?.orgId ?? null,\n container: effectiveOptions.ctx.container,\n }\n const afterResult = await runCommandInterceptorsAfter(\n allInterceptors, commandId, effectiveOptions.input, result, interceptorCtx,\n userFeatures, interceptorMetadata,\n )\n if (afterResult.modifiedResult && typeof result === 'object' && result) {\n finalResult = { ...(result as object), ...afterResult.modifiedResult } as Awaited<TResult>\n }\n }\n\n if (!effectiveOptions.skipCacheInvalidation) {\n await this.invalidateCacheAfterExecute(commandId, effectiveOptions, finalResult, mergedMeta)\n }\n // Bulk-import backfills defer heavy per-record side effects: the ctx flags are read here and\n // threaded as a local into the flush (never stored on the shared dataEngine), so a concurrent\n // command with different flags can't observe them. Reindex is restored by the caller's\n // end-of-run `query_index rebuild`. Mirrors `skipCacheInvalidation` above.\n await this.flushCrudSideEffects(effectiveOptions.ctx.container, effectiveOptions.ctx?.bulkImport)\n return { result: finalResult, logEntry }\n }\n\n async undo(undoToken: string, ctx: CommandRuntimeContext): Promise<void> {\n const service = (ctx.container.resolve('actionLogService') as ActionLogService)\n const log = await service.findByUndoToken(undoToken)\n if (!log) throw new Error('Undo token expired or not found')\n const handler = await this.resolveHandler(log.commandId)\n if (!handler.undo || this.isUndoable(handler) === false) {\n throw new Error(`Command ${log.commandId} is not undoable`)\n }\n\n // Atomically claim the action-log row before running any undo side effects.\n // Two concurrent requests holding the same undo token can both pass\n // findByUndoToken/executionState checks; the compare-and-set below ensures\n // only one transitions `done` -> `undoing` and proceeds, the other bails out.\n const claimed = await service.claimForUndo(log.id)\n if (!claimed) throw new Error('Undo token already consumed')\n\n try {\n // Run beforeUndo command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let undoInterceptorMetadata = new Map<string, Record<string, unknown>>()\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(ctx)\n : []\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBeforeUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n const blocked = beforeResult.error!\n throw new CommandInterceptorError(blocked.message, { status: blocked.status, body: blocked.body })\n }\n undoInterceptorMetadata = beforeResult.metadataByInterceptor\n }\n\n await handler.undo({\n input: log.commandPayload as Parameters<NonNullable<typeof handler.undo>>[0]['input'],\n ctx,\n logEntry: log,\n })\n await service.markUndone(log.id, this.buildUndoTraceLog(log, ctx))\n\n // Run afterUndo command interceptors\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n await runCommandInterceptorsAfterUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx,\n userFeatures, undoInterceptorMetadata,\n )\n }\n\n await this.invalidateCacheAfterUndo(log, ctx)\n await this.flushCrudSideEffects(ctx.container)\n } catch (err) {\n // Undo failed after claiming the row \u2014 release the claim so the action\n // remains retryable instead of being stranded in the `undoing` state.\n await service.releaseUndoClaim(log.id).catch(() => {})\n throw err\n }\n }\n\n private buildUndoTraceLog(log: ActionLog, ctx: CommandRuntimeContext): ActionLogCreateInput | undefined {\n const snapshotBefore = log.snapshotAfter ?? null\n const snapshotAfter = log.snapshotBefore ?? null\n const changes =\n deriveChangesFromSnapshots(snapshotBefore, snapshotAfter)\n ?? invertRecordedChanges(log.changesJson)\n ?? undefined\n\n const baseContext = asRecord(log.contextJson) ?? {}\n const context = {\n ...baseContext,\n historyAction: 'undo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n\n return {\n tenantId: log.tenantId ?? ctx.auth?.tenantId ?? null,\n organizationId: log.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n actorUserId: ctx.auth?.sub ?? log.actorUserId ?? null,\n commandId: log.commandId,\n actionLabel: log.actionLabel ?? undefined,\n resourceKind: log.resourceKind ?? undefined,\n resourceId: log.resourceId ?? undefined,\n parentResourceKind: log.parentResourceKind ?? null,\n parentResourceId: log.parentResourceId ?? null,\n relatedResourceKind: log.relatedResourceKind ?? null,\n relatedResourceId: log.relatedResourceId ?? null,\n snapshotBefore,\n snapshotAfter,\n changes,\n context,\n }\n }\n\n private async resolveUserFeaturesForInterceptors(ctx: CommandRuntimeContext): Promise<string[]> {\n if (!ctx.auth) return []\n try {\n type RbacLike = { getGrantedFeatures: (userId: string, opts: { tenantId: string | null; organizationId: string | null }) => Promise<string[]> }\n const rbac = ctx.container.resolve('rbacService') as RbacLike | undefined\n if (rbac?.getGrantedFeatures) {\n return await rbac.getGrantedFeatures(ctx.auth.sub, {\n tenantId: ctx.auth.tenantId,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId,\n })\n }\n } catch {\n // Intentional: rbacService is not registered in all runtime contexts (CLI, tests, bootstrap).\n // Falling through to return [] is safe \u2014 interceptors without feature gating still run.\n }\n return []\n }\n\n private async resolveHandler<TInput, TResult>(commandId: string): Promise<CommandHandler<TInput, TResult>> {\n const handler =\n commandRegistry.get<TInput, TResult>(commandId) ??\n ((await commandRegistry.load(commandId)) as CommandHandler<TInput, TResult> | null)\n if (!handler) {\n const moduleName = commandId.split('.')[0]\n const registered = commandRegistry.list()\n const sameModule = registered.filter((id) => id.split('.')[0] === moduleName)\n const registeredLoaders = commandRegistry.listLoaders()\n const sameModuleLoaders = registeredLoaders.filter((id) => id === commandId || id.startsWith(`${moduleName}:`))\n const hint = sameModule.length > 0\n ? ` Registered commands for module \"${moduleName}\": [${sameModule.join(', ')}].`\n : sameModuleLoaders.length > 0\n ? ` Command loaders for module \"${moduleName}\" were registered but none loaded \"${commandId}\".`\n : ` No commands or command loaders registered for module \"${moduleName}\". Ensure the command file is imported or generated lazy command loaders are registered.`\n throw new Error(`Command handler not registered for id ${commandId}.${hint}`)\n }\n return handler\n }\n\n private async prepareSnapshots<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>\n ): Promise<{ before?: unknown }> {\n if (!handler.prepare) return {}\n try {\n return (await handler.prepare(options.input, options.ctx)) || {}\n } catch (err) {\n throw err\n }\n }\n\n private async captureAfter<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult\n ): Promise<unknown> {\n if (!handler.captureAfter) return undefined\n return handler.captureAfter(options.input, result, options.ctx)\n }\n\n private async buildLog<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult,\n snapshots: { before?: unknown; after?: unknown }\n ): Promise<CommandLogMetadata | null> {\n if (!handler.buildLog) return null\n const args: CommandLogBuilderArgs<TInput, TResult> = {\n input: options.input,\n result,\n ctx: options.ctx,\n snapshots,\n }\n return (await handler.buildLog(args)) || null\n }\n\n private mergeMetadata(primary?: CommandLogMetadata | null, secondary?: CommandLogMetadata | null): CommandLogMetadata | null {\n if (!primary && !secondary) return null\n return {\n skipLog: secondary?.skipLog ?? primary?.skipLog ?? false,\n tenantId: secondary?.tenantId ?? primary?.tenantId ?? null,\n organizationId: secondary?.organizationId ?? primary?.organizationId ?? null,\n actorUserId: secondary?.actorUserId ?? primary?.actorUserId ?? null,\n actionLabel: secondary?.actionLabel ?? primary?.actionLabel ?? null,\n resourceKind: secondary?.resourceKind ?? primary?.resourceKind ?? null,\n resourceId: secondary?.resourceId ?? primary?.resourceId ?? null,\n parentResourceKind: secondary?.parentResourceKind ?? primary?.parentResourceKind ?? null,\n parentResourceId: secondary?.parentResourceId ?? primary?.parentResourceId ?? null,\n relatedResourceKind: secondary?.relatedResourceKind ?? primary?.relatedResourceKind ?? null,\n relatedResourceId: secondary?.relatedResourceId ?? primary?.relatedResourceId ?? null,\n undoToken: secondary?.undoToken ?? primary?.undoToken ?? null,\n payload: secondary?.payload ?? primary?.payload ?? null,\n snapshotBefore: secondary?.snapshotBefore ?? primary?.snapshotBefore ?? null,\n snapshotAfter: secondary?.snapshotAfter ?? primary?.snapshotAfter ?? null,\n changes: secondary?.changes ?? primary?.changes ?? null,\n context: secondary?.context ?? primary?.context ?? null,\n }\n }\n\n private async persistLog<TInput>(\n commandId: string,\n options: CommandExecutionOptions<TInput>,\n metadata: CommandLogMetadata | null\n ): Promise<ActionLog | null> {\n if (!metadata) return null\n if (metadata.skipLog) return null\n const resourceKind =\n typeof metadata.resourceKind === 'string' ? metadata.resourceKind : null\n if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {\n return null\n }\n let service: ActionLogService | null = null\n try {\n service = (options.ctx.container.resolve('actionLogService') as ActionLogService)\n } catch {\n service = null\n }\n if (!service) return null\n\n const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null\n const organizationId =\n metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null\n const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n commandId,\n }\n\n if (metadata) {\n if ('actionLabel' in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel\n if ('resourceKind' in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind\n if ('resourceId' in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId\n if ('parentResourceKind' in metadata && metadata.parentResourceKind != null) payload.parentResourceKind = metadata.parentResourceKind\n if ('parentResourceId' in metadata && metadata.parentResourceId != null) payload.parentResourceId = metadata.parentResourceId\n if ('relatedResourceKind' in metadata && metadata.relatedResourceKind != null) payload.relatedResourceKind = metadata.relatedResourceKind\n if ('relatedResourceId' in metadata && metadata.relatedResourceId != null) payload.relatedResourceId = metadata.relatedResourceId\n if ('undoToken' in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken\n if ('payload' in metadata && metadata.payload !== undefined) payload.commandPayload = metadata.payload\n if ('snapshotBefore' in metadata && metadata.snapshotBefore !== undefined) payload.snapshotBefore = metadata.snapshotBefore\n if ('snapshotAfter' in metadata && metadata.snapshotAfter !== undefined) payload.snapshotAfter = metadata.snapshotAfter\n if ('changes' in metadata && metadata.changes !== undefined && metadata.changes !== null) payload.changes = metadata.changes\n if ('context' in metadata && metadata.context !== undefined && metadata.context !== null) payload.context = metadata.context\n }\n\n const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, options.input)\n payload.commandPayload = redoEnvelope\n\n return await service.log(payload as ActionLogCreateInput)\n }\n\n private isUndoable(handler: CommandHandler<unknown, unknown>): boolean {\n return handler.isUndoable !== false && typeof handler.undo === 'function'\n }\n\n private async invalidateCacheAfterExecute<TResult>(\n commandId: string,\n options: CommandExecutionOptions<unknown>,\n result: TResult,\n metadata: CommandLogMetadata | null\n ): Promise<void> {\n const resource = typeof metadata?.resourceKind === 'string' ? metadata.resourceKind : null\n if (!resource) return\n try {\n const ctx = options.ctx\n const resultRecord = asRecord(result)\n const resultEntity = asRecord(resultRecord?.entity)\n const inputRecord = asRecord(options.input)\n const inputEntity = asRecord(inputRecord?.entity)\n\n const recordId = pickFirstIdentifier(\n metadata?.resourceId,\n extractPrimaryIdFromCommandResult(result),\n resultRecord?.entityId,\n resultRecord?.id,\n resultRecord?.recordId,\n resultEntity?.id,\n inputRecord?.id,\n inputRecord?.entityId,\n inputRecord?.recordId,\n inputEntity?.id\n )\n\n const organizationId = pickFirstIdentifier(\n metadata?.organizationId,\n resultRecord?.organizationId,\n resultEntity?.organizationId,\n inputRecord?.organizationId,\n inputEntity?.organizationId,\n ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n )\n\n const tenantId = pickFirstIdentifier(\n metadata?.tenantId,\n resultRecord?.tenantId,\n resultEntity?.tenantId,\n inputRecord?.tenantId,\n inputEntity?.tenantId,\n ctx.auth?.tenantId ?? null\n )\n\n const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null)\n\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(metadata?.context ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${commandId}:execute`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache execute-invalidation failed', { commandId, err })\n } catch {}\n }\n }\n }\n\n private async invalidateCacheAfterUndo(log: ActionLog, ctx: CommandRuntimeContext): Promise<void> {\n const resource = typeof log.resourceKind === 'string' ? log.resourceKind : null\n if (!resource) return\n try {\n const recordId = pickFirstIdentifier(log.resourceId)\n const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(log.contextJson ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(log.commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${log.commandId}:undo`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache undo-invalidation failed', { commandId: log.commandId, err })\n } catch {}\n }\n }\n }\n\n private async flushCrudSideEffects(container: AwilixContainer, suppress?: BulkImportSuppression): Promise<void> {\n try {\n const dataEngine = (container.resolve('dataEngine') as DataEngine)\n await dataEngine.flushOrmEntityChanges(suppress)\n } catch (error) {\n if (isReadProjectionAlwaysConsistent()) {\n throw error\n }\n // best-effort: failures should not block command execution\n }\n }\n}\n\ntype RedoEnvelope = {\n __redoInput: unknown\n [key: string]: unknown\n}\n\nfunction wrapRedoPayload(existing: unknown, input: unknown): RedoEnvelope {\n if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {\n const envelope: RedoEnvelope = { __redoInput: input }\n if (existing !== undefined) envelope.value = existing\n return envelope\n }\n const current = existing as Record<string, unknown>\n if ('__redoInput' in current && current.__redoInput !== undefined) {\n return current as RedoEnvelope\n }\n return { __redoInput: input, ...current }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.6925.1.cefa200fa4';\nexport const appVersion = APP_VERSION;\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.6925.1.cefa200fa4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@mikro-orm/core": "^7.1.8",
|
|
106
106
|
"@mikro-orm/decorators": "^7.1.8",
|
|
107
107
|
"@mikro-orm/postgresql": "^7.1.8",
|
|
108
|
-
"@open-mercato/cache": "0.6.8-develop.
|
|
108
|
+
"@open-mercato/cache": "0.6.8-develop.6925.1.cefa200fa4",
|
|
109
109
|
"@types/sanitize-html": "^2.16.1",
|
|
110
110
|
"dotenv": "^17.4.2",
|
|
111
111
|
"pino": "^10.3.1",
|
|
@@ -124,6 +124,149 @@ describe('CommandBus', () => {
|
|
|
124
124
|
expect(execute).toHaveBeenCalledTimes(1)
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
+
it('merges command interceptor beforeExecute returned metadata.logContext into logged context with correct precedence', async () => {
|
|
128
|
+
const logMock = jest.fn(async () => ({ id: 'log-entry' }))
|
|
129
|
+
registerCommand({
|
|
130
|
+
id: 'test.command.interceptor-context',
|
|
131
|
+
execute: jest.fn(async () => ({ ok: true })),
|
|
132
|
+
buildLog: jest.fn(() => ({
|
|
133
|
+
actionLabel: 'Test',
|
|
134
|
+
resourceKind: 'test',
|
|
135
|
+
resourceId: '123',
|
|
136
|
+
context: {
|
|
137
|
+
original: 'buildlog-original-value',
|
|
138
|
+
interceptorOverridden: 'buildlog-takes-precedence',
|
|
139
|
+
},
|
|
140
|
+
})),
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
registerCommandInterceptors([
|
|
144
|
+
{
|
|
145
|
+
moduleId: 'test-module',
|
|
146
|
+
interceptors: [
|
|
147
|
+
{
|
|
148
|
+
id: 'test-interceptor-priority-2',
|
|
149
|
+
targetCommand: 'test.command.*',
|
|
150
|
+
priority: 60, // runs second
|
|
151
|
+
beforeExecute: async () => ({
|
|
152
|
+
ok: true,
|
|
153
|
+
metadata: {
|
|
154
|
+
logContext: {
|
|
155
|
+
ip: '127.0.0.1',
|
|
156
|
+
requestId: 'req-second',
|
|
157
|
+
interceptorOverlap: 'second-wins',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'test-interceptor-priority-1',
|
|
164
|
+
targetCommand: 'test.command.*',
|
|
165
|
+
priority: 40, // runs first
|
|
166
|
+
beforeExecute: async () => ({
|
|
167
|
+
ok: true,
|
|
168
|
+
metadata: {
|
|
169
|
+
logContext: {
|
|
170
|
+
requestId: 'req-first',
|
|
171
|
+
interceptorOverlap: 'first-loss',
|
|
172
|
+
baseOverridden: 'interceptor-wins-over-base',
|
|
173
|
+
interceptorOverridden: 'interceptor-loss-to-buildlog',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
])
|
|
181
|
+
|
|
182
|
+
const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
|
|
183
|
+
container.register({ actionLogService: asValue({ log: logMock }) })
|
|
184
|
+
|
|
185
|
+
const bus = new CommandBus()
|
|
186
|
+
const ctx = {
|
|
187
|
+
container,
|
|
188
|
+
auth: { sub: 'user-1', tenantId: 'tenant-1', orgId: null },
|
|
189
|
+
organizationScope: null,
|
|
190
|
+
selectedOrganizationId: null,
|
|
191
|
+
organizationIds: null,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await bus.execute('test.command.interceptor-context', {
|
|
195
|
+
input: {},
|
|
196
|
+
ctx,
|
|
197
|
+
metadata: {
|
|
198
|
+
context: {
|
|
199
|
+
original: 'base-original-value', // will be overridden by buildLog
|
|
200
|
+
baseOverridden: 'base-original-to-be-overridden',
|
|
201
|
+
untouched: 'base-untouched',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
expect(logMock).toHaveBeenCalledWith(
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
context: {
|
|
209
|
+
untouched: 'base-untouched',
|
|
210
|
+
baseOverridden: 'interceptor-wins-over-base',
|
|
211
|
+
requestId: 'req-second',
|
|
212
|
+
interceptorOverlap: 'second-wins',
|
|
213
|
+
ip: '127.0.0.1',
|
|
214
|
+
original: 'buildlog-original-value',
|
|
215
|
+
interceptorOverridden: 'buildlog-takes-precedence',
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('does not promote a generic interceptor metadata.context key into the logged context', async () => {
|
|
222
|
+
const logMock = jest.fn(async () => ({ id: 'log-entry' }))
|
|
223
|
+
registerCommand({
|
|
224
|
+
id: 'test.command.private-metadata',
|
|
225
|
+
execute: jest.fn(async () => ({ ok: true })),
|
|
226
|
+
buildLog: jest.fn(() => ({
|
|
227
|
+
actionLabel: 'Test',
|
|
228
|
+
resourceKind: 'test',
|
|
229
|
+
resourceId: '123',
|
|
230
|
+
})),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
registerCommandInterceptors([
|
|
234
|
+
{
|
|
235
|
+
moduleId: 'test-module',
|
|
236
|
+
interceptors: [
|
|
237
|
+
{
|
|
238
|
+
id: 'test-interceptor-private-metadata',
|
|
239
|
+
targetCommand: 'test.command.*',
|
|
240
|
+
beforeExecute: async () => ({
|
|
241
|
+
ok: true,
|
|
242
|
+
// `context` here is the interceptor's own after-hook state, not audit input.
|
|
243
|
+
metadata: { context: { internalHandle: 'must-stay-private' } },
|
|
244
|
+
}),
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
])
|
|
249
|
+
|
|
250
|
+
const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
|
|
251
|
+
container.register({ actionLogService: asValue({ log: logMock }) })
|
|
252
|
+
|
|
253
|
+
await new CommandBus().execute('test.command.private-metadata', {
|
|
254
|
+
input: {},
|
|
255
|
+
ctx: {
|
|
256
|
+
container,
|
|
257
|
+
auth: { sub: 'user-1', tenantId: 'tenant-1', orgId: null },
|
|
258
|
+
organizationScope: null,
|
|
259
|
+
selectedOrganizationId: null,
|
|
260
|
+
organizationIds: null,
|
|
261
|
+
},
|
|
262
|
+
metadata: { context: { untouched: 'base-untouched' } },
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
expect(logMock).toHaveBeenCalledWith(
|
|
266
|
+
expect.objectContaining({ context: { untouched: 'base-untouched' } })
|
|
267
|
+
)
|
|
268
|
+
})
|
|
269
|
+
|
|
127
270
|
describe('interceptor rejections', () => {
|
|
128
271
|
const blockingInterceptor = (result: Record<string, unknown>): CommandInterceptor => ({
|
|
129
272
|
id: 'test.block',
|
|
@@ -267,6 +267,30 @@ export class CommandBus {
|
|
|
267
267
|
const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }
|
|
268
268
|
const logMeta = await this.buildLog(handler, effectiveOptions, result, snapshotsWithAfter)
|
|
269
269
|
let mergedMeta = this.mergeMetadata(effectiveOptions.metadata, logMeta)
|
|
270
|
+
// Interceptors opt into audit-log enrichment with a reserved `logContext` key rather
|
|
271
|
+
// than the generic `context` one, so the metadata an interceptor already passes to its
|
|
272
|
+
// own afterExecute hook is never silently promoted into audit storage.
|
|
273
|
+
// Map iteration order is interceptor priority order (see collectMatching), so a
|
|
274
|
+
// later-priority interceptor overrides an earlier one on key collisions.
|
|
275
|
+
let interceptorContextMerged: Record<string, unknown> = {}
|
|
276
|
+
for (const meta of interceptorMetadata.values()) {
|
|
277
|
+
const logContextRecord = asRecord(asRecord(meta)?.logContext)
|
|
278
|
+
if (!logContextRecord) continue
|
|
279
|
+
interceptorContextMerged = {
|
|
280
|
+
...interceptorContextMerged,
|
|
281
|
+
...logContextRecord,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const baseContext = asRecord(effectiveOptions.metadata?.context) ?? {}
|
|
285
|
+
const logMetaContext = asRecord(logMeta?.context) ?? {}
|
|
286
|
+
if (Object.keys(interceptorContextMerged).length > 0 || Object.keys(baseContext).length > 0 || Object.keys(logMetaContext).length > 0) {
|
|
287
|
+
mergedMeta = mergedMeta ?? {}
|
|
288
|
+
mergedMeta.context = {
|
|
289
|
+
...baseContext,
|
|
290
|
+
...interceptorContextMerged,
|
|
291
|
+
...logMetaContext,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
270
294
|
const undoable = this.isUndoable(handler)
|
|
271
295
|
if (undoable) {
|
|
272
296
|
mergedMeta = mergedMeta ?? {}
|
|
@@ -77,7 +77,15 @@ export interface CommandInterceptorBeforeResult {
|
|
|
77
77
|
body?: Record<string, unknown>
|
|
78
78
|
/** Modified input — shallow-merged into command input if ok:true */
|
|
79
79
|
modifiedInput?: Record<string, unknown>
|
|
80
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Metadata passed to the corresponding after hook.
|
|
82
|
+
*
|
|
83
|
+
* Reserved key — `logContext`: a record here (e.g. `{ logContext: { ip: '...' } }`) is
|
|
84
|
+
* shallow-merged into the persisted `ActionLog.context_json`. Every other key stays
|
|
85
|
+
* private to the after hook. Contributed keys take precedence over
|
|
86
|
+
* `options.metadata.context` but yield to `buildLog().context` on conflicts; when two
|
|
87
|
+
* interceptors contribute the same key, the higher `priority` (later-running) one wins.
|
|
88
|
+
*/
|
|
81
89
|
metadata?: Record<string, unknown>
|
|
82
90
|
}
|
|
83
91
|
|