@open-mercato/shared 0.10.1-develop.7576.1.21d5b50435 → 0.10.1-develop.7577.1.477f2486e5

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.
Files changed (125) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +4 -4
  3. package/dist/lib/auth/server.js +1 -1
  4. package/dist/lib/auth/server.js.map +2 -2
  5. package/dist/lib/bootstrap/dynamicLoader.js +46 -6
  6. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  7. package/dist/lib/bootstrap/factory.js +4 -3
  8. package/dist/lib/bootstrap/factory.js.map +2 -2
  9. package/dist/lib/bootstrap/next-subpath-resolve-hook-worker.js +6 -0
  10. package/dist/lib/bootstrap/next-subpath-resolve-hook-worker.js.map +7 -0
  11. package/dist/lib/bootstrap/next-subpath-resolve-hook.js +62 -0
  12. package/dist/lib/bootstrap/next-subpath-resolve-hook.js.map +7 -0
  13. package/dist/lib/catalog-visibility/canonicalizeEffectiveScope.js +33 -0
  14. package/dist/lib/catalog-visibility/canonicalizeEffectiveScope.js.map +7 -0
  15. package/dist/lib/catalog-visibility/hashEffectiveScope.js +9 -0
  16. package/dist/lib/catalog-visibility/hashEffectiveScope.js.map +7 -0
  17. package/dist/lib/catalog-visibility/index.js +4 -0
  18. package/dist/lib/catalog-visibility/index.js.map +2 -2
  19. package/dist/lib/commands/command-bus.js +45 -13
  20. package/dist/lib/commands/command-bus.js.map +2 -2
  21. package/dist/lib/commands/types.js.map +2 -2
  22. package/dist/lib/crud/exporters.js +1 -1
  23. package/dist/lib/crud/exporters.js.map +2 -2
  24. package/dist/lib/crud/ids.js +13 -1
  25. package/dist/lib/crud/ids.js.map +2 -2
  26. package/dist/lib/db/advisoryLock.js +172 -0
  27. package/dist/lib/db/advisoryLock.js.map +7 -0
  28. package/dist/lib/db/pg-errors.js +14 -0
  29. package/dist/lib/db/pg-errors.js.map +2 -2
  30. package/dist/lib/localization/index.js +9 -1
  31. package/dist/lib/localization/index.js.map +2 -2
  32. package/dist/lib/localization/translatable-fields.js +26 -0
  33. package/dist/lib/localization/translatable-fields.js.map +2 -2
  34. package/dist/lib/number.js +6 -0
  35. package/dist/lib/number.js.map +2 -2
  36. package/dist/lib/openapi/generator.js +84 -67
  37. package/dist/lib/openapi/generator.js.map +2 -2
  38. package/dist/lib/query/engine.js +45 -10
  39. package/dist/lib/query/engine.js.map +2 -2
  40. package/dist/lib/query/join-utils.js +1 -1
  41. package/dist/lib/query/join-utils.js.map +2 -2
  42. package/dist/lib/query/overlap.js +58 -0
  43. package/dist/lib/query/overlap.js.map +7 -0
  44. package/dist/lib/query/types.js.map +2 -2
  45. package/dist/lib/url.js +24 -0
  46. package/dist/lib/url.js.map +2 -2
  47. package/dist/lib/version.js +1 -1
  48. package/dist/lib/version.js.map +1 -1
  49. package/dist/lib/webhooks/body.js +7 -3
  50. package/dist/lib/webhooks/body.js.map +2 -2
  51. package/dist/lib/webhooks/index.js +2 -0
  52. package/dist/lib/webhooks/index.js.map +2 -2
  53. package/dist/modules/integrations/types.js.map +2 -2
  54. package/dist/modules/notifications/safeHref.js +20 -0
  55. package/dist/modules/notifications/safeHref.js.map +7 -0
  56. package/dist/modules/notifications/types.js +4 -0
  57. package/dist/modules/notifications/types.js.map +3 -3
  58. package/dist/modules/payment_gateways/types.js +14 -1
  59. package/dist/modules/payment_gateways/types.js.map +2 -2
  60. package/dist/modules/registry.js.map +2 -2
  61. package/dist/modules/search.js.map +2 -2
  62. package/dist/modules/widgets/injection-loader.js +14 -3
  63. package/dist/modules/widgets/injection-loader.js.map +2 -2
  64. package/package.json +9 -2
  65. package/src/lib/__tests__/number.test.ts +24 -1
  66. package/src/lib/__tests__/url.cross-site.test.ts +140 -0
  67. package/src/lib/auth/__tests__/server.test.ts +14 -0
  68. package/src/lib/auth/server.ts +1 -1
  69. package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +2 -2
  70. package/src/lib/bootstrap/__tests__/dynamicLoader.appModuleOverrides.test.ts +2 -2
  71. package/src/lib/bootstrap/__tests__/dynamicLoader.cacheRecovery.test.ts +2 -2
  72. package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +2 -2
  73. package/src/lib/bootstrap/__tests__/dynamicLoader.externalSubpath.test.ts +141 -0
  74. package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +2 -2
  75. package/src/lib/bootstrap/__tests__/factory.test.ts +18 -6
  76. package/src/lib/bootstrap/__tests__/next-subpath-resolve-hook.test.ts +174 -0
  77. package/src/lib/bootstrap/dynamicLoader.ts +63 -5
  78. package/src/lib/bootstrap/factory.ts +8 -3
  79. package/src/lib/bootstrap/next-subpath-resolve-hook-worker.ts +3 -0
  80. package/src/lib/bootstrap/next-subpath-resolve-hook.ts +130 -0
  81. package/src/lib/bootstrap/types.ts +5 -1
  82. package/src/lib/catalog-visibility/__tests__/hashEffectiveScope.test.ts +121 -0
  83. package/src/lib/catalog-visibility/canonicalizeEffectiveScope.ts +41 -0
  84. package/src/lib/catalog-visibility/hashEffectiveScope.ts +8 -0
  85. package/src/lib/catalog-visibility/index.ts +2 -0
  86. package/src/lib/commands/__tests__/command-bus.test.ts +100 -1
  87. package/src/lib/commands/command-bus.ts +42 -10
  88. package/src/lib/commands/command-interceptor.ts +4 -0
  89. package/src/lib/commands/types.ts +14 -0
  90. package/src/lib/crud/__tests__/exporters.test.ts +18 -0
  91. package/src/lib/crud/__tests__/ids.test.ts +34 -1
  92. package/src/lib/crud/exporters.ts +1 -1
  93. package/src/lib/crud/ids.ts +24 -0
  94. package/src/lib/db/__tests__/advisoryLock.integration.test.ts +343 -0
  95. package/src/lib/db/__tests__/advisoryLock.test.ts +564 -0
  96. package/src/lib/db/__tests__/pg-errors.test.ts +68 -1
  97. package/src/lib/db/advisoryLock.ts +271 -0
  98. package/src/lib/db/pg-errors.ts +35 -0
  99. package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +23 -0
  100. package/src/lib/localization/__tests__/translatable-fields.test.ts +17 -0
  101. package/src/lib/localization/index.ts +8 -1
  102. package/src/lib/localization/translatable-fields.ts +46 -0
  103. package/src/lib/number.ts +13 -0
  104. package/src/lib/openapi/__tests__/optional-path-params.test.ts +102 -0
  105. package/src/lib/openapi/generator.ts +106 -77
  106. package/src/lib/query/__tests__/empty-ids-filter.pg.test.ts +89 -0
  107. package/src/lib/query/__tests__/engine.empty-in-filter.test.ts +79 -0
  108. package/src/lib/query/__tests__/overlap.test.ts +219 -0
  109. package/src/lib/query/engine.ts +58 -8
  110. package/src/lib/query/join-utils.ts +1 -1
  111. package/src/lib/query/overlap.ts +106 -0
  112. package/src/lib/query/types.ts +13 -1
  113. package/src/lib/url.ts +30 -0
  114. package/src/lib/webhooks/__tests__/body.test.ts +48 -0
  115. package/src/lib/webhooks/body.ts +11 -4
  116. package/src/lib/webhooks/index.ts +1 -0
  117. package/src/modules/integrations/types.ts +6 -0
  118. package/src/modules/notifications/safeHref.ts +19 -0
  119. package/src/modules/notifications/types.ts +7 -0
  120. package/src/modules/payment_gateways/__tests__/types.test.ts +44 -0
  121. package/src/modules/payment_gateways/types.ts +52 -6
  122. package/src/modules/registry.ts +5 -0
  123. package/src/modules/search.ts +39 -0
  124. package/src/modules/widgets/__tests__/injection-loader.partitioned-registration.test.ts +78 -0
  125. package/src/modules/widgets/injection-loader.ts +33 -3
@@ -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 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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",
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 organizationScope: baseOptions.ctx.organizationScope ?? null,\n request: baseOptions.ctx.request ?? null,\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 snapshots: { before?: unknown } = {}\n let executionOptions = coreOptions\n if (handler.prepareSnapshotInsideTransaction && handler.prepare) {\n const prepare = handler.prepare\n const originalCtx = coreOptions.ctx\n const originalBeforeTransactionalWrite = originalCtx.beforeTransactionalWrite\n let prepared = false\n let transactionalCtx: CommandRuntimeContext\n transactionalCtx = {\n ...originalCtx,\n beforeTransactionalWrite: async (em, input) => {\n await originalBeforeTransactionalWrite?.(em, input)\n if (prepared) return\n Object.assign(\n snapshots,\n (await prepare(coreOptions.input, { ...transactionalCtx, transactionalEm: em })) ?? {},\n )\n prepared = true\n },\n }\n executionOptions = { ...coreOptions, ctx: transactionalCtx }\n } else {\n Object.assign(snapshots, await this.prepareSnapshots(handler, coreOptions))\n }\n effectiveOptions = executionOptions\n const sourceLog = executionOptions.redoLogEntry ?? null\n const result =\n sourceLog && typeof handler.redo === 'function'\n ? await handler.redo({ input: executionOptions.input, ctx: executionOptions.ctx, logEntry: sourceLog })\n : await handler.execute(executionOptions.input, executionOptions.ctx)\n const afterSnapshot = await this.captureAfter(handler, executionOptions, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, executionOptions, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(executionOptions.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(executionOptions.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 = executionOptions.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, executionOptions, 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 organizationScope: effectiveOptions.ctx.organizationScope ?? null,\n request: effectiveOptions.ctx.request ?? null,\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 organizationScope: replayCtx.organizationScope ?? null,\n request: replayCtx.request ?? null,\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 organizationScope: ctx.organizationScope ?? null,\n request: ctx.request ?? null,\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
6
  "names": ["result", "mergedMeta", "logEntry"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/commands/types.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ZodTypeAny } from 'zod'\nimport { randomUUID } from 'crypto'\nimport type { AuthContext } from '../auth/server'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { TransactionLifetime } from './transaction-lifetime'\n\n/**\n * Bulk-import / backfill deferral flags. When a command runs under a context that\n * carries this, the command bus and data engine suppress the heavy per-record side\n * effects flagged below so a large backfill can defer them to a single batched pass.\n *\n * IMPORTANT \u2014 the caller owns restoring whatever it suppresses. With `skipReindex`\n * the `query_index` projection (and its search tokens) is stale for every record\n * written under this context until the caller runs a batched `query_index rebuild`\n * for the affected entity types at end-of-run.\n *\n * Concurrency: these flags are read from the context and threaded as a local\n * parameter through the side-effect flush \u2014 no shared engine state is mutated \u2014 so\n * two commands running concurrently with different flags never clobber each other.\n */\nexport type BulkImportSuppression = {\n /** Skip the inline `query_index.upsert_one` / `delete_one` reindex (rebuild after the run). */\n skipReindex?: boolean\n /** Skip the per-record `<module>.<entity>.<action>` domain event emission. */\n skipEvents?: boolean\n /** Advisory: handlers that fan out per-record notifications SHOULD honor this and skip them. */\n skipNotifications?: boolean\n /**\n * Skip a command's own full-collection derived-data rebuild (e.g. category tree\n * `parentId`/`treePath`/`depth`/ancestor-descendant rewrite). The caller MUST run that rebuild\n * itself exactly once after the batch, in a `finally` that covers the completed, cancelled and\n * failed paths \u2014 otherwise every record written under this context is left with a stale\n * derived shape.\n */\n skipDerivedRebuild?: boolean\n}\n\nexport type CommandRuntimeContext = {\n container: AwilixContainer\n auth: AuthContext | null\n organizationScope: OrganizationScope | null\n selectedOrganizationId: string | null\n organizationIds: string[] | null\n request?: Request\n syncOrigin?: string | null\n /**\n * See {@link BulkImportSuppression}. Set by bulk backfill callers to defer heavy\n * per-record side effects (reindex, events, notifications). The caller MUST rebuild\n * the `query_index` for the affected entity types after the run when `skipReindex`\n * is set. Unset for normal (interactive) writes \u2014 they get all side effects.\n */\n bulkImport?: BulkImportSuppression\n /**\n * Marks a trusted server-side invocation (CLI seeding, tenant setup) that runs\n * without an authenticated end-user actor. Commands that gate writes behind a\n * privileged actor (e.g. super-admin-only platform tables) may treat this as\n * an explicit system grant. HTTP request paths MUST NOT set this \u2014 they always\n * carry a real `auth` actor, so a present-but-unprivileged actor stays denied.\n */\n systemActor?: boolean\n /**\n * When set, command handlers that support it MUST run their writes within this\n * existing transactional EntityManager (reusing its row locks) instead of\n * opening their own transaction. Lets a caller compose a command with its own\n * surrounding work as a single atomic, single-locked operation.\n */\n transactionalEm?: EntityManager\n /**\n * Identifies the owner of an already-active {@link transactionalEm} lifetime.\n * Atomic replay accepts an ambient EntityManager only when this is the exact\n * token returned by `getTransactionLifetime(transactionalEm)` from the outer\n * `withAtomicFlush` phase. The owner completes the token after its real commit\n * or rollback, allowing replay leases and post-commit work to follow that\n * boundary. Omit when the command bus opens and owns the transaction itself.\n */\n transactionLifetime?: TransactionLifetime\n /**\n * Optional request-level replay authorization that the command bus binds to\n * its replay EntityManager. Atomic handlers run this guard in the same\n * transaction as the source-log transition and domain mutation; legacy\n * handlers run it in a dedicated read transaction before preserving their\n * existing replay lifecycle.\n */\n replayTransactionGuard?: (args: {\n operation: CommandReplayOperation\n logEntry: CommandUndoLogEntry\n transactionalEm: EntityManager\n }) => Promise<void> | void\n /**\n * On-behalf-of attribution for non-human principals (Agent Identity &\n * On-Behalf-Of, Wave 4 P2). When an agent runs on behalf of a human, the\n * orchestrator's `runAs` wrapper sets this so every `ActionLog` the command\n * path writes records `actorUserId = runAs.actorUserId` (the agent principal's\n * `auth.User` id), `onBehalfOfUserId = runAs.onBehalfOfUserId` (the invoking\n * human, or null for system-invoked agents), and `sourceKey = runAs.source`\n * (`'agent'`). Additive + optional: callers that omit it keep the existing\n * `ctx.auth.sub`-derived attribution unchanged. This threads agent attribution\n * through the SAME audited Command/CRUD path as a human action \u2014 not a parallel\n * audit path.\n */\n runAs?: CommandRunAsContext\n}\n\nexport type CommandRunAsContext = {\n /** The actor stamped on every ActionLog this context produces (agent `auth.User` id). */\n actorUserId: string\n /** The human (or system) principal the actor acts on behalf of; null when system-invoked. */\n onBehalfOfUserId?: string | null\n /** The audit source key for the attributed writes; `'agent'` for agent runs. */\n source: 'agent'\n}\n\nexport type CommandLogMetadata = {\n skipLog?: boolean\n /**\n * Per-execution replay policy. `false` records the audit entry without an undo\n * token or command payload, so neither undo nor redo can replay sensitive input.\n * Omitted preserves the command handler's existing replay behavior.\n */\n replayable?: boolean\n /**\n * Overrides the command input persisted in the audit entry's redo envelope.\n * The default is the raw input, which for credential-bearing commands would put\n * a plaintext secret at rest in `action_logs`. A handler that still wants its\n * operation to be undoable supplies a redacted projection here instead of\n * switching the whole entry to `replayable: false` \u2014 suppressing the undo token\n * removes the operator's ability to revert a write that carries no secret of its\n * own (undoing a user create only deletes a row).\n */\n redoInput?: unknown\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n onBehalfOfUserId?: string | null\n actionLabel?: string | null\n resourceKind?: string | null\n resourceId?: string | null\n parentResourceKind?: string | null\n parentResourceId?: string | null\n undoToken?: string | null\n payload?: unknown\n snapshotBefore?: unknown\n snapshotAfter?: unknown\n relatedResourceKind?: string | null\n relatedResourceId?: string | null\n changes?: Record<string, unknown> | null\n context?: Record<string, unknown> | null\n}\n\nexport type CommandExecuteResult<TResult> = {\n result: TResult\n logEntry: any | null\n /**\n * True when an atomic redo finalized its source action log in the same\n * transaction as the domain mutation and the newly persisted log entry, and\n * that transaction has committed. An ambient replay returns this as false\n * while its caller-owned transaction is still active; the returned result\n * object is updated to true by the owner's commit callback. It remains false\n * on rollback. Callers that historically finalized redo themselves can use\n * this additive signal to avoid a redundant post-commit write.\n */\n replaySourceFinalized?: boolean\n}\n\n/**\n * Shape of the persisted action log handed to a command's `undo()` handler.\n *\n * IMPORTANT: there is intentionally **no `payload` field**. `buildLog()` returns\n * a `payload` in its metadata, but the command bus persists that under\n * `commandPayload` (column `command_payload`, wrapped in a redo envelope) \u2014 the\n * stored row never has a top-level `payload`. Reading `logEntry.payload` in an\n * undo handler is therefore always `undefined` and silently no-ops the undo\n * (issue #2504). Always read the undo snapshot through\n * `extractUndoPayload(logEntry)` from `@open-mercato/shared/lib/commands/undo`,\n * which unwraps `commandPayload`/snapshots correctly. Omitting `payload` here\n * makes the footgun a compile-time error instead of a runtime silent failure.\n */\nexport type CommandUndoLogEntry = {\n id?: string\n commandId?: string\n commandPayload?: unknown | null\n snapshotBefore?: unknown | null\n snapshotAfter?: unknown | null\n resourceKind?: string | null\n resourceId?: string | null\n undoToken?: string | null\n actionLabel?: string | null\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n changesJson?: Record<string, unknown> | null\n contextJson?: Record<string, unknown> | null\n createdAt?: Date | string\n updatedAt?: Date | string\n}\n\nexport type CommandLogBuilderArgs<TInput, TResult> = {\n input: TInput\n result: TResult\n ctx: CommandRuntimeContext\n snapshots: {\n before?: unknown\n after?: unknown\n }\n}\n\nexport type CommandReplayOperation = 'undo' | 'redo'\n\nexport type CommandReplayAuthorizationArgs<TInput> = {\n operation: CommandReplayOperation\n input: TInput\n ctx: CommandRuntimeContext\n logEntry: CommandUndoLogEntry\n}\n\nexport interface CommandHandler<TInput = unknown, TResult = unknown> {\n readonly id: string\n readonly isUndoable?: boolean\n /**\n * Opts replay into the command bus's transaction-bound lifecycle. The source\n * action-log transition, handler mutation, and any new replay log share one\n * EntityManager and commit or roll back together. Handlers that enable this\n * MUST reuse `ctx.transactionalEm` for every replay-time database operation.\n * Omitted preserves the legacy non-atomic replay path.\n */\n readonly atomicReplay?: boolean\n /**\n * Acquires every authorization-state lock needed by an atomic replay before\n * request-level replay authorization and feature-gated interceptors run.\n * Implementations MUST discover their complete actor/target/destination set\n * before taking the first lock and MUST reuse `ctx.transactionalEm`.\n * Omitted preserves the existing lifecycle for handlers that do not need\n * domain-specific authorization stabilization.\n */\n stabilizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n /**\n * Optional Zod schema describing the command's return value. Feeds the\n * workflows context ledger so downstream activities can reason about the\n * shape a command produces; when absent the ledger renders the output as\n * unknown.\n */\n readonly outputSchema?: ZodTypeAny\n prepare?(input: TInput, ctx: CommandRuntimeContext): Promise<{ before?: unknown } | null> | { before?: unknown } | null\n execute(input: TInput, ctx: CommandRuntimeContext): Promise<TResult> | TResult\n buildLog?(args: CommandLogBuilderArgs<TInput, TResult>): Promise<CommandLogMetadata | null | undefined> | CommandLogMetadata | null | undefined\n captureAfter?(input: TInput, result: TResult, ctx: CommandRuntimeContext): Promise<unknown> | unknown\n /**\n * Re-authorizes a stored command against the actor and resource state that\n * exist at replay time. Legacy handlers are checked before claiming an undo\n * log or beginning redo processing. Atomic handlers are checked inside their\n * replay transaction before the guarded source transition; their mutation\n * path can repeat the check after taking domain locks. Throwing aborts replay\n * without committed domain side effects.\n */\n authorizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n undo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<void> | void\n /**\n * Optional redo handler. When defined, the command bus calls this instead of\n * `execute()` while replaying a previously undone action (the redo route passes\n * `redoLogEntry` in the execution options). It receives the source action log so\n * it can re-materialize the original record **reusing its id** \u2014 for a create\n * command this restores the soft-deleted row (or re-creates it from the\n * `snapshotAfter`) instead of minting a new id, keeping undo/redo snapshots and\n * references stable (issue #2506, invariant I6). Handlers without `redo` keep the\n * legacy behavior of replaying `execute(__redoInput)`.\n */\n redo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<TResult> | TResult\n}\n\nexport type CommandExecutionOptions<TInput> = {\n input: TInput\n ctx: CommandRuntimeContext\n metadata?: CommandLogMetadata | null\n skipCacheInvalidation?: boolean\n /**\n * When set, marks this execution as a redo of a previously undone action. If the\n * resolved handler defines a `redo` method, the command bus calls\n * `handler.redo({ input, ctx, logEntry })` instead of `handler.execute(...)`. The\n * rest of the pipeline (snapshots, buildLog, undo-token minting, persistence,\n * cache invalidation, side effects) is identical, so the fresh log entry \u2014 and\n * the `x-om-operation` header derived from it \u2014 automatically carry the restored\n * resourceId. Ignored when the handler has no `redo` method (legacy replay path).\n */\n redoLogEntry?: CommandUndoLogEntry | null\n}\n\nexport function defaultUndoToken(): string {\n return randomUUID()\n}\n"],
5
- "mappings": "AAGA,SAAS,kBAAkB;AA6RpB,SAAS,mBAA2B;AACzC,SAAO,WAAW;AACpB;",
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ZodTypeAny } from 'zod'\nimport { randomUUID } from 'crypto'\nimport type { AuthContext } from '../auth/server'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { TransactionLifetime } from './transaction-lifetime'\n\n/**\n * Bulk-import / backfill deferral flags. When a command runs under a context that\n * carries this, the command bus and data engine suppress the heavy per-record side\n * effects flagged below so a large backfill can defer them to a single batched pass.\n *\n * IMPORTANT \u2014 the caller owns restoring whatever it suppresses. With `skipReindex`\n * the `query_index` projection (and its search tokens) is stale for every record\n * written under this context until the caller runs a batched `query_index rebuild`\n * for the affected entity types at end-of-run.\n *\n * Concurrency: these flags are read from the context and threaded as a local\n * parameter through the side-effect flush \u2014 no shared engine state is mutated \u2014 so\n * two commands running concurrently with different flags never clobber each other.\n */\nexport type BulkImportSuppression = {\n /** Skip the inline `query_index.upsert_one` / `delete_one` reindex (rebuild after the run). */\n skipReindex?: boolean\n /** Skip the per-record `<module>.<entity>.<action>` domain event emission. */\n skipEvents?: boolean\n /** Advisory: handlers that fan out per-record notifications SHOULD honor this and skip them. */\n skipNotifications?: boolean\n /**\n * Skip a command's own full-collection derived-data rebuild (e.g. category tree\n * `parentId`/`treePath`/`depth`/ancestor-descendant rewrite). The caller MUST run that rebuild\n * itself exactly once after the batch, in a `finally` that covers the completed, cancelled and\n * failed paths \u2014 otherwise every record written under this context is left with a stale\n * derived shape.\n */\n skipDerivedRebuild?: boolean\n}\n\nexport type CommandRuntimeContext = {\n container: AwilixContainer\n auth: AuthContext | null\n organizationScope: OrganizationScope | null\n selectedOrganizationId: string | null\n organizationIds: string[] | null\n request?: Request\n syncOrigin?: string | null\n /**\n * See {@link BulkImportSuppression}. Set by bulk backfill callers to defer heavy\n * per-record side effects (reindex, events, notifications). The caller MUST rebuild\n * the `query_index` for the affected entity types after the run when `skipReindex`\n * is set. Unset for normal (interactive) writes \u2014 they get all side effects.\n */\n bulkImport?: BulkImportSuppression\n /**\n * Marks a trusted server-side invocation (CLI seeding, tenant setup) that runs\n * without an authenticated end-user actor. Commands that gate writes behind a\n * privileged actor (e.g. super-admin-only platform tables) may treat this as\n * an explicit system grant. HTTP request paths MUST NOT set this \u2014 they always\n * carry a real `auth` actor, so a present-but-unprivileged actor stays denied.\n */\n systemActor?: boolean\n /**\n * When set, command handlers that support it MUST run their writes within this\n * existing transactional EntityManager (reusing its row locks) instead of\n * opening their own transaction. Lets a caller compose a command with its own\n * surrounding work as a single atomic, single-locked operation.\n */\n transactionalEm?: EntityManager\n /**\n * Optional server-side guard that supported command handlers invoke after\n * opening their write transaction and before reading or mutating records.\n * The guard receives that same transaction-bound EntityManager and, when the\n * handler provides it, the final input after command interceptors. The input\n * stays optional so existing handlers and callers remain source-compatible.\n */\n beforeTransactionalWrite?: (em: EntityManager, input?: unknown) => Promise<void>\n /**\n * Identifies the owner of an already-active {@link transactionalEm} lifetime.\n * Atomic replay accepts an ambient EntityManager only when this is the exact\n * token returned by `getTransactionLifetime(transactionalEm)` from the outer\n * `withAtomicFlush` phase. The owner completes the token after its real commit\n * or rollback, allowing replay leases and post-commit work to follow that\n * boundary. Omit when the command bus opens and owns the transaction itself.\n */\n transactionLifetime?: TransactionLifetime\n /**\n * Optional request-level replay authorization that the command bus binds to\n * its replay EntityManager. Atomic handlers run this guard in the same\n * transaction as the source-log transition and domain mutation; legacy\n * handlers run it in a dedicated read transaction before preserving their\n * existing replay lifecycle.\n */\n replayTransactionGuard?: (args: {\n operation: CommandReplayOperation\n logEntry: CommandUndoLogEntry\n transactionalEm: EntityManager\n }) => Promise<void> | void\n /**\n * On-behalf-of attribution for non-human principals (Agent Identity &\n * On-Behalf-Of, Wave 4 P2). When an agent runs on behalf of a human, the\n * orchestrator's `runAs` wrapper sets this so every `ActionLog` the command\n * path writes records `actorUserId = runAs.actorUserId` (the agent principal's\n * `auth.User` id), `onBehalfOfUserId = runAs.onBehalfOfUserId` (the invoking\n * human, or null for system-invoked agents), and `sourceKey = runAs.source`\n * (`'agent'`). Additive + optional: callers that omit it keep the existing\n * `ctx.auth.sub`-derived attribution unchanged. This threads agent attribution\n * through the SAME audited Command/CRUD path as a human action \u2014 not a parallel\n * audit path.\n */\n runAs?: CommandRunAsContext\n}\n\nexport type CommandRunAsContext = {\n /** The actor stamped on every ActionLog this context produces (agent `auth.User` id). */\n actorUserId: string\n /** The human (or system) principal the actor acts on behalf of; null when system-invoked. */\n onBehalfOfUserId?: string | null\n /** The audit source key for the attributed writes; `'agent'` for agent runs. */\n source: 'agent'\n}\n\nexport type CommandLogMetadata = {\n skipLog?: boolean\n /**\n * Per-execution replay policy. `false` records the audit entry without an undo\n * token or command payload, so neither undo nor redo can replay sensitive input.\n * Omitted preserves the command handler's existing replay behavior.\n */\n replayable?: boolean\n /**\n * Overrides the command input persisted in the audit entry's redo envelope.\n * The default is the raw input, which for credential-bearing commands would put\n * a plaintext secret at rest in `action_logs`. A handler that still wants its\n * operation to be undoable supplies a redacted projection here instead of\n * switching the whole entry to `replayable: false` \u2014 suppressing the undo token\n * removes the operator's ability to revert a write that carries no secret of its\n * own (undoing a user create only deletes a row).\n */\n redoInput?: unknown\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n onBehalfOfUserId?: string | null\n actionLabel?: string | null\n resourceKind?: string | null\n resourceId?: string | null\n parentResourceKind?: string | null\n parentResourceId?: string | null\n undoToken?: string | null\n payload?: unknown\n snapshotBefore?: unknown\n snapshotAfter?: unknown\n relatedResourceKind?: string | null\n relatedResourceId?: string | null\n changes?: Record<string, unknown> | null\n context?: Record<string, unknown> | null\n}\n\nexport type CommandExecuteResult<TResult> = {\n result: TResult\n logEntry: any | null\n /**\n * True when an atomic redo finalized its source action log in the same\n * transaction as the domain mutation and the newly persisted log entry, and\n * that transaction has committed. An ambient replay returns this as false\n * while its caller-owned transaction is still active; the returned result\n * object is updated to true by the owner's commit callback. It remains false\n * on rollback. Callers that historically finalized redo themselves can use\n * this additive signal to avoid a redundant post-commit write.\n */\n replaySourceFinalized?: boolean\n}\n\n/**\n * Shape of the persisted action log handed to a command's `undo()` handler.\n *\n * IMPORTANT: there is intentionally **no `payload` field**. `buildLog()` returns\n * a `payload` in its metadata, but the command bus persists that under\n * `commandPayload` (column `command_payload`, wrapped in a redo envelope) \u2014 the\n * stored row never has a top-level `payload`. Reading `logEntry.payload` in an\n * undo handler is therefore always `undefined` and silently no-ops the undo\n * (issue #2504). Always read the undo snapshot through\n * `extractUndoPayload(logEntry)` from `@open-mercato/shared/lib/commands/undo`,\n * which unwraps `commandPayload`/snapshots correctly. Omitting `payload` here\n * makes the footgun a compile-time error instead of a runtime silent failure.\n */\nexport type CommandUndoLogEntry = {\n id?: string\n commandId?: string\n commandPayload?: unknown | null\n snapshotBefore?: unknown | null\n snapshotAfter?: unknown | null\n resourceKind?: string | null\n resourceId?: string | null\n undoToken?: string | null\n actionLabel?: string | null\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n changesJson?: Record<string, unknown> | null\n contextJson?: Record<string, unknown> | null\n createdAt?: Date | string\n updatedAt?: Date | string\n}\n\nexport type CommandLogBuilderArgs<TInput, TResult> = {\n input: TInput\n result: TResult\n ctx: CommandRuntimeContext\n snapshots: {\n before?: unknown\n after?: unknown\n }\n}\n\nexport type CommandReplayOperation = 'undo' | 'redo'\n\nexport type CommandReplayAuthorizationArgs<TInput> = {\n operation: CommandReplayOperation\n input: TInput\n ctx: CommandRuntimeContext\n logEntry: CommandUndoLogEntry\n}\n\nexport interface CommandHandler<TInput = unknown, TResult = unknown> {\n readonly id: string\n readonly isUndoable?: boolean\n /**\n * Opts replay into the command bus's transaction-bound lifecycle. The source\n * action-log transition, handler mutation, and any new replay log share one\n * EntityManager and commit or roll back together. Handlers that enable this\n * MUST reuse `ctx.transactionalEm` for every replay-time database operation.\n * Omitted preserves the legacy non-atomic replay path.\n */\n readonly atomicReplay?: boolean\n /**\n * Acquires every authorization-state lock needed by an atomic replay before\n * request-level replay authorization and feature-gated interceptors run.\n * Implementations MUST discover their complete actor/target/destination set\n * before taking the first lock and MUST reuse `ctx.transactionalEm`.\n * Omitted preserves the existing lifecycle for handlers that do not need\n * domain-specific authorization stabilization.\n */\n stabilizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n /**\n * Optional Zod schema describing the command's return value. Feeds the\n * workflows context ledger so downstream activities can reason about the\n * shape a command produces; when absent the ledger renders the output as\n * unknown.\n */\n readonly outputSchema?: ZodTypeAny\n prepare?(input: TInput, ctx: CommandRuntimeContext): Promise<{ before?: unknown } | null> | { before?: unknown } | null\n /**\n * Opt in to taking the undo snapshot inside the write transaction. The bus\n * defers `prepare()` into `ctx.beforeTransactionalWrite`, after the handler's\n * transaction guard and before its first read or mutation.\n */\n readonly prepareSnapshotInsideTransaction?: boolean\n execute(input: TInput, ctx: CommandRuntimeContext): Promise<TResult> | TResult\n buildLog?(args: CommandLogBuilderArgs<TInput, TResult>): Promise<CommandLogMetadata | null | undefined> | CommandLogMetadata | null | undefined\n captureAfter?(input: TInput, result: TResult, ctx: CommandRuntimeContext): Promise<unknown> | unknown\n /**\n * Re-authorizes a stored command against the actor and resource state that\n * exist at replay time. Legacy handlers are checked before claiming an undo\n * log or beginning redo processing. Atomic handlers are checked inside their\n * replay transaction before the guarded source transition; their mutation\n * path can repeat the check after taking domain locks. Throwing aborts replay\n * without committed domain side effects.\n */\n authorizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n undo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<void> | void\n /**\n * Optional redo handler. When defined, the command bus calls this instead of\n * `execute()` while replaying a previously undone action (the redo route passes\n * `redoLogEntry` in the execution options). It receives the source action log so\n * it can re-materialize the original record **reusing its id** \u2014 for a create\n * command this restores the soft-deleted row (or re-creates it from the\n * `snapshotAfter`) instead of minting a new id, keeping undo/redo snapshots and\n * references stable (issue #2506, invariant I6). Handlers without `redo` keep the\n * legacy behavior of replaying `execute(__redoInput)`.\n */\n redo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<TResult> | TResult\n}\n\nexport type CommandExecutionOptions<TInput> = {\n input: TInput\n ctx: CommandRuntimeContext\n metadata?: CommandLogMetadata | null\n skipCacheInvalidation?: boolean\n /**\n * When set, marks this execution as a redo of a previously undone action. If the\n * resolved handler defines a `redo` method, the command bus calls\n * `handler.redo({ input, ctx, logEntry })` instead of `handler.execute(...)`. The\n * rest of the pipeline (snapshots, buildLog, undo-token minting, persistence,\n * cache invalidation, side effects) is identical, so the fresh log entry \u2014 and\n * the `x-om-operation` header derived from it \u2014 automatically carry the restored\n * resourceId. Ignored when the handler has no `redo` method (legacy replay path).\n */\n redoLogEntry?: CommandUndoLogEntry | null\n}\n\nexport function defaultUndoToken(): string {\n return randomUUID()\n}\n"],
5
+ "mappings": "AAGA,SAAS,kBAAkB;AA2SpB,SAAS,mBAA2B;AACzC,SAAO,WAAW;AACpB;",
6
6
  "names": []
7
7
  }
@@ -47,7 +47,7 @@ function escapeXmlValue(value) {
47
47
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
48
48
  }
49
49
  function serializeCsv(prepared) {
50
- const headers = prepared.columns.map((col) => col.header);
50
+ const headers = prepared.columns.map((col) => escapeCsv(neutralizeSpreadsheetFormula(col.header)));
51
51
  const lines = [headers.join(",")];
52
52
  for (const row of prepared.rows) {
53
53
  const values = prepared.columns.map((col) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/crud/exporters.ts"],
4
- "sourcesContent": ["export type CrudExportFormat = 'csv' | 'json' | 'xml' | 'markdown'\n\nexport type CrudExportColumn = {\n field: string\n header: string\n}\n\nexport type PreparedExport = {\n columns: CrudExportColumn[]\n rows: Array<Record<string, unknown>>\n}\n\nexport type SerializedExport = {\n body: string\n contentType: string\n fileExtension: string\n}\n\nconst CSV_CONTENT_TYPE = 'text/csv; charset=utf-8'\nconst JSON_CONTENT_TYPE = 'application/json; charset=utf-8'\nconst XML_CONTENT_TYPE = 'application/xml; charset=utf-8'\nconst MARKDOWN_CONTENT_TYPE = 'text/markdown; charset=utf-8'\n\nfunction normalizeValue(value: unknown): string {\n if (value == null) return ''\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'bigint') return value.toString()\n if (typeof value === 'object') {\n if (Array.isArray(value)) return value.map((v) => normalizeValue(v)).filter(Boolean).join(', ')\n return JSON.stringify(value)\n }\n return String(value)\n}\n\nfunction escapeCsv(value: string): string {\n if (/[\",\\n\\r]/.test(value)) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value\n}\n\nfunction neutralizeSpreadsheetFormula(value: string): string {\n if (/^[=+\\-@\\t\\r\\n]/.test(value)) {\n return `'${value}`\n }\n return value\n}\n\nfunction normalizeCsvValue(value: unknown): string {\n const normalized = normalizeValue(value)\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return normalized\n }\n return neutralizeSpreadsheetFormula(normalized)\n}\n\nfunction escapeMarkdown(value: string): string {\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\|/g, '\\\\|')\n .replace(/\\r?\\n/g, '<br />')\n return escaped || ' '\n}\n\nfunction escapeXmlTag(tag: string, fallbackIndex: number): string {\n const sanitized = tag.replace(/[^A-Za-z0-9_:-]/g, '_')\n const normalized = sanitized.length > 0 ? sanitized : `field_${fallbackIndex}`\n if (/^[^A-Za-z_]/.test(normalized)) {\n return `f_${normalized}`\n }\n return normalized\n}\n\nfunction escapeXmlValue(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\nfunction serializeCsv(prepared: PreparedExport): SerializedExport {\n const headers = prepared.columns.map((col) => col.header)\n const lines = [headers.join(',')]\n for (const row of prepared.rows) {\n const values = prepared.columns.map((col) => {\n const raw = normalizeCsvValue(row[col.field])\n return escapeCsv(raw)\n })\n lines.push(values.join(','))\n }\n return {\n body: lines.join('\\n'),\n contentType: CSV_CONTENT_TYPE,\n fileExtension: 'csv',\n }\n}\n\nfunction serializeJson(prepared: PreparedExport): SerializedExport {\n // For JSON we return objects keyed by exported field names with human headers as metadata\n const payload = prepared.rows.map((row) => {\n const obj: Record<string, unknown> = {}\n for (const column of prepared.columns) {\n obj[column.header] = row[column.field]\n }\n return obj\n })\n return {\n body: JSON.stringify(payload, null, 2),\n contentType: JSON_CONTENT_TYPE,\n fileExtension: 'json',\n }\n}\n\nfunction serializeXml(prepared: PreparedExport): SerializedExport {\n const lines: string[] = ['<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '<records>']\n for (const row of prepared.rows) {\n lines.push(' <record>')\n prepared.columns.forEach((column, index) => {\n const tag = escapeXmlTag(column.field || column.header, index)\n const value = escapeXmlValue(normalizeValue(row[column.field]))\n lines.push(` <${tag}>${value}</${tag}>`)\n })\n lines.push(' </record>')\n }\n lines.push('</records>')\n return {\n body: lines.join('\\n'),\n contentType: XML_CONTENT_TYPE,\n fileExtension: 'xml',\n }\n}\n\nfunction serializeMarkdown(prepared: PreparedExport): SerializedExport {\n const headers = prepared.columns.map((col) => escapeMarkdown(col.header))\n const headerLine = `| ${headers.join(' | ')} |`\n const dividerLine = `| ${prepared.columns.map(() => '---').join(' | ')} |`\n const rows = prepared.rows.map((row) => {\n const cells = prepared.columns.map((col) => escapeMarkdown(normalizeValue(row[col.field])))\n return `| ${cells.join(' | ')} |`\n })\n const body = [headerLine, dividerLine, ...rows].join('\\n')\n return {\n body,\n contentType: MARKDOWN_CONTENT_TYPE,\n fileExtension: 'md',\n }\n}\n\nexport function serializeExport(prepared: PreparedExport, format: CrudExportFormat): SerializedExport {\n switch (format) {\n case 'csv':\n return serializeCsv(prepared)\n case 'json':\n return serializeJson(prepared)\n case 'xml':\n return serializeXml(prepared)\n case 'markdown':\n return serializeMarkdown(prepared)\n default:\n return serializeJson(prepared)\n }\n}\n\nexport function normalizeExportFormat(raw: unknown): CrudExportFormat | null {\n if (typeof raw !== 'string' || raw.trim() === '') return null\n const value = raw.toLowerCase()\n if (value === 'csv') return 'csv'\n if (value === 'json' || value === 'application/json') return 'json'\n if (value === 'xml' || value === 'application/xml') return 'xml'\n if (value === 'markdown' || value === 'md' || value === 'text/markdown') return 'markdown'\n return null\n}\n\nexport function defaultExportFilename(base: string | undefined | null, format: CrudExportFormat): string {\n const safeBase = (base && base.trim().length > 0 ? base.trim() : 'export')\n .replace(/[^a-z0-9_\\-]/gi, '_')\n const suffix = format === 'markdown' ? 'md' : format\n return `${safeBase}.${suffix}`\n}\n\nexport function toHeaderLabel(key: string): string {\n const withoutPrefix = key.replace(/^cf[:_\\-\\s]+/i, '')\n const normalized = withoutPrefix.replace(/[_\\-\\s]+/g, ' ').trim()\n if (!normalized) return 'Field'\n return normalized.replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nexport function ensureColumns(rows: Array<Record<string, unknown>>, hint?: CrudExportColumn[]): CrudExportColumn[] {\n if (hint && hint.length > 0) return hint\n const used = new Map<string, string>()\n rows.forEach((row) => {\n Object.keys(row || {}).forEach((key) => {\n if (!used.has(key)) used.set(key, key)\n })\n })\n if (used.size === 0) return [{ field: 'id', header: 'ID' }]\n return Array.from(used.keys()).map((key) => ({\n field: key,\n header: toHeaderLabel(key),\n }))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export type CrudExportFormat = 'csv' | 'json' | 'xml' | 'markdown'\n\nexport type CrudExportColumn = {\n field: string\n header: string\n}\n\nexport type PreparedExport = {\n columns: CrudExportColumn[]\n rows: Array<Record<string, unknown>>\n}\n\nexport type SerializedExport = {\n body: string\n contentType: string\n fileExtension: string\n}\n\nconst CSV_CONTENT_TYPE = 'text/csv; charset=utf-8'\nconst JSON_CONTENT_TYPE = 'application/json; charset=utf-8'\nconst XML_CONTENT_TYPE = 'application/xml; charset=utf-8'\nconst MARKDOWN_CONTENT_TYPE = 'text/markdown; charset=utf-8'\n\nfunction normalizeValue(value: unknown): string {\n if (value == null) return ''\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'bigint') return value.toString()\n if (typeof value === 'object') {\n if (Array.isArray(value)) return value.map((v) => normalizeValue(v)).filter(Boolean).join(', ')\n return JSON.stringify(value)\n }\n return String(value)\n}\n\nfunction escapeCsv(value: string): string {\n if (/[\",\\n\\r]/.test(value)) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value\n}\n\nfunction neutralizeSpreadsheetFormula(value: string): string {\n if (/^[=+\\-@\\t\\r\\n]/.test(value)) {\n return `'${value}`\n }\n return value\n}\n\nfunction normalizeCsvValue(value: unknown): string {\n const normalized = normalizeValue(value)\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return normalized\n }\n return neutralizeSpreadsheetFormula(normalized)\n}\n\nfunction escapeMarkdown(value: string): string {\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\|/g, '\\\\|')\n .replace(/\\r?\\n/g, '<br />')\n return escaped || ' '\n}\n\nfunction escapeXmlTag(tag: string, fallbackIndex: number): string {\n const sanitized = tag.replace(/[^A-Za-z0-9_:-]/g, '_')\n const normalized = sanitized.length > 0 ? sanitized : `field_${fallbackIndex}`\n if (/^[^A-Za-z_]/.test(normalized)) {\n return `f_${normalized}`\n }\n return normalized\n}\n\nfunction escapeXmlValue(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\nfunction serializeCsv(prepared: PreparedExport): SerializedExport {\n const headers = prepared.columns.map((col) => escapeCsv(neutralizeSpreadsheetFormula(col.header)))\n const lines = [headers.join(',')]\n for (const row of prepared.rows) {\n const values = prepared.columns.map((col) => {\n const raw = normalizeCsvValue(row[col.field])\n return escapeCsv(raw)\n })\n lines.push(values.join(','))\n }\n return {\n body: lines.join('\\n'),\n contentType: CSV_CONTENT_TYPE,\n fileExtension: 'csv',\n }\n}\n\nfunction serializeJson(prepared: PreparedExport): SerializedExport {\n // For JSON we return objects keyed by exported field names with human headers as metadata\n const payload = prepared.rows.map((row) => {\n const obj: Record<string, unknown> = {}\n for (const column of prepared.columns) {\n obj[column.header] = row[column.field]\n }\n return obj\n })\n return {\n body: JSON.stringify(payload, null, 2),\n contentType: JSON_CONTENT_TYPE,\n fileExtension: 'json',\n }\n}\n\nfunction serializeXml(prepared: PreparedExport): SerializedExport {\n const lines: string[] = ['<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '<records>']\n for (const row of prepared.rows) {\n lines.push(' <record>')\n prepared.columns.forEach((column, index) => {\n const tag = escapeXmlTag(column.field || column.header, index)\n const value = escapeXmlValue(normalizeValue(row[column.field]))\n lines.push(` <${tag}>${value}</${tag}>`)\n })\n lines.push(' </record>')\n }\n lines.push('</records>')\n return {\n body: lines.join('\\n'),\n contentType: XML_CONTENT_TYPE,\n fileExtension: 'xml',\n }\n}\n\nfunction serializeMarkdown(prepared: PreparedExport): SerializedExport {\n const headers = prepared.columns.map((col) => escapeMarkdown(col.header))\n const headerLine = `| ${headers.join(' | ')} |`\n const dividerLine = `| ${prepared.columns.map(() => '---').join(' | ')} |`\n const rows = prepared.rows.map((row) => {\n const cells = prepared.columns.map((col) => escapeMarkdown(normalizeValue(row[col.field])))\n return `| ${cells.join(' | ')} |`\n })\n const body = [headerLine, dividerLine, ...rows].join('\\n')\n return {\n body,\n contentType: MARKDOWN_CONTENT_TYPE,\n fileExtension: 'md',\n }\n}\n\nexport function serializeExport(prepared: PreparedExport, format: CrudExportFormat): SerializedExport {\n switch (format) {\n case 'csv':\n return serializeCsv(prepared)\n case 'json':\n return serializeJson(prepared)\n case 'xml':\n return serializeXml(prepared)\n case 'markdown':\n return serializeMarkdown(prepared)\n default:\n return serializeJson(prepared)\n }\n}\n\nexport function normalizeExportFormat(raw: unknown): CrudExportFormat | null {\n if (typeof raw !== 'string' || raw.trim() === '') return null\n const value = raw.toLowerCase()\n if (value === 'csv') return 'csv'\n if (value === 'json' || value === 'application/json') return 'json'\n if (value === 'xml' || value === 'application/xml') return 'xml'\n if (value === 'markdown' || value === 'md' || value === 'text/markdown') return 'markdown'\n return null\n}\n\nexport function defaultExportFilename(base: string | undefined | null, format: CrudExportFormat): string {\n const safeBase = (base && base.trim().length > 0 ? base.trim() : 'export')\n .replace(/[^a-z0-9_\\-]/gi, '_')\n const suffix = format === 'markdown' ? 'md' : format\n return `${safeBase}.${suffix}`\n}\n\nexport function toHeaderLabel(key: string): string {\n const withoutPrefix = key.replace(/^cf[:_\\-\\s]+/i, '')\n const normalized = withoutPrefix.replace(/[_\\-\\s]+/g, ' ').trim()\n if (!normalized) return 'Field'\n return normalized.replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nexport function ensureColumns(rows: Array<Record<string, unknown>>, hint?: CrudExportColumn[]): CrudExportColumn[] {\n if (hint && hint.length > 0) return hint\n const used = new Map<string, string>()\n rows.forEach((row) => {\n Object.keys(row || {}).forEach((key) => {\n if (!used.has(key)) used.set(key, key)\n })\n })\n if (used.size === 0) return [{ field: 'id', header: 'ID' }]\n return Array.from(used.keys()).map((key) => ({\n field: key,\n header: toHeaderLabel(key),\n }))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -40,6 +40,16 @@ function parseIdsParam(raw, maxIds = MAX_IDS_PER_REQUEST) {
40
40
  const parsed = normalizeIdList(values);
41
41
  return parsed.slice(0, safeMax);
42
42
  }
43
+ const UUID_SHAPE_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
+ function splitIdListParam(raw) {
45
+ if (typeof raw !== "string") return [];
46
+ return raw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
47
+ }
48
+ function isUuidListParam(raw) {
49
+ if (raw === void 0) return true;
50
+ if (typeof raw !== "string") return false;
51
+ return splitIdListParam(raw).every((value) => UUID_SHAPE_REGEX.test(value));
52
+ }
43
53
  function isIdsParamProvided(raw) {
44
54
  const occurrences = Array.isArray(raw) ? raw : [raw];
45
55
  return occurrences.some((value) => typeof value === "string" && value.trim().length > 0);
@@ -69,7 +79,9 @@ function mergeIdFilter(existingFilters, parsedIds, options) {
69
79
  export {
70
80
  MAX_IDS_PER_REQUEST,
71
81
  isIdsParamProvided,
82
+ isUuidListParam,
72
83
  mergeIdFilter,
73
- parseIdsParam
84
+ parseIdsParam,
85
+ splitIdListParam
74
86
  };
75
87
  //# sourceMappingURL=ids.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/crud/ids.ts"],
4
- "sourcesContent": ["import type { Where } from '@open-mercato/shared/lib/query/types'\nimport { toQueryValueList } from './query-params'\n\nexport const MAX_IDS_PER_REQUEST = 200\n\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nfunction isUuid(value: unknown): value is string {\n return typeof value === 'string' && UUID_REGEX.test(value)\n}\n\nfunction normalizeIdList(values: string[]): string[] {\n if (values.length === 0) return values\n const deduped = new Set<string>()\n for (const value of values) {\n const trimmed = value.trim()\n if (!trimmed || !isUuid(trimmed)) continue\n deduped.add(trimmed)\n }\n return Array.from(deduped)\n}\n\nfunction readExistingIds(filter: unknown): string[] | null {\n if (typeof filter === 'string') {\n return isUuid(filter) ? [filter] : null\n }\n if (Array.isArray(filter)) {\n return normalizeIdList(\n filter.filter((value): value is string => typeof value === 'string'),\n )\n }\n if (!filter || typeof filter !== 'object') return null\n\n const operators = filter as Record<string, unknown>\n if (isUuid(operators.$eq)) return [operators.$eq]\n\n if (Array.isArray(operators.$in)) {\n return normalizeIdList(\n operators.$in.filter((value): value is string => typeof value === 'string'),\n )\n }\n\n return null\n}\n\nexport function parseIdsParam(raw: unknown, maxIds: number = MAX_IDS_PER_REQUEST): string[] {\n const values = toQueryValueList(raw)\n if (values.length === 0) return []\n const safeMax = Number.isFinite(maxIds) && maxIds > 0 ? Math.floor(maxIds) : MAX_IDS_PER_REQUEST\n const parsed = normalizeIdList(values)\n return parsed.slice(0, safeMax)\n}\n\n/**\n * Whether an `?ids=` param was supplied at all (a non-empty string, or repeated\n * `?ids=` occurrences), regardless of whether any value survived UUID\n * validation. Lets a caller tell \"no ids filter requested\" apart from \"ids\n * filter requested but every value was malformed\" \u2014 the latter must match\n * nothing, not fall back to the full list (#4143 Finding 3).\n */\nexport function isIdsParamProvided(raw: unknown): boolean {\n const occurrences = Array.isArray(raw) ? raw : [raw]\n return occurrences.some((value) => typeof value === 'string' && value.trim().length > 0)\n}\n\nexport function mergeIdFilter<Fields extends Record<string, unknown>>(\n existingFilters: Where<Fields>,\n parsedIds: string[],\n options?: { idsParamProvided?: boolean },\n): Where<Fields> {\n if (parsedIds.length === 0) {\n // The `?ids=` param was supplied but nothing survived UUID validation.\n // Match nothing, mirroring a valid-but-unknown id returning zero rows,\n // rather than silently dropping the filter and returning the full list\n // (record-count side channel \u2014 #4143 Finding 3).\n if (options?.idsParamProvided) {\n return { ...existingFilters, id: { $in: [] } }\n }\n return existingFilters\n }\n\n const existingFilter = (existingFilters as Record<string, unknown>).id\n const existingIds = readExistingIds(existingFilter)\n\n if (!existingIds) {\n // No existing narrowing \u2014 safe to install the user-supplied `$in`.\n if (existingFilter === undefined || existingFilter === null) {\n return { ...existingFilters, id: { $in: parsedIds } }\n }\n // Existing `id` filter is in a shape we do not recognise. Fail closed:\n // preserve the existing filter instead of widening to `parsedIds`. Adding\n // a new recognised shape to `readExistingIds` is preferable to silently\n // dropping a narrowing that another caller put in place.\n return existingFilters\n }\n\n const allowed = new Set(parsedIds)\n const intersection = existingIds.filter((id) => allowed.has(id))\n return {\n ...existingFilters,\n id: { $in: intersection },\n }\n}\n"],
5
- "mappings": "AACA,SAAS,wBAAwB;AAE1B,MAAM,sBAAsB;AAEnC,MAAM,aAAa;AAEnB,SAAS,OAAO,OAAiC;AAC/C,SAAO,OAAO,UAAU,YAAY,WAAW,KAAK,KAAK;AAC3D;AAEA,SAAS,gBAAgB,QAA4B;AACnD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,SAAS,QAAQ;AAC1B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,WAAW,CAAC,OAAO,OAAO,EAAG;AAClC,YAAQ,IAAI,OAAO;AAAA,EACrB;AACA,SAAO,MAAM,KAAK,OAAO;AAC3B;AAEA,SAAS,gBAAgB,QAAkC;AACzD,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO,OAAO,MAAM,IAAI,CAAC,MAAM,IAAI;AAAA,EACrC;AACA,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO;AAAA,MACL,OAAO,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ;AAAA,IACrE;AAAA,EACF;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAElD,QAAM,YAAY;AAClB,MAAI,OAAO,UAAU,GAAG,EAAG,QAAO,CAAC,UAAU,GAAG;AAEhD,MAAI,MAAM,QAAQ,UAAU,GAAG,GAAG;AAChC,WAAO;AAAA,MACL,UAAU,IAAI,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ;AAAA,IAC5E;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,KAAc,SAAiB,qBAA+B;AAC1F,QAAM,SAAS,iBAAiB,GAAG;AACnC,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,QAAM,UAAU,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,KAAK,MAAM,MAAM,IAAI;AAC7E,QAAM,SAAS,gBAAgB,MAAM;AACrC,SAAO,OAAO,MAAM,GAAG,OAAO;AAChC;AASO,SAAS,mBAAmB,KAAuB;AACxD,QAAM,cAAc,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AACnD,SAAO,YAAY,KAAK,CAAC,UAAU,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,CAAC;AACzF;AAEO,SAAS,cACd,iBACA,WACA,SACe;AACf,MAAI,UAAU,WAAW,GAAG;AAK1B,QAAI,SAAS,kBAAkB;AAC7B,aAAO,EAAE,GAAG,iBAAiB,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAEA,QAAM,iBAAkB,gBAA4C;AACpE,QAAM,cAAc,gBAAgB,cAAc;AAElD,MAAI,CAAC,aAAa;AAEhB,QAAI,mBAAmB,UAAa,mBAAmB,MAAM;AAC3D,aAAO,EAAE,GAAG,iBAAiB,IAAI,EAAE,KAAK,UAAU,EAAE;AAAA,IACtD;AAKA,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,IAAI,IAAI,SAAS;AACjC,QAAM,eAAe,YAAY,OAAO,CAAC,OAAO,QAAQ,IAAI,EAAE,CAAC;AAC/D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI,EAAE,KAAK,aAAa;AAAA,EAC1B;AACF;",
4
+ "sourcesContent": ["import type { Where } from '@open-mercato/shared/lib/query/types'\nimport { toQueryValueList } from './query-params'\n\nexport const MAX_IDS_PER_REQUEST = 200\n\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nfunction isUuid(value: unknown): value is string {\n return typeof value === 'string' && UUID_REGEX.test(value)\n}\n\nfunction normalizeIdList(values: string[]): string[] {\n if (values.length === 0) return values\n const deduped = new Set<string>()\n for (const value of values) {\n const trimmed = value.trim()\n if (!trimmed || !isUuid(trimmed)) continue\n deduped.add(trimmed)\n }\n return Array.from(deduped)\n}\n\nfunction readExistingIds(filter: unknown): string[] | null {\n if (typeof filter === 'string') {\n return isUuid(filter) ? [filter] : null\n }\n if (Array.isArray(filter)) {\n return normalizeIdList(\n filter.filter((value): value is string => typeof value === 'string'),\n )\n }\n if (!filter || typeof filter !== 'object') return null\n\n const operators = filter as Record<string, unknown>\n if (isUuid(operators.$eq)) return [operators.$eq]\n\n if (Array.isArray(operators.$in)) {\n return normalizeIdList(\n operators.$in.filter((value): value is string => typeof value === 'string'),\n )\n }\n\n return null\n}\n\nexport function parseIdsParam(raw: unknown, maxIds: number = MAX_IDS_PER_REQUEST): string[] {\n const values = toQueryValueList(raw)\n if (values.length === 0) return []\n const safeMax = Number.isFinite(maxIds) && maxIds > 0 ? Math.floor(maxIds) : MAX_IDS_PER_REQUEST\n const parsed = normalizeIdList(values)\n return parsed.slice(0, safeMax)\n}\n\nconst UUID_SHAPE_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/**\n * Splits a comma-separated id list query param into its trimmed, non-empty entries.\n */\nexport function splitIdListParam(raw: unknown): string[] {\n if (typeof raw !== 'string') return []\n return raw\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n}\n\n/**\n * Whether every entry of a comma-separated id list query param is UUID-shaped, so it can be\n * bound to a uuid column without a database error. Use it to reject a malformed or mixed list\n * with a 400 instead of dropping the bad entries and widening the result set (#7265).\n */\nexport function isUuidListParam(raw: unknown): boolean {\n if (raw === undefined) return true\n if (typeof raw !== 'string') return false\n return splitIdListParam(raw).every((value) => UUID_SHAPE_REGEX.test(value))\n}\n\n/**\n * Whether an `?ids=` param was supplied at all (a non-empty string, or repeated\n * `?ids=` occurrences), regardless of whether any value survived UUID\n * validation. Lets a caller tell \"no ids filter requested\" apart from \"ids\n * filter requested but every value was malformed\" \u2014 the latter must match\n * nothing, not fall back to the full list (#4143 Finding 3).\n */\nexport function isIdsParamProvided(raw: unknown): boolean {\n const occurrences = Array.isArray(raw) ? raw : [raw]\n return occurrences.some((value) => typeof value === 'string' && value.trim().length > 0)\n}\n\nexport function mergeIdFilter<Fields extends Record<string, unknown>>(\n existingFilters: Where<Fields>,\n parsedIds: string[],\n options?: { idsParamProvided?: boolean },\n): Where<Fields> {\n if (parsedIds.length === 0) {\n // The `?ids=` param was supplied but nothing survived UUID validation.\n // Match nothing, mirroring a valid-but-unknown id returning zero rows,\n // rather than silently dropping the filter and returning the full list\n // (record-count side channel \u2014 #4143 Finding 3).\n if (options?.idsParamProvided) {\n return { ...existingFilters, id: { $in: [] } }\n }\n return existingFilters\n }\n\n const existingFilter = (existingFilters as Record<string, unknown>).id\n const existingIds = readExistingIds(existingFilter)\n\n if (!existingIds) {\n // No existing narrowing \u2014 safe to install the user-supplied `$in`.\n if (existingFilter === undefined || existingFilter === null) {\n return { ...existingFilters, id: { $in: parsedIds } }\n }\n // Existing `id` filter is in a shape we do not recognise. Fail closed:\n // preserve the existing filter instead of widening to `parsedIds`. Adding\n // a new recognised shape to `readExistingIds` is preferable to silently\n // dropping a narrowing that another caller put in place.\n return existingFilters\n }\n\n const allowed = new Set(parsedIds)\n const intersection = existingIds.filter((id) => allowed.has(id))\n return {\n ...existingFilters,\n id: { $in: intersection },\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }