@open-mercato/shared 0.8.1-develop.7296.1.2111d779db → 0.9.0
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +2 -0
- package/dist/lib/auth/jwt.js +4 -0
- package/dist/lib/auth/jwt.js.map +2 -2
- package/dist/lib/auth/organizationScope.js +15 -1
- package/dist/lib/auth/organizationScope.js.map +2 -2
- package/dist/lib/availability/catalogOnlyProvider.js +77 -0
- package/dist/lib/availability/catalogOnlyProvider.js.map +7 -0
- package/dist/lib/availability/index.js +8 -0
- package/dist/lib/availability/index.js.map +7 -0
- package/dist/lib/availability/registry.js +47 -0
- package/dist/lib/availability/registry.js.map +7 -0
- package/dist/lib/availability/types.js +7 -0
- package/dist/lib/availability/types.js.map +7 -0
- package/dist/lib/catalog-visibility/index.js +11 -0
- package/dist/lib/catalog-visibility/index.js.map +7 -0
- package/dist/lib/catalog-visibility/intersectScopes.js +49 -0
- package/dist/lib/catalog-visibility/intersectScopes.js.map +7 -0
- package/dist/lib/catalog-visibility/matchesOne.js +27 -0
- package/dist/lib/catalog-visibility/matchesOne.js.map +7 -0
- package/dist/lib/catalog-visibility/matchesScope.js +9 -0
- package/dist/lib/catalog-visibility/matchesScope.js.map +7 -0
- package/dist/lib/catalog-visibility/types.js +1 -0
- package/dist/lib/catalog-visibility/types.js.map +7 -0
- package/dist/lib/catalog-visibility/unionScopes.js +9 -0
- package/dist/lib/catalog-visibility/unionScopes.js.map +7 -0
- package/dist/lib/commands/command-bus.js +383 -125
- package/dist/lib/commands/command-bus.js.map +3 -3
- package/dist/lib/commands/flush.js +16 -0
- package/dist/lib/commands/flush.js.map +2 -2
- package/dist/lib/commands/index.js +8 -0
- package/dist/lib/commands/index.js.map +2 -2
- package/dist/lib/commands/redo.js +2 -7
- package/dist/lib/commands/redo.js.map +2 -2
- package/dist/lib/commands/transaction-lifetime.js +67 -0
- package/dist/lib/commands/transaction-lifetime.js.map +7 -0
- package/dist/lib/commands/types.js.map +2 -2
- package/dist/lib/commands/undo.js +54 -2
- package/dist/lib/commands/undo.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +64 -9
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +42 -18
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/data/engine.js +12 -3
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/accentInsensitiveSearch.js +21 -0
- package/dist/lib/db/accentInsensitiveSearch.js.map +7 -0
- package/dist/lib/encryption/kms.js +1 -5
- package/dist/lib/encryption/kms.js.map +2 -2
- package/dist/lib/encryption/likeFilterWarning.js +3 -1
- package/dist/lib/encryption/likeFilterWarning.js.map +2 -2
- package/dist/lib/encryption/migration-backfill.js +69 -0
- package/dist/lib/encryption/migration-backfill.js.map +7 -0
- package/dist/lib/encryption/policyMemo.js +28 -0
- package/dist/lib/encryption/policyMemo.js.map +7 -0
- package/dist/lib/encryption/subscriber.js +30 -3
- package/dist/lib/encryption/subscriber.js.map +2 -2
- package/dist/lib/encryption/tenantDataEncryptionService.js +113 -200
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +3 -3
- package/dist/lib/modules/api-route-identity.js +102 -0
- package/dist/lib/modules/api-route-identity.js.map +7 -0
- package/dist/lib/telemetry/runtime.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/events/factory.js +20 -0
- package/dist/modules/events/factory.js.map +2 -2
- package/dist/modules/registry.js +3 -1
- package/dist/modules/registry.js.map +2 -2
- package/package.json +12 -5
- package/src/lib/auth/__tests__/jwt.test.ts +12 -0
- package/src/lib/auth/__tests__/organizationScope.test.ts +27 -0
- package/src/lib/auth/jwt.ts +9 -0
- package/src/lib/auth/organizationScope.ts +39 -0
- package/src/lib/availability/__tests__/catalogOnlyProvider.test.ts +166 -0
- package/src/lib/availability/__tests__/registry.test.ts +130 -0
- package/src/lib/availability/catalogOnlyProvider.ts +152 -0
- package/src/lib/availability/index.ts +18 -0
- package/src/lib/availability/registry.ts +131 -0
- package/src/lib/availability/types.ts +86 -0
- package/src/lib/catalog-visibility/__tests__/intersectScopes.test.ts +168 -0
- package/src/lib/catalog-visibility/__tests__/matchesOne.test.ts +116 -0
- package/src/lib/catalog-visibility/__tests__/matchesScope.test.ts +35 -0
- package/src/lib/catalog-visibility/__tests__/testFixtures.ts +66 -0
- package/src/lib/catalog-visibility/__tests__/unionScopes.test.ts +62 -0
- package/src/lib/catalog-visibility/index.ts +5 -0
- package/src/lib/catalog-visibility/intersectScopes.ts +93 -0
- package/src/lib/catalog-visibility/matchesOne.ts +28 -0
- package/src/lib/catalog-visibility/matchesScope.ts +8 -0
- package/src/lib/catalog-visibility/types.ts +31 -0
- package/src/lib/catalog-visibility/unionScopes.ts +16 -0
- package/src/lib/commands/__tests__/command-bus.atomic-replay.test.ts +650 -0
- package/src/lib/commands/__tests__/command-bus.test.ts +175 -0
- package/src/lib/commands/__tests__/command-bus.undo-toctou.test.ts +1 -1
- package/src/lib/commands/__tests__/redo.test.ts +4 -0
- package/src/lib/commands/__tests__/transaction-lifetime.test.ts +118 -0
- package/src/lib/commands/__tests__/undo.test.ts +90 -1
- package/src/lib/commands/command-bus.ts +463 -148
- package/src/lib/commands/flush.ts +16 -0
- package/src/lib/commands/index.ts +7 -0
- package/src/lib/commands/redo.ts +4 -8
- package/src/lib/commands/transaction-lifetime.ts +88 -0
- package/src/lib/commands/types.ts +91 -0
- package/src/lib/commands/undo.ts +79 -1
- package/src/lib/crud/__tests__/crud-factory.cache-tenant-level.test.ts +149 -0
- package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +48 -6
- package/src/lib/crud/__tests__/crud-factory.test.ts +224 -27
- package/src/lib/crud/__tests__/enricher-runner.test.ts +270 -5
- package/src/lib/crud/enricher-runner.ts +83 -9
- package/src/lib/crud/factory.ts +46 -19
- package/src/lib/data/__tests__/engine.pin-organization-id.test.ts +102 -0
- package/src/lib/data/engine.ts +12 -0
- package/src/lib/db/__tests__/accentInsensitiveSearch.test.ts +51 -0
- package/src/lib/db/accentInsensitiveSearch.ts +70 -0
- package/src/lib/encryption/__tests__/dek-lifecycle.test.ts +11 -9
- package/src/lib/encryption/__tests__/fixtures/policy-process-worker.ts +96 -0
- package/src/lib/encryption/__tests__/kms.test.ts +45 -3
- package/src/lib/encryption/__tests__/likeFilterWarning.test.ts +43 -0
- package/src/lib/encryption/__tests__/migration-backfill.test.ts +57 -0
- package/src/lib/encryption/__tests__/process-policy-coherence.test.ts +128 -0
- package/src/lib/encryption/__tests__/subscriber.deep-decrypt-collections.test.ts +66 -1
- package/src/lib/encryption/__tests__/subscriber.test.ts +3 -0
- package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +412 -51
- package/src/lib/encryption/kms.ts +1 -6
- package/src/lib/encryption/likeFilterWarning.ts +10 -2
- package/src/lib/encryption/migration-backfill.ts +90 -0
- package/src/lib/encryption/policyMemo.ts +51 -0
- package/src/lib/encryption/subscriber.ts +41 -4
- package/src/lib/encryption/tenantDataEncryptionService.ts +153 -219
- package/src/lib/i18n/__tests__/detect-locale-narrowed.test.ts +20 -10
- package/src/lib/modules/api-route-identity.ts +116 -0
- package/src/lib/telemetry/runtime.ts +8 -0
- package/src/modules/__tests__/registry.test.ts +92 -0
- package/src/modules/events/__tests__/broadcast-coalescing-declaration.test.ts +53 -0
- package/src/modules/events/factory.ts +31 -0
- package/src/modules/events/types.ts +13 -0
- package/src/modules/notifications/types.ts +1 -0
- package/src/modules/registry.ts +4 -1
|
@@ -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 // 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 onBehalfOfUserId: secondary?.onBehalfOfUserId ?? primary?.onBehalfOfUserId ?? 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 // On-behalf-of attribution (Wave 4 P2): when `ctx.runAs` is set the actor is\n // the agent principal and the human it acts for is recorded separately. This\n // funnels agent writes through the SAME ActionLog path as a human's \u2014 only the\n // attribution differs (actorUserId=agent, onBehalfOfUserId=human, source='agent').\n const runAs = options.ctx.runAs ?? null\n const actorUserId = runAs?.actorUserId ?? metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const onBehalfOfUserId = runAs ? (runAs.onBehalfOfUserId ?? null) : (metadata.onBehalfOfUserId ?? null)\n const systemActorContext = !actorUserId && options.ctx.systemActor === true\n ? { systemActor: 'system:command' }\n : null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n onBehalfOfUserId: onBehalfOfUserId ?? 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) {\n payload.context = { ...(systemActorContext ?? {}), ...metadata.context }\n } else if (systemActorContext) {\n payload.context = systemActorContext\n }\n }\n\n if (runAs) {\n // Stamp the audit source so `deriveActionLogSource` projects `sourceKey='agent'`.\n // Merge into any caller-provided context rather than replacing it.\n const baseContext = asRecord(payload.context) ?? {}\n payload.context = { ...baseContext, source: runAs.source }\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
|
-
"names": []
|
|
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 CommandUndoLogEntry,\n} from './types'\nimport { defaultUndoToken } from './types'\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'\nimport { withAtomicFlush } from './flush'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n getTransactionLifetime,\n onTransactionLifetimeComplete,\n ownsTransactionLifetime,\n type TransactionLifetime,\n} from './transaction-lifetime'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\ntype CommandActionLogService = {\n findByUndoToken(undoToken: string): Promise<ActionLog | null>\n claimForUndo(id: string, transactionalEm?: EntityManager): Promise<boolean>\n releaseUndoClaim(id: string, transactionalEm?: EntityManager): Promise<boolean>\n markUndone(id: string, traceInput?: ActionLogCreateInput, transactionalEm?: EntityManager): Promise<ActionLog | null | void>\n claimForRedo(id: string, transactionalEm?: EntityManager): Promise<boolean>\n log(input: ActionLogCreateInput, transactionalEm?: EntityManager): Promise<ActionLog | null>\n}\n\ntype AtomicReplayTransaction = {\n em: EntityManager\n joinsAmbient: boolean\n}\n\nfunction isInTransaction(em: EntityManager): boolean {\n const probe = (em as { isInTransaction?: () => boolean }).isInTransaction\n return typeof probe === 'function' && probe.call(em)\n}\n\nfunction resolveAtomicReplayTransaction(ctx: CommandRuntimeContext): AtomicReplayTransaction {\n const em = ctx.transactionalEm ?? (ctx.container.resolve('em') as EntityManager)\n const joinsAmbient = isInTransaction(em)\n if (joinsAmbient && !ownsTransactionLifetime(em, ctx.transactionLifetime)) {\n throw new Error('[internal] Ambient atomic replay requires its outer transaction lifetime owner')\n }\n return { em, joinsAmbient }\n}\n\nfunction bindReplayTransactionContext(\n ctx: CommandRuntimeContext,\n em: EntityManager,\n expectedLifetime?: TransactionLifetime,\n): CommandRuntimeContext {\n const transactionLifetime = getTransactionLifetime(em)\n if (!transactionLifetime || (expectedLifetime && transactionLifetime !== expectedLifetime)) {\n throw new Error('[internal] Atomic replay transaction lifetime owner changed before replay')\n }\n return { ...ctx, transactionalEm: em, transactionLifetime }\n}\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 const replayLogEntry = options.redoLogEntry ?? null\n const isAtomicReplay = replayLogEntry !== null && handler.atomicReplay === true\n const allInterceptors = getAllCommandInterceptorInstances()\n let interceptorMetadata = new Map<string, Record<string, unknown>>()\n let effectiveOptions = options\n let userFeatures: string[] = []\n const runBeforeExecuteInterceptors = async (baseOptions: CommandExecutionOptions<TInput>) => {\n const resolvedUserFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(baseOptions.ctx)\n : []\n if (!allInterceptors.length) {\n return {\n effectiveOptions: baseOptions,\n metadata: new Map<string, Record<string, unknown>>(),\n userFeatures: resolvedUserFeatures,\n }\n }\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: baseOptions.ctx.auth ?? null,\n selectedOrganizationId: baseOptions.ctx.selectedOrganizationId ?? baseOptions.ctx.auth?.orgId ?? null,\n container: baseOptions.ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBefore(\n allInterceptors, commandId, baseOptions.input, interceptorCtx, resolvedUserFeatures,\n )\n if (!beforeResult.ok) {\n const blocked = beforeResult.error!\n throw new CommandInterceptorError(blocked.message, { status: blocked.status, body: blocked.body })\n }\n const preparedOptions = beforeResult.modifiedInput\n ? {\n ...baseOptions,\n input: { ...(baseOptions.input as object), ...beforeResult.modifiedInput } as TInput,\n }\n : baseOptions\n return {\n effectiveOptions: preparedOptions,\n metadata: beforeResult.metadataByInterceptor,\n userFeatures: resolvedUserFeatures,\n }\n }\n\n if (replayLogEntry && !isAtomicReplay) {\n await this.runReplayTransactionGuard(options.ctx, 'redo', replayLogEntry)\n if (handler.authorizeReplay) {\n await handler.authorizeReplay({\n operation: 'redo',\n input: options.input,\n ctx: options.ctx,\n logEntry: replayLogEntry,\n })\n }\n }\n\n if (!isAtomicReplay) {\n const prepared = await runBeforeExecuteInterceptors(options)\n effectiveOptions = prepared.effectiveOptions\n interceptorMetadata = prepared.metadata\n userFeatures = prepared.userFeatures\n }\n\n const executeCore = async (coreOptions: CommandExecutionOptions<TInput>) => {\n const sourceLog = coreOptions.redoLogEntry ?? null\n const snapshots = await this.prepareSnapshots(handler, coreOptions)\n const result =\n sourceLog && typeof handler.redo === 'function'\n ? await handler.redo({ input: coreOptions.input, ctx: coreOptions.ctx, logEntry: sourceLog })\n : await handler.execute(coreOptions.input, coreOptions.ctx)\n const afterSnapshot = await this.captureAfter(handler, coreOptions, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, coreOptions, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(coreOptions.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(coreOptions.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 && mergedMeta?.replayable !== false) {\n mergedMeta = mergedMeta ?? {}\n if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken()\n if (mergedMeta.actorUserId === undefined) mergedMeta.actorUserId = coreOptions.ctx.auth?.sub ?? null\n } else if (mergedMeta?.replayable === false) {\n mergedMeta.undoToken = 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, coreOptions, mergedMeta)\n return { result, mergedMeta, logEntry }\n }\n\n let replaySourceFinalized = false\n let coreResult: Awaited<ReturnType<typeof executeCore>>\n const atomicSourceLog = options.redoLogEntry ?? null\n let ambientReplay: AtomicReplayTransaction | null = null\n let replayTransactionLifetime: TransactionLifetime | null = null\n if (atomicSourceLog && isAtomicReplay) {\n ambientReplay = resolveAtomicReplayTransaction(options.ctx)\n const replayEm = ambientReplay.em\n await withAtomicFlush(replayEm, [async () => {\n const replayCtx = bindReplayTransactionContext(\n options.ctx,\n replayEm,\n ambientReplay?.joinsAmbient ? options.ctx.transactionLifetime : undefined,\n )\n replayTransactionLifetime = replayCtx.transactionLifetime ?? null\n const transactionalBaseOptions: CommandExecutionOptions<TInput> = {\n ...options,\n ctx: replayCtx,\n }\n if (!atomicSourceLog.id) throw new Error('[internal] Atomic redo source log id is required')\n if (handler.stabilizeReplay) {\n await handler.stabilizeReplay({\n operation: 'redo',\n input: transactionalBaseOptions.input,\n ctx: transactionalBaseOptions.ctx,\n logEntry: atomicSourceLog,\n })\n }\n await this.runReplayTransactionGuard(\n transactionalBaseOptions.ctx,\n 'redo',\n atomicSourceLog,\n replayEm,\n )\n const prepared = await runBeforeExecuteInterceptors(transactionalBaseOptions)\n effectiveOptions = prepared.effectiveOptions\n interceptorMetadata = prepared.metadata\n userFeatures = prepared.userFeatures\n const service = effectiveOptions.ctx.container.resolve('actionLogService') as CommandActionLogService\n if (handler.authorizeReplay) {\n await handler.authorizeReplay({\n operation: 'redo',\n input: effectiveOptions.input,\n ctx: effectiveOptions.ctx,\n logEntry: atomicSourceLog,\n })\n }\n const claimed = await service.claimForRedo(atomicSourceLog.id, replayEm)\n if (!claimed) throw new Error('[internal] Redo source already consumed')\n coreResult = await executeCore(effectiveOptions)\n }], { transaction: true, label: `${commandId}.redo` })\n replaySourceFinalized = !ambientReplay.joinsAmbient\n } else {\n coreResult = await executeCore(effectiveOptions)\n }\n const { result, mergedMeta, logEntry } = coreResult!\n\n const runPostCommit = async () => {\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 finalResult\n }\n\n const executionResult: CommandExecuteResult<TResult> = {\n result,\n logEntry,\n replaySourceFinalized,\n }\n if (ambientReplay?.joinsAmbient) {\n if (!replayTransactionLifetime || !ownsTransactionLifetime(ambientReplay.em, replayTransactionLifetime)) {\n throw new Error('[internal] Ambient atomic replay lost its outer transaction lifetime owner')\n }\n onTransactionLifetimeComplete(ambientReplay.em, async (outcome) => {\n if (outcome !== 'committed') return\n executionResult.result = await runPostCommit()\n executionResult.replaySourceFinalized = true\n })\n return executionResult\n }\n\n executionResult.result = await runPostCommit()\n return executionResult\n }\n\n async undo(undoToken: string, ctx: CommandRuntimeContext): Promise<void> {\n const service = (ctx.container.resolve('actionLogService') as CommandActionLogService)\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 const isAtomicReplay = handler.atomicReplay === true\n const allInterceptors = getAllCommandInterceptorInstances()\n let undoInterceptorMetadata = new Map<string, Record<string, unknown>>()\n let userFeatures: string[] = []\n const runBeforeUndoInterceptors = async (replayCtx: CommandRuntimeContext) => {\n const resolvedUserFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(replayCtx)\n : []\n if (!allInterceptors.length) {\n return {\n metadata: new Map<string, Record<string, unknown>>(),\n userFeatures: resolvedUserFeatures,\n }\n }\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: replayCtx.auth ?? null,\n selectedOrganizationId: replayCtx.selectedOrganizationId ?? replayCtx.auth?.orgId ?? null,\n container: replayCtx.container,\n }\n const beforeResult = await runCommandInterceptorsBeforeUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx, resolvedUserFeatures,\n )\n if (!beforeResult.ok) {\n const blocked = beforeResult.error!\n throw new CommandInterceptorError(blocked.message, { status: blocked.status, body: blocked.body })\n }\n return {\n metadata: beforeResult.metadataByInterceptor,\n userFeatures: resolvedUserFeatures,\n }\n }\n\n if (!isAtomicReplay) {\n await this.runReplayTransactionGuard(ctx, 'undo', log)\n if (handler.authorizeReplay) {\n await handler.authorizeReplay({\n operation: 'undo',\n input: log.commandPayload as Parameters<NonNullable<typeof handler.authorizeReplay>>[0]['input'],\n ctx,\n logEntry: log,\n })\n }\n }\n\n const claimUndo = async (replayEm?: EntityManager) => {\n // Two contenders can both pass the initial lookup; only the compare-and-set\n // below may proceed. On the atomic path this state is still uncommitted and\n // rolls back with every handler/log write if anything later fails.\n const claimed = replayEm\n ? await service.claimForUndo(log.id, replayEm)\n : await service.claimForUndo(log.id)\n if (!claimed) throw new Error('[internal] Undo token already consumed')\n }\n const finishUndo = async (replayCtx: CommandRuntimeContext, replayEm?: EntityManager) => {\n await handler.undo!({\n input: log.commandPayload as Parameters<NonNullable<typeof handler.undo>>[0]['input'],\n ctx: replayCtx,\n logEntry: log,\n })\n const traceLog = this.buildUndoTraceLog(log, replayCtx)\n if (replayEm) {\n await service.markUndone(log.id, traceLog, replayEm)\n } else {\n await service.markUndone(log.id, traceLog)\n }\n }\n\n let ambientReplay: AtomicReplayTransaction | null = null\n let replayTransactionLifetime: TransactionLifetime | null = null\n if (isAtomicReplay) {\n ambientReplay = resolveAtomicReplayTransaction(ctx)\n const replayEm = ambientReplay.em\n await withAtomicFlush(replayEm, [async () => {\n const replayCtx = bindReplayTransactionContext(\n ctx,\n replayEm,\n ambientReplay?.joinsAmbient ? ctx.transactionLifetime : undefined,\n )\n replayTransactionLifetime = replayCtx.transactionLifetime ?? null\n if (handler.stabilizeReplay) {\n await handler.stabilizeReplay({\n operation: 'undo',\n input: log.commandPayload as Parameters<NonNullable<typeof handler.stabilizeReplay>>[0]['input'],\n ctx: replayCtx,\n logEntry: log,\n })\n }\n await this.runReplayTransactionGuard(replayCtx, 'undo', log, replayEm)\n const prepared = await runBeforeUndoInterceptors(replayCtx)\n undoInterceptorMetadata = prepared.metadata\n userFeatures = prepared.userFeatures\n if (handler.authorizeReplay) {\n await handler.authorizeReplay({\n operation: 'undo',\n input: log.commandPayload as Parameters<NonNullable<typeof handler.authorizeReplay>>[0]['input'],\n ctx: replayCtx,\n logEntry: log,\n })\n }\n await claimUndo(replayEm)\n await finishUndo(replayCtx, replayEm)\n }], {\n transaction: true,\n label: `${log.commandId}.undo`,\n })\n } else {\n try {\n // Preserve the established non-atomic lifecycle exactly: authorization,\n // source claim, before-undo interceptors, handler, then finalization.\n await claimUndo()\n const prepared = await runBeforeUndoInterceptors(ctx)\n undoInterceptorMetadata = prepared.metadata\n userFeatures = prepared.userFeatures\n await finishUndo(ctx)\n } catch (err) {\n // Legacy handlers retain their compensating release behavior because\n // their domain mutation may not share the action-log transaction.\n await service.releaseUndoClaim(log.id).catch(() => {})\n throw err\n }\n }\n\n const runPostCommit = async () => {\n // Post-commit hooks and side effects run only after an atomic replay has\n // durably committed both the domain state and source/trace log state.\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 }\n\n if (ambientReplay?.joinsAmbient) {\n if (!replayTransactionLifetime || !ownsTransactionLifetime(ambientReplay.em, replayTransactionLifetime)) {\n throw new Error('[internal] Ambient atomic replay lost its outer transaction lifetime owner')\n }\n onTransactionLifetimeComplete(ambientReplay.em, async (outcome) => {\n if (outcome === 'committed') await runPostCommit()\n })\n return\n }\n\n await runPostCommit()\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 type RbacLike = {\n getGrantedFeatures: (\n userId: string,\n opts: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n getGrantedFeaturesWithEntityManager?: (\n em: EntityManager,\n userId: string,\n opts: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n }\n let rbac: RbacLike | undefined\n try {\n rbac = ctx.container.resolve('rbacService') as RbacLike | undefined\n } catch {\n // Intentional: rbacService is not registered in all runtime contexts (CLI, tests, bootstrap).\n return []\n }\n const scope = {\n tenantId: ctx.auth.tenantId,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId,\n }\n if (ctx.transactionalEm) {\n if (!rbac?.getGrantedFeaturesWithEntityManager) return []\n return await rbac.getGrantedFeaturesWithEntityManager(\n ctx.transactionalEm,\n ctx.auth.sub,\n scope,\n )\n }\n try {\n if (rbac?.getGrantedFeatures) {\n return await rbac.getGrantedFeatures(ctx.auth.sub, scope)\n }\n } catch {\n // Outside a transaction a failed lookup cannot poison anything; interceptors without\n // feature gating still run. Inside one the error is rethrown above, because the failed\n // query has already aborted the transaction.\n }\n return []\n }\n\n private async runReplayTransactionGuard(\n ctx: CommandRuntimeContext,\n operation: 'undo' | 'redo',\n logEntry: CommandUndoLogEntry,\n transactionalEm?: EntityManager,\n ): Promise<void> {\n if (!ctx.replayTransactionGuard) return\n if (transactionalEm) {\n await ctx.replayTransactionGuard({ operation, logEntry, transactionalEm })\n return\n }\n const requestEm = ctx.container.resolve('em') as EntityManager\n const guardEm = typeof requestEm.fork === 'function' ? requestEm.fork() : requestEm\n await withAtomicFlush(guardEm, [async () => {\n await ctx.replayTransactionGuard!({ operation, logEntry, transactionalEm: guardEm })\n }], { transaction: true, label: `${logEntry.commandId}.${operation}.request-guard` })\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 replayable:\n secondary?.replayable === false || primary?.replayable === false\n ? false\n : secondary?.replayable ?? primary?.replayable,\n tenantId: secondary?.tenantId ?? primary?.tenantId ?? null,\n organizationId: secondary?.organizationId ?? primary?.organizationId ?? null,\n actorUserId: secondary?.actorUserId ?? primary?.actorUserId ?? null,\n onBehalfOfUserId: secondary?.onBehalfOfUserId ?? primary?.onBehalfOfUserId ?? 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 // A redacted redo input always wins: it exists precisely because the raw\n // input carries a secret that must not reach `action_logs`.\n redoInput: secondary?.redoInput !== undefined ? secondary.redoInput : primary?.redoInput,\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: CommandActionLogService | null = null\n try {\n service = (options.ctx.container.resolve('actionLogService') as CommandActionLogService)\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 // On-behalf-of attribution (Wave 4 P2): when `ctx.runAs` is set the actor is\n // the agent principal and the human it acts for is recorded separately. This\n // funnels agent writes through the SAME ActionLog path as a human's \u2014 only the\n // attribution differs (actorUserId=agent, onBehalfOfUserId=human, source='agent').\n const runAs = options.ctx.runAs ?? null\n const actorUserId = runAs?.actorUserId ?? metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const onBehalfOfUserId = runAs ? (runAs.onBehalfOfUserId ?? null) : (metadata.onBehalfOfUserId ?? null)\n const systemActorContext = !actorUserId && options.ctx.systemActor === true\n ? { systemActor: 'system:command' }\n : null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n onBehalfOfUserId: onBehalfOfUserId ?? 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) {\n payload.context = { ...(systemActorContext ?? {}), ...metadata.context }\n } else if (systemActorContext) {\n payload.context = systemActorContext\n }\n }\n\n if (runAs) {\n // Stamp the audit source so `deriveActionLogSource` projects `sourceKey='agent'`.\n // Merge into any caller-provided context rather than replacing it.\n const baseContext = asRecord(payload.context) ?? {}\n payload.context = { ...baseContext, source: runAs.source }\n }\n\n if (metadata.replayable === false) {\n delete payload.commandPayload\n } else {\n const redoInput = metadata.redoInput !== undefined ? metadata.redoInput : options.input\n const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, redoInput)\n payload.commandPayload = redoEnvelope\n }\n\n return options.ctx.transactionalEm\n ? await service.log(payload as ActionLogCreateInput, options.ctx.transactionalEm)\n : 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
|
+
"names": ["result", "mergedMeta", "logEntry"]
|
|
7
7
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "../logger/index.js";
|
|
2
|
+
import { getTelemetryRuntime } from "../telemetry/runtime.js";
|
|
3
|
+
import {
|
|
4
|
+
beginTransactionLifetime,
|
|
5
|
+
completeTransactionLifetime
|
|
6
|
+
} from "./transaction-lifetime.js";
|
|
2
7
|
const logger = createLogger("shared").child({ component: "commands" });
|
|
3
8
|
async function flushPendingChangesGuard(em, label) {
|
|
4
9
|
let pendingCount = -1;
|
|
@@ -37,6 +42,7 @@ async function withAtomicFlush(em, phases, options) {
|
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
44
|
await em.begin(options.isolationLevel ? { isolationLevel: options.isolationLevel } : void 0);
|
|
45
|
+
const transactionLifetime = beginTransactionLifetime(em);
|
|
40
46
|
try {
|
|
41
47
|
await runPhasesAndFlush();
|
|
42
48
|
await em.commit();
|
|
@@ -45,8 +51,18 @@ async function withAtomicFlush(em, phases, options) {
|
|
|
45
51
|
await em.rollback();
|
|
46
52
|
} catch {
|
|
47
53
|
}
|
|
54
|
+
try {
|
|
55
|
+
await completeTransactionLifetime(em, transactionLifetime, "rolled_back");
|
|
56
|
+
} catch (completionErr) {
|
|
57
|
+
logger.error("withAtomicFlush: rollback completion failed", { label: options.label ?? null, err: completionErr });
|
|
58
|
+
getTelemetryRuntime()?.reportError(completionErr, {
|
|
59
|
+
module: "shared",
|
|
60
|
+
code: "shared.transaction_rollback_completion_failed"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
48
63
|
throw err;
|
|
49
64
|
}
|
|
65
|
+
await completeTransactionLifetime(em, transactionLifetime, "committed");
|
|
50
66
|
}
|
|
51
67
|
export {
|
|
52
68
|
withAtomicFlush
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/commands/flush.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { IsolationLevel } from '@mikro-orm/core'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\n/**\n * Options controlling how {@link withAtomicFlush} executes its phases.\n */\nexport type AtomicFlushOptions = {\n /**\n * When true, the whole sequence runs inside a database transaction for\n * all-or-nothing semantics. Default: false (a single `em.flush()` commits\n * all phases at the end \u2014 no transaction).\n */\n transaction?: boolean\n /**\n * Optional transaction isolation level, forwarded to `em.begin()`. Only\n * honoured when this call opens a new top-level transaction (i.e.\n * `transaction: true` and the EntityManager is not already inside a\n * transaction). Ignored when joining an ambient transaction.\n */\n isolationLevel?: IsolationLevel\n /**\n * Optional label for diagnostics. Surfaced in the commit-boundary guard's\n * dev warning when a pending change set had to be flushed defensively, so the\n * offending command is identifiable.\n */\n label?: string\n}\n\ntype UnitOfWorkProbe = {\n computeChangeSets?: () => void\n getChangeSets?: () => ReadonlyArray<unknown>\n}\n\ntype FlushGuardEntityManager = {\n flush: () => Promise<void>\n getUnitOfWork?: () => UnitOfWorkProbe | undefined\n}\n\n/**\n * Commit-boundary safety net.\n *\n * After every phase has run and flushed, this asserts the UnitOfWork holds NO\n * pending change sets before the transaction commits. If it still does \u2014 a phase\n * mutated a managed entity AFTER its own per-phase flush boundary (the exact\n * shape that silently drops a scalar UPDATE under MikroORM v7) \u2014 the guard\n * flushes those changes defensively so the write can never be lost, and warns in\n * non-production so the latent ordering bug gets fixed at the source.\n *\n * Detection is best-effort and fail-safe: it only issues the extra flush when it\n * can PROVE the UnitOfWork is dirty (`computeChangeSets()` \u2192 `getChangeSets()`\n * non-empty). On EntityManagers that don't expose a UnitOfWork (partial/mock EMs\n * in unit tests) it does nothing \u2014 the per-phase flushes already ran \u2014 so it\n * never double-flushes a clean unit of work and never changes flush counts for\n * callers that were already correct.\n */\nasync function flushPendingChangesGuard(\n em: FlushGuardEntityManager,\n label?: string,\n): Promise<void> {\n let pendingCount = -1\n try {\n const uow = typeof em.getUnitOfWork === 'function' ? em.getUnitOfWork() : undefined\n if (uow && typeof uow.computeChangeSets === 'function' && typeof uow.getChangeSets === 'function') {\n uow.computeChangeSets()\n pendingCount = uow.getChangeSets().length\n }\n } catch {\n // Probing the UnitOfWork must never break a command; fall back to \"unknown\".\n pendingCount = -1\n }\n\n if (pendingCount > 0) {\n await em.flush()\n if (process.env.NODE_ENV !== 'production') {\n logger.warn('withAtomicFlush: pending change-sets remained at the commit boundary and were flushed defensively \u2014 a phase mutated a managed entity after its flush boundary; split the mutation and any dependent read/sync into separate phases', { label: label ?? null, pendingCount })\n }\n }\n}\n\n/**\n * Wraps multiple mutation phases in a single atomic flush.\n *\n * Prevents partial commits when a command mutates entities across\n * multiple phases (e.g., scalar mutations + relation syncs).\n * Each phase function runs sequentially; a single `em.flush()`\n * commits all changes at the end on the same `EntityManager` the\n * phases mutate, so closures over `em` stay valid.\n *\n * When `options.transaction` is true, the whole sequence runs\n * inside a database transaction for all-or-nothing semantics.\n *\n * ## Re-entrancy / composability\n *\n * `withAtomicFlush({ transaction: true })` is safe to nest. If the\n * supplied `EntityManager` is **already inside a transaction**, this\n * call does NOT open a second one (raw `em.begin()` would clobber the\n * active `#transactionContext` and orphan the outer transaction \u2014 unlike\n * `em.transactional()`, MikroORM's `em.begin()` does not check\n * `isInTransaction()`). Instead it joins the ambient transaction: the\n * phases run and flush within it, and the outermost caller owns the final\n * `commit()` / `rollback()`. A phase error therefore rolls back the entire\n * enclosing transaction (all-or-nothing across the whole nest).\n *\n * This mirrors the contract every command relies on: each command forks\n * the request `EntityManager` first, so the common case opens a fresh\n * top-level transaction; nesting only happens when one transactional unit\n * is composed inside another on the same `em`.\n *\n * When `phases` is empty the call is a true no-op \u2014 no flush,\n * no transaction. Callers that need an explicit commit should\n * pass at least one phase.\n *\n * Keep side-effect emissions (`emitCrudSideEffects` etc.) OUTSIDE\n * the `withAtomicFlush` block \u2014 they should only fire after commit.\n */\nexport async function withAtomicFlush(\n em: EntityManager,\n phases: Array<() => void | Promise<void>>,\n options?: AtomicFlushOptions,\n): Promise<void> {\n if (phases.length === 0) return\n\n // SPEC-018: the phases ARE flush boundaries \u2014 flush AFTER EACH phase, not once\n // at the end. A phase's scalar mutations must be persisted before the NEXT\n // phase runs any query (em.find / findOne / nativeUpdate / a sync helper);\n // otherwise the interleaved read resets MikroORM v7's identity-map changeset\n // and the pending scalar UPDATE is silently dropped (the #2453 family). This\n // is the framework-level guarantee that lets commands keep mutations and the\n // reads that depend on them in separate phases without hand-rolled flushes.\n //\n // Atomicity is preserved: when `transaction: true` (or an ambient transaction\n // is joined), each `em.flush()` only emits SQL inside the open transaction \u2014\n // the single commit/rollback below still spans every phase, so a later-phase\n // failure rolls back all earlier phases. Without a transaction the helper\n // keeps its documented \"each phase flushes independently\" behavior.\n //\n // Commit-boundary guarantee: after the last phase flush, `flushPendingChangesGuard`\n // re-checks the UnitOfWork and flushes once more if ANY pending change set remains\n // (a phase mutated state after its boundary). The transaction therefore can never\n // commit with unflushed work \u2014 if a per-phase flush was missed \"for some reason\",\n // the guard catches it inside the same transaction and warns in dev.\n const runPhasesAndFlush = async () => {\n for (const phase of phases) {\n await phase()\n await em.flush()\n }\n await flushPendingChangesGuard(em as unknown as FlushGuardEntityManager, options?.label)\n }\n\n if (!options?.transaction) {\n await runPhasesAndFlush()\n return\n }\n\n // Re-entrancy guard: never open a nested transaction with raw begin/commit.\n // If a transaction is already active on this EntityManager, join it \u2014 the\n // outermost caller owns commit/rollback. A phase error propagates and rolls\n // back the whole enclosing transaction.\n //\n // Guard the probe: real MikroORM EntityManagers always implement\n // `isInTransaction()`, but partial / mock EMs may not. A missing method is\n // treated as \"not in a transaction\", so this call opens its own top-level\n // transaction via the begin/commit path below (which those EMs do support).\n const isInTransaction = (em as { isInTransaction?: () => boolean }).isInTransaction\n if (typeof isInTransaction === 'function' && isInTransaction.call(em)) {\n await runPhasesAndFlush()\n return\n }\n\n await em.begin(options.isolationLevel ? { isolationLevel: options.isolationLevel } : undefined)\n try {\n await runPhasesAndFlush()\n await em.commit()\n } catch (err) {\n try {\n await em.rollback()\n } catch {\n // rollback failure should not mask the original error; intentionally swallowed\n }\n throw err\n }\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { IsolationLevel } from '@mikro-orm/core'\nimport { createLogger } from '../logger'\nimport { getTelemetryRuntime } from '../telemetry/runtime'\nimport {\n beginTransactionLifetime,\n completeTransactionLifetime,\n} from './transaction-lifetime'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\n/**\n * Options controlling how {@link withAtomicFlush} executes its phases.\n */\nexport type AtomicFlushOptions = {\n /**\n * When true, the whole sequence runs inside a database transaction for\n * all-or-nothing semantics. Default: false (a single `em.flush()` commits\n * all phases at the end \u2014 no transaction).\n */\n transaction?: boolean\n /**\n * Optional transaction isolation level, forwarded to `em.begin()`. Only\n * honoured when this call opens a new top-level transaction (i.e.\n * `transaction: true` and the EntityManager is not already inside a\n * transaction). Ignored when joining an ambient transaction.\n */\n isolationLevel?: IsolationLevel\n /**\n * Optional label for diagnostics. Surfaced in the commit-boundary guard's\n * dev warning when a pending change set had to be flushed defensively, so the\n * offending command is identifiable.\n */\n label?: string\n}\n\ntype UnitOfWorkProbe = {\n computeChangeSets?: () => void\n getChangeSets?: () => ReadonlyArray<unknown>\n}\n\ntype FlushGuardEntityManager = {\n flush: () => Promise<void>\n getUnitOfWork?: () => UnitOfWorkProbe | undefined\n}\n\n/**\n * Commit-boundary safety net.\n *\n * After every phase has run and flushed, this asserts the UnitOfWork holds NO\n * pending change sets before the transaction commits. If it still does \u2014 a phase\n * mutated a managed entity AFTER its own per-phase flush boundary (the exact\n * shape that silently drops a scalar UPDATE under MikroORM v7) \u2014 the guard\n * flushes those changes defensively so the write can never be lost, and warns in\n * non-production so the latent ordering bug gets fixed at the source.\n *\n * Detection is best-effort and fail-safe: it only issues the extra flush when it\n * can PROVE the UnitOfWork is dirty (`computeChangeSets()` \u2192 `getChangeSets()`\n * non-empty). On EntityManagers that don't expose a UnitOfWork (partial/mock EMs\n * in unit tests) it does nothing \u2014 the per-phase flushes already ran \u2014 so it\n * never double-flushes a clean unit of work and never changes flush counts for\n * callers that were already correct.\n */\nasync function flushPendingChangesGuard(\n em: FlushGuardEntityManager,\n label?: string,\n): Promise<void> {\n let pendingCount = -1\n try {\n const uow = typeof em.getUnitOfWork === 'function' ? em.getUnitOfWork() : undefined\n if (uow && typeof uow.computeChangeSets === 'function' && typeof uow.getChangeSets === 'function') {\n uow.computeChangeSets()\n pendingCount = uow.getChangeSets().length\n }\n } catch {\n // Probing the UnitOfWork must never break a command; fall back to \"unknown\".\n pendingCount = -1\n }\n\n if (pendingCount > 0) {\n await em.flush()\n if (process.env.NODE_ENV !== 'production') {\n logger.warn('withAtomicFlush: pending change-sets remained at the commit boundary and were flushed defensively \u2014 a phase mutated a managed entity after its flush boundary; split the mutation and any dependent read/sync into separate phases', { label: label ?? null, pendingCount })\n }\n }\n}\n\n/**\n * Wraps multiple mutation phases in a single atomic flush.\n *\n * Prevents partial commits when a command mutates entities across\n * multiple phases (e.g., scalar mutations + relation syncs).\n * Each phase function runs sequentially; a single `em.flush()`\n * commits all changes at the end on the same `EntityManager` the\n * phases mutate, so closures over `em` stay valid.\n *\n * When `options.transaction` is true, the whole sequence runs\n * inside a database transaction for all-or-nothing semantics.\n *\n * ## Re-entrancy / composability\n *\n * `withAtomicFlush({ transaction: true })` is safe to nest. If the\n * supplied `EntityManager` is **already inside a transaction**, this\n * call does NOT open a second one (raw `em.begin()` would clobber the\n * active `#transactionContext` and orphan the outer transaction \u2014 unlike\n * `em.transactional()`, MikroORM's `em.begin()` does not check\n * `isInTransaction()`). Instead it joins the ambient transaction: the\n * phases run and flush within it, and the outermost caller owns the final\n * `commit()` / `rollback()`. A phase error therefore rolls back the entire\n * enclosing transaction (all-or-nothing across the whole nest).\n *\n * This mirrors the contract every command relies on: each command forks\n * the request `EntityManager` first, so the common case opens a fresh\n * top-level transaction; nesting only happens when one transactional unit\n * is composed inside another on the same `em`.\n *\n * When `phases` is empty the call is a true no-op \u2014 no flush,\n * no transaction. Callers that need an explicit commit should\n * pass at least one phase.\n *\n * Keep side-effect emissions (`emitCrudSideEffects` etc.) OUTSIDE\n * the `withAtomicFlush` block \u2014 they should only fire after commit.\n */\nexport async function withAtomicFlush(\n em: EntityManager,\n phases: Array<() => void | Promise<void>>,\n options?: AtomicFlushOptions,\n): Promise<void> {\n if (phases.length === 0) return\n\n // SPEC-018: the phases ARE flush boundaries \u2014 flush AFTER EACH phase, not once\n // at the end. A phase's scalar mutations must be persisted before the NEXT\n // phase runs any query (em.find / findOne / nativeUpdate / a sync helper);\n // otherwise the interleaved read resets MikroORM v7's identity-map changeset\n // and the pending scalar UPDATE is silently dropped (the #2453 family). This\n // is the framework-level guarantee that lets commands keep mutations and the\n // reads that depend on them in separate phases without hand-rolled flushes.\n //\n // Atomicity is preserved: when `transaction: true` (or an ambient transaction\n // is joined), each `em.flush()` only emits SQL inside the open transaction \u2014\n // the single commit/rollback below still spans every phase, so a later-phase\n // failure rolls back all earlier phases. Without a transaction the helper\n // keeps its documented \"each phase flushes independently\" behavior.\n //\n // Commit-boundary guarantee: after the last phase flush, `flushPendingChangesGuard`\n // re-checks the UnitOfWork and flushes once more if ANY pending change set remains\n // (a phase mutated state after its boundary). The transaction therefore can never\n // commit with unflushed work \u2014 if a per-phase flush was missed \"for some reason\",\n // the guard catches it inside the same transaction and warns in dev.\n const runPhasesAndFlush = async () => {\n for (const phase of phases) {\n await phase()\n await em.flush()\n }\n await flushPendingChangesGuard(em as unknown as FlushGuardEntityManager, options?.label)\n }\n\n if (!options?.transaction) {\n await runPhasesAndFlush()\n return\n }\n\n // Re-entrancy guard: never open a nested transaction with raw begin/commit.\n // If a transaction is already active on this EntityManager, join it \u2014 the\n // outermost caller owns commit/rollback. A phase error propagates and rolls\n // back the whole enclosing transaction.\n //\n // Guard the probe: real MikroORM EntityManagers always implement\n // `isInTransaction()`, but partial / mock EMs may not. A missing method is\n // treated as \"not in a transaction\", so this call opens its own top-level\n // transaction via the begin/commit path below (which those EMs do support).\n const isInTransaction = (em as { isInTransaction?: () => boolean }).isInTransaction\n if (typeof isInTransaction === 'function' && isInTransaction.call(em)) {\n await runPhasesAndFlush()\n return\n }\n\n await em.begin(options.isolationLevel ? { isolationLevel: options.isolationLevel } : undefined)\n const transactionLifetime = beginTransactionLifetime(em)\n try {\n await runPhasesAndFlush()\n await em.commit()\n } catch (err) {\n try {\n await em.rollback()\n } catch {\n // rollback failure should not mask the original error; intentionally swallowed\n }\n try {\n await completeTransactionLifetime(em, transactionLifetime, 'rolled_back')\n } catch (completionErr) {\n logger.error('withAtomicFlush: rollback completion failed', { label: options.label ?? null, err: completionErr })\n getTelemetryRuntime()?.reportError(completionErr, {\n module: 'shared',\n code: 'shared.transaction_rollback_completion_failed',\n })\n }\n throw err\n }\n await completeTransactionLifetime(em, transactionLifetime, 'committed')\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS,aAAa,QAAQ,EAAE,MAAM,EAAE,WAAW,WAAW,CAAC;AAsDrE,eAAe,yBACb,IACA,OACe;AACf,MAAI,eAAe;AACnB,MAAI;AACF,UAAM,MAAM,OAAO,GAAG,kBAAkB,aAAa,GAAG,cAAc,IAAI;AAC1E,QAAI,OAAO,OAAO,IAAI,sBAAsB,cAAc,OAAO,IAAI,kBAAkB,YAAY;AACjG,UAAI,kBAAkB;AACtB,qBAAe,IAAI,cAAc,EAAE;AAAA,IACrC;AAAA,EACF,QAAQ;AAEN,mBAAe;AAAA,EACjB;AAEA,MAAI,eAAe,GAAG;AACpB,UAAM,GAAG,MAAM;AACf,QAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,aAAO,KAAK,2OAAsO,EAAE,OAAO,SAAS,MAAM,aAAa,CAAC;AAAA,IAC1R;AAAA,EACF;AACF;AAsCA,eAAsB,gBACpB,IACA,QACA,SACe;AACf,MAAI,OAAO,WAAW,EAAG;AAqBzB,QAAM,oBAAoB,YAAY;AACpC,eAAW,SAAS,QAAQ;AAC1B,YAAM,MAAM;AACZ,YAAM,GAAG,MAAM;AAAA,IACjB;AACA,UAAM,yBAAyB,IAA0C,SAAS,KAAK;AAAA,EACzF;AAEA,MAAI,CAAC,SAAS,aAAa;AACzB,UAAM,kBAAkB;AACxB;AAAA,EACF;AAWA,QAAM,kBAAmB,GAA2C;AACpE,MAAI,OAAO,oBAAoB,cAAc,gBAAgB,KAAK,EAAE,GAAG;AACrE,UAAM,kBAAkB;AACxB;AAAA,EACF;AAEA,QAAM,GAAG,MAAM,QAAQ,iBAAiB,EAAE,gBAAgB,QAAQ,eAAe,IAAI,MAAS;AAC9F,QAAM,sBAAsB,yBAAyB,EAAE;AACvD,MAAI;AACF,UAAM,kBAAkB;AACxB,UAAM,GAAG,OAAO;AAAA,EAClB,SAAS,KAAK;AACZ,QAAI;AACF,YAAM,GAAG,SAAS;AAAA,IACpB,QAAQ;AAAA,IAER;AACA,QAAI;AACF,YAAM,4BAA4B,IAAI,qBAAqB,aAAa;AAAA,IAC1E,SAAS,eAAe;AACtB,aAAO,MAAM,+CAA+C,EAAE,OAAO,QAAQ,SAAS,MAAM,KAAK,cAAc,CAAC;AAChH,0BAAoB,GAAG,YAAY,eAAe;AAAA,QAChD,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR;AACA,QAAM,4BAA4B,IAAI,qBAAqB,WAAW;AACxE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,11 @@ import { CommandBus } from "./command-bus.js";
|
|
|
4
4
|
export * from "./customFieldSnapshots.js";
|
|
5
5
|
export * from "./undo.js";
|
|
6
6
|
export * from "./redo.js";
|
|
7
|
+
import {
|
|
8
|
+
getTransactionLifetime,
|
|
9
|
+
onTransactionLifetimeComplete,
|
|
10
|
+
ownsTransactionLifetime
|
|
11
|
+
} from "./transaction-lifetime.js";
|
|
7
12
|
import {
|
|
8
13
|
CommandInterceptorError,
|
|
9
14
|
isCommandInterceptorError,
|
|
@@ -16,7 +21,10 @@ export {
|
|
|
16
21
|
CommandBus,
|
|
17
22
|
CommandInterceptorError,
|
|
18
23
|
getCommandInterceptorHttpRejection,
|
|
24
|
+
getTransactionLifetime,
|
|
19
25
|
isCommandInterceptorError,
|
|
26
|
+
onTransactionLifetimeComplete,
|
|
27
|
+
ownsTransactionLifetime,
|
|
20
28
|
runCrudCommandWrite
|
|
21
29
|
};
|
|
22
30
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/commands/index.ts"],
|
|
4
|
-
"sourcesContent": ["export * from './types'\nexport * from './registry'\nexport { CommandBus } from './command-bus'\nexport * from './customFieldSnapshots'\nexport * from './undo'\nexport * from './redo'\nexport {\n CommandInterceptorError,\n isCommandInterceptorError,\n getCommandInterceptorHttpRejection,\n type CommandInterceptorErrorOptions,\n type CommandInterceptorHttpRejection,\n} from './errors'\nexport {\n runCrudCommandWrite,\n type RunCrudCommandWriteOptions,\n type RunCrudCommandWriteResult,\n type CrudCommandWritePhase,\n type CrudCommandWriteScope,\n type CrudCommandWriteSideEffectTarget,\n} from './runCrudCommandWrite'\n"],
|
|
5
|
-
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;AAC3B,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAMK;",
|
|
4
|
+
"sourcesContent": ["export * from './types'\nexport * from './registry'\nexport { CommandBus } from './command-bus'\nexport * from './customFieldSnapshots'\nexport * from './undo'\nexport * from './redo'\nexport {\n getTransactionLifetime,\n onTransactionLifetimeComplete,\n ownsTransactionLifetime,\n type TransactionLifetime,\n type TransactionOutcome,\n} from './transaction-lifetime'\nexport {\n CommandInterceptorError,\n isCommandInterceptorError,\n getCommandInterceptorHttpRejection,\n type CommandInterceptorErrorOptions,\n type CommandInterceptorHttpRejection,\n} from './errors'\nexport {\n runCrudCommandWrite,\n type RunCrudCommandWriteOptions,\n type RunCrudCommandWriteResult,\n type CrudCommandWritePhase,\n type CrudCommandWriteScope,\n type CrudCommandWriteSideEffectTarget,\n} from './runCrudCommandWrite'\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;AAC3B,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAMK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { CrudHttpError, conflict, isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
|
|
2
|
-
import { extractUndoPayload } from "./undo.js";
|
|
2
|
+
import { extractUndoPayload, reviveSnapshotDates } from "./undo.js";
|
|
3
3
|
import { emitCrudSideEffects } from "./helpers.js";
|
|
4
4
|
import { withAtomicFlush } from "./flush.js";
|
|
5
5
|
const DEFAULT_SNAPSHOT_DATE_FIELDS = ["createdAt", "updatedAt", "deletedAt"];
|
|
6
6
|
function reviveSnapshotSeed(snapshot, dateFields = DEFAULT_SNAPSHOT_DATE_FIELDS) {
|
|
7
|
-
|
|
8
|
-
for (const field of dateFields) {
|
|
9
|
-
const value = seed[field];
|
|
10
|
-
if (typeof value === "string") seed[field] = new Date(value);
|
|
11
|
-
}
|
|
12
|
-
return seed;
|
|
7
|
+
return reviveSnapshotDates(snapshot, dateFields);
|
|
13
8
|
}
|
|
14
9
|
function serializeRowSnapshot(entity, fields, dateFields = DEFAULT_SNAPSHOT_DATE_FIELDS) {
|
|
15
10
|
const snapshot = {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/commands/redo.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext, CommandUndoLogEntry } from './types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { extractUndoPayload, type UndoPayload } from './undo'\nimport { emitCrudSideEffects } from './helpers'\nimport { withAtomicFlush } from './flush'\n\ntype EntityClass<T> = abstract new (...args: never[]) => T\n\ntype ScopedSoftDeletable = {\n id: string\n organizationId?: string | null\n tenantId?: string | null\n deletedAt?: Date | null\n isActive?: boolean\n}\n\n/** Snapshot keys revived from ISO strings to `Date` when no explicit `dateFields` is given. */\nconst DEFAULT_SNAPSHOT_DATE_FIELDS = ['createdAt', 'updatedAt', 'deletedAt'] as const\n\n/**\n * Turn an after-snapshot into a create seed by shallow-cloning it and reviving the\n * declared date fields from ISO strings back to `Date`. Single-row snapshots are a\n * faithful serialized row whose keys already equal entity property names, so the\n * snapshot doubles as the seed once dates are revived \u2014 no per-command mapping needed.\n */\nexport function reviveSnapshotSeed(\n snapshot: Record<string, unknown>,\n dateFields: readonly string[] = DEFAULT_SNAPSHOT_DATE_FIELDS,\n): Record<string, unknown> {\n const seed: Record<string, unknown> = { ...snapshot }\n for (const field of dateFields) {\n const value = seed[field]\n if (typeof value === 'string') seed[field] = new Date(value)\n }\n return seed\n}\n\n/**\n * Serialize a persisted row into a plain after-snapshot object: pick `fields` from\n * the entity and convert the declared `dateFields` from `Date` to ISO strings. Use\n * for single-row snapshot loaders that are a clean 1:1 column copy; loaders that\n * shape nested/related data keep their bespoke mapping.\n */\nexport function serializeRowSnapshot<TEntity extends Record<string, unknown>>(\n entity: TEntity,\n fields: readonly string[],\n dateFields: readonly string[] = DEFAULT_SNAPSHOT_DATE_FIELDS,\n): Record<string, unknown> {\n const snapshot: Record<string, unknown> = {}\n const dateFieldSet = new Set(dateFields)\n for (const field of fields) {\n const value = entity[field]\n if (dateFieldSet.has(field)) {\n snapshot[field] = value instanceof Date ? value.toISOString() : (value ?? null)\n } else {\n snapshot[field] = value ?? null\n }\n }\n return snapshot\n}\n\n/**\n * Resolve the after-snapshot a create command persisted for its action log, so a\n * `redo` handler can re-materialize the original record. Reads the `undo.after`\n * snapshot via {@link extractUndoPayload} and falls back to `snapshotAfter`.\n */\nexport function resolveRedoSnapshot<T>(logEntry: CommandUndoLogEntry | null | undefined): T | null {\n if (!logEntry) return null\n const undo = extractUndoPayload<UndoPayload<T>>(logEntry)\n if (undo && undo.after != null) return undo.after as T\n if (logEntry.snapshotAfter != null) return logEntry.snapshotAfter as T\n return null\n}\n\n/**\n * Re-materialize a single row that a create command produced, reusing its original\n * id. If the row still exists (it was soft-deleted by undo), clears `deletedAt` and\n * restores `isActive` from the seed. If it was hard-deleted, re-creates it from the\n * seed (which MUST include the original `id`). This keeps redo idempotent on id so\n * undo/redo snapshots and cross-references stay stable (issue #2506, invariant I6).\n */\nexport async function restoreCreatedRow<TEntity extends ScopedSoftDeletable>(\n em: EntityManager,\n entityClass: EntityClass<TEntity>,\n id: string,\n seedFromSnapshot: () => Record<string, unknown>,\n findRow?: (em: EntityManager, id: string) => Promise<TEntity | null>,\n): Promise<TEntity> {\n const existing = findRow\n ? await findRow(em, id)\n : ((await em.findOne(entityClass as never, { id } as never)) as TEntity | null)\n if (existing) {\n existing.deletedAt = null\n const seed = seedFromSnapshot()\n if (typeof seed.isActive === 'boolean') existing.isActive = seed.isActive\n return existing\n }\n const record = em.create(entityClass as never, { ...seedFromSnapshot(), deletedAt: null } as never) as TEntity\n em.persist(record as never)\n return record\n}\n\nexport type CreateRedoConfig<TEntity extends ScopedSoftDeletable, TSnapshot, TResult> = {\n entityClass: EntityClass<TEntity>\n /**\n * Pulls the original primary id out of the after-snapshot. Defaults to\n * `(snapshot) => snapshot.id`, which fits single-row snapshots whose top-level\n * `id` is the row's primary key. Override only when the id lives elsewhere.\n */\n getSnapshotId?: (snapshot: TSnapshot) => string | null | undefined\n /**\n * Maps the after-snapshot back to a create seed; MUST include the original id.\n * Defaults to {@link reviveSnapshotSeed} \u2014 the snapshot itself with `dateFields`\n * revived to `Date`. Override only when the snapshot keys diverge from entity\n * columns (e.g. nested shapes or derived columns).\n */\n seedFromSnapshot?: (snapshot: TSnapshot) => Record<string, unknown>\n /**\n * Snapshot keys to revive from ISO string to `Date` for the default seed.\n * Defaults to `['createdAt', 'updatedAt', 'deletedAt']`; list additional date\n * columns (e.g. `effectiveAt`, `returnedAt`) when the entity has them.\n */\n dateFields?: readonly string[]\n /** Builds the command result (mirrors `execute`'s return), e.g. `(e) => ({ currencyId: e.id })`. */\n buildResult: (entity: TEntity, snapshot: TSnapshot) => TResult\n events?: CrudEventsConfig<any>\n indexer?: CrudIndexerConfig<any>\n /**\n * Override how the existing row is looked up before restore. Defaults to\n * `em.findOne(entityClass, { id })`. Pass a decryption-aware finder\n * (`findOneWithDecryption`) for encrypted entities so the revive-in-place path\n * sees the same row the rest of the module does.\n */\n findRow?: (args: { em: EntityManager; ctx: CommandRuntimeContext; id: string; snapshot: TSnapshot }) => Promise<TEntity | null>\n /**\n * Runs after the em fork, before the row is restored. Use to validate\n * referenced relations (throw `CrudHttpError` to fail the redo) or resolve\n * relation entities. Anything it returns is shallow-merged into the create\n * seed (e.g. `{ entity: resolvedEntity }`), letting the seed reference a live\n * relation instead of a raw id.\n */\n beforeRestore?: (args: {\n em: EntityManager\n ctx: CommandRuntimeContext\n snapshot: TSnapshot\n }) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void\n /** Optional extra side effects after the row is restored (e.g. query-index upserts, custom-field restore). */\n afterRestore?: (args: {\n em: EntityManager\n ctx: CommandRuntimeContext\n entity: TEntity\n snapshot: TSnapshot\n logEntry: CommandUndoLogEntry\n }) => Promise<void> | void\n /**\n * Wrap the restore (create/revive + flush) in a single transaction via\n * {@link withAtomicFlush}. Use when the create participated in a multi-phase\n * atomic flush in `execute` and partial commits must be impossible.\n */\n transaction?: boolean\n}\n\n/**\n * Build a create command's `redo` handler that restores the original row in place\n * (reusing its id) instead of replaying `execute` and minting a new id. Wires the\n * `created` side effects (events + query index) exactly like `execute`. Use this for\n * single-row create commands; multi-entity creates implement `redo` by hand.\n */\nexport function makeCreateRedo<\n TEntity extends ScopedSoftDeletable,\n TSnapshot,\n TInput = unknown,\n TResult = unknown,\n>(config: CreateRedoConfig<TEntity, TSnapshot, TResult>) {\n const getSnapshotId = config.getSnapshotId ?? ((snapshot: TSnapshot) => (snapshot as { id?: string | null }).id ?? null)\n const seedFromSnapshot =\n config.seedFromSnapshot ?? ((snapshot: TSnapshot) => reviveSnapshotSeed(snapshot as Record<string, unknown>, config.dateFields))\n return async ({ ctx, logEntry }: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<TResult> => {\n const snapshot = resolveRedoSnapshot<TSnapshot>(logEntry)\n const id = snapshot ? getSnapshotId(snapshot) : (logEntry.resourceId ?? null)\n if (!snapshot || !id) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for create command' })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = config.beforeRestore ? await config.beforeRestore({ em, ctx, snapshot }) : undefined\n const buildSeed = () => (overrides ? { ...seedFromSnapshot(snapshot), ...overrides } : seedFromSnapshot(snapshot))\n const findRow = config.findRow\n ? (forkedEm: EntityManager, rowId: string) => config.findRow!({ em: forkedEm, ctx, id: rowId, snapshot })\n : undefined\n let entity!: TEntity\n const restorePhase = async () => {\n entity = await restoreCreatedRow(em, config.entityClass, id, buildSeed, findRow)\n }\n const afterRestorePhase = async () => {\n if (config.afterRestore) {\n await config.afterRestore({ em, ctx, entity, snapshot, logEntry })\n }\n }\n try {\n if (config.transaction) {\n const phases = config.afterRestore ? [restorePhase, afterRestorePhase] : [restorePhase]\n await withAtomicFlush(em, phases, { transaction: true })\n } else {\n await restorePhase()\n await em.flush()\n await afterRestorePhase()\n }\n } catch (err) {\n if (isUniqueViolation(err)) {\n // [internal] prefix: this shared-lib helper has no `t(...)` context; the\n // redo unique-collision is a rare developer-facing edge (the after-snapshot's\n // unique key was re-taken since undo), surfaced via normal error handling.\n throw conflict('[internal] Cannot redo: a record with the same unique key already exists.')\n }\n throw err\n }\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity,\n identifiers: {\n id,\n organizationId: entity.organizationId ?? null,\n tenantId: entity.tenantId ?? null,\n },\n events: config.events,\n indexer: config.indexer,\n })\n return config.buildResult(entity, snapshot)\n }\n}\n"],
|
|
5
|
-
"mappings": "AAIA,SAAS,eAAe,UAAU,yBAAyB;AAC3D,SAAS,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext, CommandUndoLogEntry } from './types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { extractUndoPayload, reviveSnapshotDates, type UndoPayload } from './undo'\nimport { emitCrudSideEffects } from './helpers'\nimport { withAtomicFlush } from './flush'\n\ntype EntityClass<T> = abstract new (...args: never[]) => T\n\ntype ScopedSoftDeletable = {\n id: string\n organizationId?: string | null\n tenantId?: string | null\n deletedAt?: Date | null\n isActive?: boolean\n}\n\n/** Snapshot keys revived from ISO strings to `Date` when no explicit `dateFields` is given. */\nconst DEFAULT_SNAPSHOT_DATE_FIELDS = ['createdAt', 'updatedAt', 'deletedAt'] as const\n\n/**\n * Turn an after-snapshot into a create seed by shallow-cloning it and reviving the\n * declared date fields from ISO strings back to `Date` (via `reviveSnapshotDates`,\n * which throws on an unparsable value). Single-row snapshots are a\n * faithful serialized row whose keys already equal entity property names, so the\n * snapshot doubles as the seed once dates are revived \u2014 no per-command mapping needed.\n */\nexport function reviveSnapshotSeed(\n snapshot: Record<string, unknown>,\n dateFields: readonly string[] = DEFAULT_SNAPSHOT_DATE_FIELDS,\n): Record<string, unknown> {\n return reviveSnapshotDates(snapshot, dateFields)\n}\n\n/**\n * Serialize a persisted row into a plain after-snapshot object: pick `fields` from\n * the entity and convert the declared `dateFields` from `Date` to ISO strings. Use\n * for single-row snapshot loaders that are a clean 1:1 column copy; loaders that\n * shape nested/related data keep their bespoke mapping.\n */\nexport function serializeRowSnapshot<TEntity extends Record<string, unknown>>(\n entity: TEntity,\n fields: readonly string[],\n dateFields: readonly string[] = DEFAULT_SNAPSHOT_DATE_FIELDS,\n): Record<string, unknown> {\n const snapshot: Record<string, unknown> = {}\n const dateFieldSet = new Set(dateFields)\n for (const field of fields) {\n const value = entity[field]\n if (dateFieldSet.has(field)) {\n snapshot[field] = value instanceof Date ? value.toISOString() : (value ?? null)\n } else {\n snapshot[field] = value ?? null\n }\n }\n return snapshot\n}\n\n/**\n * Resolve the after-snapshot a create command persisted for its action log, so a\n * `redo` handler can re-materialize the original record. Reads the `undo.after`\n * snapshot via {@link extractUndoPayload} and falls back to `snapshotAfter`.\n */\nexport function resolveRedoSnapshot<T>(logEntry: CommandUndoLogEntry | null | undefined): T | null {\n if (!logEntry) return null\n const undo = extractUndoPayload<UndoPayload<T>>(logEntry)\n if (undo && undo.after != null) return undo.after as T\n if (logEntry.snapshotAfter != null) return logEntry.snapshotAfter as T\n return null\n}\n\n/**\n * Re-materialize a single row that a create command produced, reusing its original\n * id. If the row still exists (it was soft-deleted by undo), clears `deletedAt` and\n * restores `isActive` from the seed. If it was hard-deleted, re-creates it from the\n * seed (which MUST include the original `id`). This keeps redo idempotent on id so\n * undo/redo snapshots and cross-references stay stable (issue #2506, invariant I6).\n */\nexport async function restoreCreatedRow<TEntity extends ScopedSoftDeletable>(\n em: EntityManager,\n entityClass: EntityClass<TEntity>,\n id: string,\n seedFromSnapshot: () => Record<string, unknown>,\n findRow?: (em: EntityManager, id: string) => Promise<TEntity | null>,\n): Promise<TEntity> {\n const existing = findRow\n ? await findRow(em, id)\n : ((await em.findOne(entityClass as never, { id } as never)) as TEntity | null)\n if (existing) {\n existing.deletedAt = null\n const seed = seedFromSnapshot()\n if (typeof seed.isActive === 'boolean') existing.isActive = seed.isActive\n return existing\n }\n const record = em.create(entityClass as never, { ...seedFromSnapshot(), deletedAt: null } as never) as TEntity\n em.persist(record as never)\n return record\n}\n\nexport type CreateRedoConfig<TEntity extends ScopedSoftDeletable, TSnapshot, TResult> = {\n entityClass: EntityClass<TEntity>\n /**\n * Pulls the original primary id out of the after-snapshot. Defaults to\n * `(snapshot) => snapshot.id`, which fits single-row snapshots whose top-level\n * `id` is the row's primary key. Override only when the id lives elsewhere.\n */\n getSnapshotId?: (snapshot: TSnapshot) => string | null | undefined\n /**\n * Maps the after-snapshot back to a create seed; MUST include the original id.\n * Defaults to {@link reviveSnapshotSeed} \u2014 the snapshot itself with `dateFields`\n * revived to `Date`. Override only when the snapshot keys diverge from entity\n * columns (e.g. nested shapes or derived columns).\n */\n seedFromSnapshot?: (snapshot: TSnapshot) => Record<string, unknown>\n /**\n * Snapshot keys to revive from ISO string to `Date` for the default seed.\n * Defaults to `['createdAt', 'updatedAt', 'deletedAt']`; list additional date\n * columns (e.g. `effectiveAt`, `returnedAt`) when the entity has them.\n */\n dateFields?: readonly string[]\n /** Builds the command result (mirrors `execute`'s return), e.g. `(e) => ({ currencyId: e.id })`. */\n buildResult: (entity: TEntity, snapshot: TSnapshot) => TResult\n events?: CrudEventsConfig<any>\n indexer?: CrudIndexerConfig<any>\n /**\n * Override how the existing row is looked up before restore. Defaults to\n * `em.findOne(entityClass, { id })`. Pass a decryption-aware finder\n * (`findOneWithDecryption`) for encrypted entities so the revive-in-place path\n * sees the same row the rest of the module does.\n */\n findRow?: (args: { em: EntityManager; ctx: CommandRuntimeContext; id: string; snapshot: TSnapshot }) => Promise<TEntity | null>\n /**\n * Runs after the em fork, before the row is restored. Use to validate\n * referenced relations (throw `CrudHttpError` to fail the redo) or resolve\n * relation entities. Anything it returns is shallow-merged into the create\n * seed (e.g. `{ entity: resolvedEntity }`), letting the seed reference a live\n * relation instead of a raw id.\n */\n beforeRestore?: (args: {\n em: EntityManager\n ctx: CommandRuntimeContext\n snapshot: TSnapshot\n }) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void\n /** Optional extra side effects after the row is restored (e.g. query-index upserts, custom-field restore). */\n afterRestore?: (args: {\n em: EntityManager\n ctx: CommandRuntimeContext\n entity: TEntity\n snapshot: TSnapshot\n logEntry: CommandUndoLogEntry\n }) => Promise<void> | void\n /**\n * Wrap the restore (create/revive + flush) in a single transaction via\n * {@link withAtomicFlush}. Use when the create participated in a multi-phase\n * atomic flush in `execute` and partial commits must be impossible.\n */\n transaction?: boolean\n}\n\n/**\n * Build a create command's `redo` handler that restores the original row in place\n * (reusing its id) instead of replaying `execute` and minting a new id. Wires the\n * `created` side effects (events + query index) exactly like `execute`. Use this for\n * single-row create commands; multi-entity creates implement `redo` by hand.\n */\nexport function makeCreateRedo<\n TEntity extends ScopedSoftDeletable,\n TSnapshot,\n TInput = unknown,\n TResult = unknown,\n>(config: CreateRedoConfig<TEntity, TSnapshot, TResult>) {\n const getSnapshotId = config.getSnapshotId ?? ((snapshot: TSnapshot) => (snapshot as { id?: string | null }).id ?? null)\n const seedFromSnapshot =\n config.seedFromSnapshot ?? ((snapshot: TSnapshot) => reviveSnapshotSeed(snapshot as Record<string, unknown>, config.dateFields))\n return async ({ ctx, logEntry }: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<TResult> => {\n const snapshot = resolveRedoSnapshot<TSnapshot>(logEntry)\n const id = snapshot ? getSnapshotId(snapshot) : (logEntry.resourceId ?? null)\n if (!snapshot || !id) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for create command' })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = config.beforeRestore ? await config.beforeRestore({ em, ctx, snapshot }) : undefined\n const buildSeed = () => (overrides ? { ...seedFromSnapshot(snapshot), ...overrides } : seedFromSnapshot(snapshot))\n const findRow = config.findRow\n ? (forkedEm: EntityManager, rowId: string) => config.findRow!({ em: forkedEm, ctx, id: rowId, snapshot })\n : undefined\n let entity!: TEntity\n const restorePhase = async () => {\n entity = await restoreCreatedRow(em, config.entityClass, id, buildSeed, findRow)\n }\n const afterRestorePhase = async () => {\n if (config.afterRestore) {\n await config.afterRestore({ em, ctx, entity, snapshot, logEntry })\n }\n }\n try {\n if (config.transaction) {\n const phases = config.afterRestore ? [restorePhase, afterRestorePhase] : [restorePhase]\n await withAtomicFlush(em, phases, { transaction: true })\n } else {\n await restorePhase()\n await em.flush()\n await afterRestorePhase()\n }\n } catch (err) {\n if (isUniqueViolation(err)) {\n // [internal] prefix: this shared-lib helper has no `t(...)` context; the\n // redo unique-collision is a rare developer-facing edge (the after-snapshot's\n // unique key was re-taken since undo), surfaced via normal error handling.\n throw conflict('[internal] Cannot redo: a record with the same unique key already exists.')\n }\n throw err\n }\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity,\n identifiers: {\n id,\n organizationId: entity.organizationId ?? null,\n tenantId: entity.tenantId ?? null,\n },\n events: config.events,\n indexer: config.indexer,\n })\n return config.buildResult(entity, snapshot)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { createLogger } from "../logger/index.js";
|
|
3
|
+
import { getTelemetryRuntime } from "../telemetry/runtime.js";
|
|
4
|
+
const logger = createLogger("shared").child({ component: "transaction-lifetime" });
|
|
5
|
+
const transactionLifetimeBrand = /* @__PURE__ */ Symbol("open-mercato.transaction-lifetime");
|
|
6
|
+
const activeTransactionLifetimes = new AsyncLocalStorage();
|
|
7
|
+
function beginTransactionLifetime(em) {
|
|
8
|
+
const key = em;
|
|
9
|
+
const currentLifetimes = activeTransactionLifetimes.getStore();
|
|
10
|
+
if (currentLifetimes?.has(key)) {
|
|
11
|
+
throw new Error("[internal] Transaction lifetime already active for EntityManager");
|
|
12
|
+
}
|
|
13
|
+
const lifetime = {
|
|
14
|
+
[transactionLifetimeBrand]: true,
|
|
15
|
+
completionCallbacks: /* @__PURE__ */ new Set()
|
|
16
|
+
};
|
|
17
|
+
const scopedLifetimes = new Map(currentLifetimes);
|
|
18
|
+
scopedLifetimes.set(key, lifetime);
|
|
19
|
+
activeTransactionLifetimes.enterWith(scopedLifetimes);
|
|
20
|
+
return lifetime;
|
|
21
|
+
}
|
|
22
|
+
function getTransactionLifetime(em) {
|
|
23
|
+
return activeTransactionLifetimes.getStore()?.get(em) ?? null;
|
|
24
|
+
}
|
|
25
|
+
function ownsTransactionLifetime(em, lifetime) {
|
|
26
|
+
return lifetime != null && activeTransactionLifetimes.getStore()?.get(em) === lifetime;
|
|
27
|
+
}
|
|
28
|
+
function onTransactionLifetimeComplete(em, callback) {
|
|
29
|
+
const lifetime = activeTransactionLifetimes.getStore()?.get(em);
|
|
30
|
+
if (!lifetime) {
|
|
31
|
+
throw new Error("[internal] Transaction-owned state requires an active transaction lifetime");
|
|
32
|
+
}
|
|
33
|
+
lifetime.completionCallbacks.add(callback);
|
|
34
|
+
}
|
|
35
|
+
async function completeTransactionLifetime(em, lifetimeToken, outcome) {
|
|
36
|
+
const key = em;
|
|
37
|
+
const currentLifetimes = activeTransactionLifetimes.getStore();
|
|
38
|
+
const lifetime = currentLifetimes?.get(key);
|
|
39
|
+
if (!currentLifetimes || !lifetime || lifetime !== lifetimeToken) {
|
|
40
|
+
throw new Error("[internal] Transaction lifetime mismatch for EntityManager");
|
|
41
|
+
}
|
|
42
|
+
currentLifetimes.delete(key);
|
|
43
|
+
const failures = [];
|
|
44
|
+
for (const callback of lifetime.completionCallbacks) {
|
|
45
|
+
try {
|
|
46
|
+
await callback(outcome);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
failures.push(err);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const err of failures) {
|
|
52
|
+
logger.error("Transaction completion callback failed", { outcome, failures: failures.length, err });
|
|
53
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
54
|
+
module: "shared",
|
|
55
|
+
code: "shared.transaction_completion_callback_failed",
|
|
56
|
+
attributes: { outcome }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
beginTransactionLifetime,
|
|
62
|
+
completeTransactionLifetime,
|
|
63
|
+
getTransactionLifetime,
|
|
64
|
+
onTransactionLifetimeComplete,
|
|
65
|
+
ownsTransactionLifetime
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=transaction-lifetime.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/commands/transaction-lifetime.ts"],
|
|
4
|
+
"sourcesContent": ["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '../logger'\nimport { getTelemetryRuntime } from '../telemetry/runtime'\n\nconst logger = createLogger('shared').child({ component: 'transaction-lifetime' })\n\nexport type TransactionOutcome = 'committed' | 'rolled_back'\n\nconst transactionLifetimeBrand: unique symbol = Symbol('open-mercato.transaction-lifetime')\n\nexport type TransactionLifetime = {\n readonly [transactionLifetimeBrand]: true\n}\n\ntype ActiveTransactionLifetime = TransactionLifetime & {\n completionCallbacks: Set<(outcome: TransactionOutcome) => void | Promise<void>>\n}\n\nconst activeTransactionLifetimes = new AsyncLocalStorage<Map<object, ActiveTransactionLifetime>>()\n\nexport function beginTransactionLifetime(em: EntityManager): TransactionLifetime {\n const key = em as object\n const currentLifetimes = activeTransactionLifetimes.getStore()\n if (currentLifetimes?.has(key)) {\n throw new Error('[internal] Transaction lifetime already active for EntityManager')\n }\n const lifetime: ActiveTransactionLifetime = {\n [transactionLifetimeBrand]: true,\n completionCallbacks: new Set(),\n }\n const scopedLifetimes = new Map(currentLifetimes)\n scopedLifetimes.set(key, lifetime)\n activeTransactionLifetimes.enterWith(scopedLifetimes)\n return lifetime\n}\n\nexport function getTransactionLifetime(em: EntityManager): TransactionLifetime | null {\n return activeTransactionLifetimes.getStore()?.get(em as object) ?? null\n}\n\nexport function ownsTransactionLifetime(\n em: EntityManager,\n lifetime: TransactionLifetime | null | undefined,\n): lifetime is TransactionLifetime {\n return lifetime != null && activeTransactionLifetimes.getStore()?.get(em as object) === lifetime\n}\n\nexport function onTransactionLifetimeComplete(\n em: EntityManager,\n callback: (outcome: TransactionOutcome) => void | Promise<void>,\n): void {\n const lifetime = activeTransactionLifetimes.getStore()?.get(em as object)\n if (!lifetime) {\n throw new Error('[internal] Transaction-owned state requires an active transaction lifetime')\n }\n lifetime.completionCallbacks.add(callback)\n}\n\nexport async function completeTransactionLifetime(\n em: EntityManager,\n lifetimeToken: TransactionLifetime,\n outcome: TransactionOutcome,\n): Promise<void> {\n const key = em as object\n const currentLifetimes = activeTransactionLifetimes.getStore()\n const lifetime = currentLifetimes?.get(key)\n if (!currentLifetimes || !lifetime || lifetime !== lifetimeToken) {\n throw new Error('[internal] Transaction lifetime mismatch for EntityManager')\n }\n currentLifetimes.delete(key)\n const failures: unknown[] = []\n for (const callback of lifetime.completionCallbacks) {\n try {\n await callback(outcome)\n } catch (err) {\n failures.push(err)\n }\n }\n for (const err of failures) {\n logger.error('Transaction completion callback failed', { outcome, failures: failures.length, err })\n getTelemetryRuntime()?.reportError(err, {\n module: 'shared',\n code: 'shared.transaction_completion_callback_failed',\n attributes: { outcome },\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AAEpC,MAAM,SAAS,aAAa,QAAQ,EAAE,MAAM,EAAE,WAAW,uBAAuB,CAAC;AAIjF,MAAM,2BAA0C,uBAAO,mCAAmC;AAU1F,MAAM,6BAA6B,IAAI,kBAA0D;AAE1F,SAAS,yBAAyB,IAAwC;AAC/E,QAAM,MAAM;AACZ,QAAM,mBAAmB,2BAA2B,SAAS;AAC7D,MAAI,kBAAkB,IAAI,GAAG,GAAG;AAC9B,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AACA,QAAM,WAAsC;AAAA,IAC1C,CAAC,wBAAwB,GAAG;AAAA,IAC5B,qBAAqB,oBAAI,IAAI;AAAA,EAC/B;AACA,QAAM,kBAAkB,IAAI,IAAI,gBAAgB;AAChD,kBAAgB,IAAI,KAAK,QAAQ;AACjC,6BAA2B,UAAU,eAAe;AACpD,SAAO;AACT;AAEO,SAAS,uBAAuB,IAA+C;AACpF,SAAO,2BAA2B,SAAS,GAAG,IAAI,EAAY,KAAK;AACrE;AAEO,SAAS,wBACd,IACA,UACiC;AACjC,SAAO,YAAY,QAAQ,2BAA2B,SAAS,GAAG,IAAI,EAAY,MAAM;AAC1F;AAEO,SAAS,8BACd,IACA,UACM;AACN,QAAM,WAAW,2BAA2B,SAAS,GAAG,IAAI,EAAY;AACxE,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,4EAA4E;AAAA,EAC9F;AACA,WAAS,oBAAoB,IAAI,QAAQ;AAC3C;AAEA,eAAsB,4BACpB,IACA,eACA,SACe;AACf,QAAM,MAAM;AACZ,QAAM,mBAAmB,2BAA2B,SAAS;AAC7D,QAAM,WAAW,kBAAkB,IAAI,GAAG;AAC1C,MAAI,CAAC,oBAAoB,CAAC,YAAY,aAAa,eAAe;AAChE,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AACA,mBAAiB,OAAO,GAAG;AAC3B,QAAM,WAAsB,CAAC;AAC7B,aAAW,YAAY,SAAS,qBAAqB;AACnD,QAAI;AACF,YAAM,SAAS,OAAO;AAAA,IACxB,SAAS,KAAK;AACZ,eAAS,KAAK,GAAG;AAAA,IACnB;AAAA,EACF;AACA,aAAW,OAAO,UAAU;AAC1B,WAAO,MAAM,0CAA0C,EAAE,SAAS,UAAU,SAAS,QAAQ,IAAI,CAAC;AAClG,wBAAoB,GAAG,YAAY,KAAK;AAAA,MACtC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,YAAY,EAAE,QAAQ;AAAA,IACxB,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|