@open-mercato/core 0.6.7-develop.6653.1.b5bc7194a0 → 0.6.7-develop.6660.1.90e1e2eef6

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 (42) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/business_rules/lib/value-resolver.js +7 -0
  3. package/dist/modules/business_rules/lib/value-resolver.js.map +2 -2
  4. package/dist/modules/currencies/commands/currencies.js +22 -8
  5. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  6. package/dist/modules/currencies/commands/exchange-rates.js +22 -8
  7. package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
  8. package/dist/modules/currencies/commands/scope.js +22 -0
  9. package/dist/modules/currencies/commands/scope.js.map +7 -0
  10. package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
  11. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  12. package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
  13. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
  14. package/dist/modules/feature_toggles/api/global/route.js +6 -0
  15. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  16. package/dist/modules/feature_toggles/commands/global.js +9 -9
  17. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  18. package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
  19. package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
  20. package/dist/modules/query_index/subscribers/delete_one.js +20 -6
  21. package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
  22. package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
  23. package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
  24. package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
  25. package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
  26. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
  27. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
  28. package/package.json +7 -7
  29. package/src/modules/business_rules/lib/value-resolver.ts +16 -0
  30. package/src/modules/currencies/commands/currencies.ts +26 -8
  31. package/src/modules/currencies/commands/exchange-rates.ts +26 -8
  32. package/src/modules/currencies/commands/scope.ts +32 -0
  33. package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
  34. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
  35. package/src/modules/feature_toggles/api/global/route.ts +7 -1
  36. package/src/modules/feature_toggles/commands/global.ts +9 -12
  37. package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
  38. package/src/modules/query_index/subscribers/delete_one.ts +21 -10
  39. package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
  40. package/src/modules/workflows/AGENTS.md +22 -0
  41. package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
  42. package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +7 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/commands/global.ts"],
4
- "sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { ToggleCreateInput, toggleCreateSchema, ToggleUpdateInput, toggleUpdateSchema } from '../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { FeatureTogglesService } from '../lib/feature-flag-check'\nimport { E } from '#generated/entities.ids.generated'\n\nfunction assertGlobalToggleSuperAdmin(ctx: { auth?: { [key: string]: unknown } | null; systemActor?: boolean }): void {\n // Trusted server-side callers (CLI seed-defaults/toggle-*, tenant setup) run\n // without an authenticated actor and opt in via `systemActor`. HTTP request\n // paths never set it and always carry a real `auth` actor, so an authenticated\n // but non-super-admin caller \u2014 the cross-tenant escalation vector (#2266) \u2014\n // stays denied.\n if (ctx.systemActor === true) return\n if (ctx.auth?.isSuperAdmin !== true) {\n throw new CrudHttpError(403, { error: 'Global feature toggles can only be managed by a super administrator.' })\n }\n}\n\ntype ToggleSnapshot = {\n id: string\n identifier: string\n name: string\n description: string | null\n category: string | null\n type: 'boolean' | 'string' | 'number' | 'json'\n defaultValue: any\n}\n\ntype OverrideSnapshot = {\n id: string\n toggleId: string\n tenantId: string\n value?: any\n}\n\ntype ToggleUndoPayload = {\n after?: ToggleSnapshot | null\n before?: ToggleSnapshot | null\n overrides?: OverrideSnapshot[]\n}\n\nconst featureToggleCrudIndexer = { entityType: E.feature_toggles.feature_toggle }\n\nconst FEATURE_TOGGLE_LOCK_RESOURCE_KIND = 'feature_toggles.feature_toggle'\n\nfunction featureToggleIdentifiers(\n toggle: FeatureToggle | ToggleSnapshot,\n ctx: { auth?: { tenantId?: string | null } | null },\n) {\n return {\n id: toggle.id,\n organizationId: null,\n tenantId: ctx.auth?.tenantId ?? null,\n }\n}\n\nasync function loadToggleSnapshot(em: EntityManager, id: string): Promise<ToggleSnapshot | null> {\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) return null\n return {\n id: toggle.id,\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n type: toggle.type ?? 'boolean',\n defaultValue: toggle.defaultValue ?? null,\n }\n}\n\nasync function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promise<OverrideSnapshot[]> {\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n return overrides.map(o => ({\n id: o.id,\n toggleId: o.toggle.id,\n tenantId: o.tenantId,\n value: o.value,\n }))\n}\n\nconst createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.create',\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleCreateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n const toggle = em.create(FeatureToggle, {\n identifier: parsed.identifier,\n name: parsed.name,\n description: parsed.description,\n category: parsed.category,\n type: parsed.type,\n defaultValue: parsed.defaultValue,\n })\n em.persist(toggle)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadToggleSnapshot(em, result.toggleId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadToggleSnapshot(em, result.toggleId)\n return {\n actionLabel: translate('feature_toggles.audit.toggles.create', 'Create toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: result.toggleId,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const toggleId = logEntry?.resourceId ?? null\n if (!toggleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n const toggle = await em.findOne(FeatureToggle, { id: toggleId })\n if (toggle) {\n em.remove(toggle)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<ToggleSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for toggle create' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: after.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: after.id,\n identifier: after.identifier,\n name: after.name,\n description: after.description,\n category: after.category,\n type: after.type,\n defaultValue: after.defaultValue,\n })\n em.persist(toggle)\n } else {\n toggle.deletedAt = null\n toggle.identifier = after.identifier\n toggle.name = after.name\n toggle.description = after.description\n toggle.category = after.category\n toggle.type = after.type\n toggle.defaultValue = after.defaultValue\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n}\n\nconst updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.update',\n async prepare(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id: parsed.id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const previousIdentifier = toggle.identifier\n if (parsed.identifier && parsed.identifier !== toggle.identifier) {\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing && existing.id !== toggle.id) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n }\n toggle.identifier = parsed.identifier ?? toggle.identifier\n toggle.name = parsed.name ?? toggle.name\n toggle.description = parsed.description ?? toggle.description\n toggle.category = parsed.category ?? toggle.category\n toggle.type = parsed.type ?? toggle.type\n toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n if (previousIdentifier !== toggle.identifier) {\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(previousIdentifier)\n }\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ToggleSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadToggleSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n [\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'failMode',\n 'type',\n 'defaultValue',\n ]\n )\n : {}\n\n return {\n actionLabel: translate('feature_toggles.audit.toggles.update', 'Update toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n}\n\nconst deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { toggleId: string }> =\n{\n id: 'feature_toggles.global.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, id)\n const overrides = await loadOverrideSnapshots(em, id)\n return snapshot ? { before: snapshot, overrides } : {}\n },\n async execute(input, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Feature toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n\n toggle.deletedAt = new Date()\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ToggleSnapshot | undefined\n const overrides = (snapshots as any).overrides as OverrideSnapshot[] | undefined\n\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('feature_toggles.audit.toggles.delete', 'Delete toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n overrides,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n const overrides = payload?.overrides || []\n\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n\n for (const ov of overrides) {\n const override = em.create(FeatureToggleOverride, {\n id: ov.id,\n toggle: toggle,\n tenantId: ov.tenantId,\n value: ov.value ?? null,\n })\n em.persist(override)\n }\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle, ctx),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n },\n}\n\nregisterCommand(createToggleCommand)\nregisterCommand(updateToggleCommand)\nregisterCommand(deleteToggleCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { ToggleCreateInput, toggleCreateSchema, ToggleUpdateInput, toggleUpdateSchema } from '../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { FeatureTogglesService } from '../lib/feature-flag-check'\nimport { E } from '#generated/entities.ids.generated'\n\nfunction assertGlobalToggleSuperAdmin(ctx: { auth?: { [key: string]: unknown } | null; systemActor?: boolean }): void {\n // Trusted server-side callers (CLI seed-defaults/toggle-*, tenant setup) run\n // without an authenticated actor and opt in via `systemActor`. HTTP request\n // paths never set it and always carry a real `auth` actor, so an authenticated\n // but non-super-admin caller \u2014 the cross-tenant escalation vector (#2266) \u2014\n // stays denied.\n if (ctx.systemActor === true) return\n if (ctx.auth?.isSuperAdmin !== true) {\n throw new CrudHttpError(403, { error: 'Global feature toggles can only be managed by a super administrator.' })\n }\n}\n\ntype ToggleSnapshot = {\n id: string\n identifier: string\n name: string\n description: string | null\n category: string | null\n type: 'boolean' | 'string' | 'number' | 'json'\n defaultValue: any\n}\n\ntype OverrideSnapshot = {\n id: string\n toggleId: string\n tenantId: string\n value?: any\n}\n\ntype ToggleUndoPayload = {\n after?: ToggleSnapshot | null\n before?: ToggleSnapshot | null\n overrides?: OverrideSnapshot[]\n}\n\nconst featureToggleCrudIndexer = { entityType: E.feature_toggles.feature_toggle }\n\nconst FEATURE_TOGGLE_LOCK_RESOURCE_KIND = 'feature_toggles.feature_toggle'\n\nfunction featureToggleIdentifiers(toggle: FeatureToggle | ToggleSnapshot) {\n return {\n id: toggle.id,\n organizationId: null,\n tenantId: null,\n }\n}\n\nasync function loadToggleSnapshot(em: EntityManager, id: string): Promise<ToggleSnapshot | null> {\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) return null\n return {\n id: toggle.id,\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n type: toggle.type ?? 'boolean',\n defaultValue: toggle.defaultValue ?? null,\n }\n}\n\nasync function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promise<OverrideSnapshot[]> {\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n return overrides.map(o => ({\n id: o.id,\n toggleId: o.toggle.id,\n tenantId: o.tenantId,\n value: o.value,\n }))\n}\n\nconst createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.create',\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleCreateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n const toggle = em.create(FeatureToggle, {\n identifier: parsed.identifier,\n name: parsed.name,\n description: parsed.description,\n category: parsed.category,\n type: parsed.type,\n defaultValue: parsed.defaultValue,\n })\n em.persist(toggle)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadToggleSnapshot(em, result.toggleId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadToggleSnapshot(em, result.toggleId)\n return {\n actionLabel: translate('feature_toggles.audit.toggles.create', 'Create toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: result.toggleId,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const toggleId = logEntry?.resourceId ?? null\n if (!toggleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n const toggle = await em.findOne(FeatureToggle, { id: toggleId })\n if (toggle) {\n em.remove(toggle)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<ToggleSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for toggle create' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: after.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: after.id,\n identifier: after.identifier,\n name: after.name,\n description: after.description,\n category: after.category,\n type: after.type,\n defaultValue: after.defaultValue,\n })\n em.persist(toggle)\n } else {\n toggle.deletedAt = null\n toggle.identifier = after.identifier\n toggle.name = after.name\n toggle.description = after.description\n toggle.category = after.category\n toggle.type = after.type\n toggle.defaultValue = after.defaultValue\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n}\n\nconst updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.update',\n async prepare(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id: parsed.id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const previousIdentifier = toggle.identifier\n if (parsed.identifier && parsed.identifier !== toggle.identifier) {\n const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier })\n if (existing && existing.id !== toggle.id) {\n throw new CrudHttpError(400, { error: 'Feature toggle identifier already exists' })\n }\n }\n toggle.identifier = parsed.identifier ?? toggle.identifier\n toggle.name = parsed.name ?? toggle.name\n toggle.description = parsed.description ?? toggle.description\n toggle.category = parsed.category ?? toggle.category\n toggle.type = parsed.type ?? toggle.type\n toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n if (previousIdentifier !== toggle.identifier) {\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(previousIdentifier)\n }\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ToggleSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadToggleSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n [\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'failMode',\n 'type',\n 'defaultValue',\n ]\n )\n : {}\n\n return {\n actionLabel: translate('feature_toggles.audit.toggles.update', 'Update toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n}\n\nconst deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { toggleId: string }> =\n{\n id: 'feature_toggles.global.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, id)\n const overrides = await loadOverrideSnapshots(em, id)\n return snapshot ? { before: snapshot, overrides } : {}\n },\n async execute(input, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id, deletedAt: null })\n if (!toggle) throw new CrudHttpError(404, { error: 'Feature toggle not found' })\n enforceCommandOptimisticLock({\n resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,\n resourceId: toggle.id,\n current: toggle.updatedAt ?? null,\n request: ctx.request ?? null,\n })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n\n toggle.deletedAt = new Date()\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ToggleSnapshot | undefined\n const overrides = (snapshots as any).overrides as OverrideSnapshot[] | undefined\n\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('feature_toggles.audit.toggles.delete', 'Delete toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n overrides,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n const overrides = payload?.overrides || []\n\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n\n for (const ov of overrides) {\n const override = em.create(FeatureToggleOverride, {\n id: ov.id,\n toggle: toggle,\n tenantId: ov.tenantId,\n value: ov.value ?? null,\n })\n em.persist(override)\n }\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n toggle.type = before.type\n toggle.defaultValue = before.defaultValue\n toggle.deletedAt = null\n }\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n await em.flush()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: toggle,\n identifiers: featureToggleIdentifiers(toggle),\n syncOrigin: ctx.syncOrigin,\n indexer: featureToggleCrudIndexer,\n })\n },\n}\n\nregisterCommand(createToggleCommand)\nregisterCommand(updateToggleCommand)\nregisterCommand(deleteToggleCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,20 +1,49 @@
1
- import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
1
+ import { getEntityIds } from "@open-mercato/shared/lib/encryption/entityIds";
2
+ import { resolveRegisteredEntityTableName } from "@open-mercato/shared/lib/query/engine";
2
3
  class QueryIndexScopeError extends Error {
3
4
  constructor(message) {
4
5
  super(message);
5
6
  this.name = "QueryIndexScopeError";
6
7
  }
7
8
  }
8
- async function loadQueryIndexRowScope(em, entityType, recordId) {
9
+ function resolveQueryIndexSourceMetadata(em, entityType) {
10
+ const registeredEntityIds = Object.values(getEntityIds(false)).flatMap((moduleEntities) => Object.values(moduleEntities ?? {}));
11
+ if (!registeredEntityIds.includes(entityType)) {
12
+ throw new QueryIndexScopeError(`Query index entity type is not registered: ${entityType}`);
13
+ }
14
+ const table = resolveRegisteredEntityTableName(em, entityType);
15
+ if (!table) {
16
+ throw new QueryIndexScopeError(`Query index entity type has no registered ORM table: ${entityType}`);
17
+ }
18
+ const registry = em.getMetadata?.();
19
+ const allMetadataRaw = typeof registry?.getAll === "function" ? registry.getAll() : null;
20
+ const allMetadata = Array.isArray(allMetadataRaw) ? allMetadataRaw : allMetadataRaw instanceof Map ? Array.from(allMetadataRaw.values()) : Object.values(allMetadataRaw ?? {});
21
+ const metadata = allMetadata.find((candidate) => String(candidate.tableName ?? "") === table);
22
+ if (!metadata) {
23
+ throw new QueryIndexScopeError(`Query index entity metadata was not found for table: ${table}`);
24
+ }
25
+ return {
26
+ table,
27
+ organizationColumn: resolveScopeColumn(metadata, "organizationId", table),
28
+ tenantColumn: resolveScopeColumn(metadata, "tenantId", table)
29
+ };
30
+ }
31
+ async function loadQueryIndexRowScope(em, source, recordId) {
32
+ if (!source.organizationColumn && !source.tenantColumn) {
33
+ return { kind: "global" };
34
+ }
9
35
  const db = em.getKysely();
10
- const table = resolveEntityTableName(em, entityType);
11
- const row = await db.selectFrom(table).select(["organization_id", "tenant_id"]).where("id", "=", recordId).executeTakeFirst();
36
+ const columns = [source.organizationColumn, source.tenantColumn].filter((column) => column !== null);
37
+ const row = await db.selectFrom(source.table).select(columns).where("id", "=", recordId).executeTakeFirst();
12
38
  if (!row) {
13
- return null;
39
+ return { kind: "missing" };
14
40
  }
15
41
  return {
16
- organizationId: row.organization_id ?? null,
17
- tenantId: row.tenant_id ?? null
42
+ kind: "row",
43
+ scope: {
44
+ organizationId: source.organizationColumn ? row[source.organizationColumn] ?? null : null,
45
+ tenantId: source.tenantColumn ? row[source.tenantColumn] ?? null : null
46
+ }
18
47
  };
19
48
  }
20
49
  function resolveQueryIndexRecordScope(input) {
@@ -23,9 +52,20 @@ function resolveQueryIndexRecordScope(input) {
23
52
  payloadOrganizationId,
24
53
  hasPayloadTenantId,
25
54
  hasPayloadOrganizationId,
26
- rowScope
55
+ sourceScope
27
56
  } = input;
28
- if (!rowScope) {
57
+ if (sourceScope.kind === "global") {
58
+ if (!hasPayloadTenantId || !hasPayloadOrganizationId || payloadTenantId !== null || payloadOrganizationId !== null) {
59
+ throw new QueryIndexScopeError(
60
+ "Query index event for a global entity must explicitly provide tenantId and organizationId as null"
61
+ );
62
+ }
63
+ return {
64
+ tenantId: null,
65
+ organizationId: null
66
+ };
67
+ }
68
+ if (sourceScope.kind === "missing") {
29
69
  if (!hasPayloadTenantId || !hasPayloadOrganizationId) {
30
70
  throw new QueryIndexScopeError(
31
71
  "Query index event is missing tenantId/organizationId and source row scope could not be resolved"
@@ -36,6 +76,7 @@ function resolveQueryIndexRecordScope(input) {
36
76
  organizationId: payloadOrganizationId ?? null
37
77
  };
38
78
  }
79
+ const rowScope = sourceScope.scope;
39
80
  if (hasPayloadTenantId && !isSameScopeValue(payloadTenantId, rowScope.tenantId)) {
40
81
  throw new QueryIndexScopeError(
41
82
  `Query index event tenantId does not match source row scope (payload=${String(payloadTenantId ?? null)}, row=${String(rowScope.tenantId)})`
@@ -65,10 +106,22 @@ function resolveQueryIndexReindexScope(input) {
65
106
  function isSameScopeValue(left, right) {
66
107
  return (left ?? null) === right;
67
108
  }
109
+ function resolveScopeColumn(metadata, property, table) {
110
+ const scopeProperty = metadata.properties?.[property];
111
+ if (!scopeProperty) return null;
112
+ const fieldNames = scopeProperty.fieldNames;
113
+ if (!Array.isArray(fieldNames) || fieldNames.length !== 1 || typeof fieldNames[0] !== "string" || !fieldNames[0]) {
114
+ throw new QueryIndexScopeError(
115
+ `Query index ${property} metadata must map exactly one physical column for table: ${table}`
116
+ );
117
+ }
118
+ return fieldNames[0];
119
+ }
68
120
  export {
69
121
  QueryIndexScopeError,
70
122
  loadQueryIndexRowScope,
71
123
  resolveQueryIndexRecordScope,
72
- resolveQueryIndexReindexScope
124
+ resolveQueryIndexReindexScope,
125
+ resolveQueryIndexSourceMetadata
73
126
  };
74
127
  //# sourceMappingURL=subscriber-scope.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/lib/subscriber-scope.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\n\nexport type QueryIndexScope = {\n tenantId: string | null\n organizationId: string | null\n}\n\nexport class QueryIndexScopeError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'QueryIndexScopeError'\n }\n}\n\ntype ResolveRecordScopeInput = {\n payloadTenantId: string | null | undefined\n payloadOrganizationId: string | null | undefined\n hasPayloadTenantId: boolean\n hasPayloadOrganizationId: boolean\n rowScope: QueryIndexScope | null\n}\n\ntype ResolveReindexScopeInput = {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n allowAllTenants?: boolean\n}\n\nexport async function loadQueryIndexRowScope(\n em: EntityManager,\n entityType: string,\n recordId: string\n): Promise<QueryIndexScope | null> {\n const db = em.getKysely<any>()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', recordId)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n\n if (!row) {\n return null\n }\n\n return {\n organizationId: row.organization_id ?? null,\n tenantId: row.tenant_id ?? null,\n }\n}\n\nexport function resolveQueryIndexRecordScope(input: ResolveRecordScopeInput): QueryIndexScope {\n const {\n payloadTenantId,\n payloadOrganizationId,\n hasPayloadTenantId,\n hasPayloadOrganizationId,\n rowScope,\n } = input\n\n if (!rowScope) {\n if (!hasPayloadTenantId || !hasPayloadOrganizationId) {\n throw new QueryIndexScopeError(\n 'Query index event is missing tenantId/organizationId and source row scope could not be resolved'\n )\n }\n\n return {\n tenantId: payloadTenantId ?? null,\n organizationId: payloadOrganizationId ?? null,\n }\n }\n\n if (hasPayloadTenantId && !isSameScopeValue(payloadTenantId, rowScope.tenantId)) {\n throw new QueryIndexScopeError(\n `Query index event tenantId does not match source row scope (payload=${String(payloadTenantId ?? null)}, row=${String(rowScope.tenantId)})`\n )\n }\n\n if (hasPayloadOrganizationId && !isSameScopeValue(payloadOrganizationId, rowScope.organizationId)) {\n throw new QueryIndexScopeError(\n `Query index event organizationId does not match source row scope (payload=${String(payloadOrganizationId ?? null)}, row=${String(rowScope.organizationId)})`\n )\n }\n\n return {\n tenantId: hasPayloadTenantId ? (payloadTenantId ?? null) : rowScope.tenantId,\n organizationId: hasPayloadOrganizationId ? (payloadOrganizationId ?? null) : rowScope.organizationId,\n }\n}\n\nexport function resolveQueryIndexReindexScope(input: ResolveReindexScopeInput): {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n} {\n if (input.tenantId === undefined && input.allowAllTenants !== true) {\n throw new QueryIndexScopeError(\n 'Query index reindex requires tenantId to be set explicitly; all-tenant reindex must opt in with allowAllTenants=true'\n )\n }\n\n return {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }\n}\n\nfunction isSameScopeValue(left: string | null | undefined, right: string | null): boolean {\n return (left ?? null) === right\n}\n"],
5
- "mappings": "AACA,SAAS,8BAA8B;AAOhC,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAgBA,eAAsB,uBACpB,IACA,YACA,UACiC;AACjC,QAAM,KAAK,GAAG,UAAe;AAC7B,QAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,QAAM,MAAM,MAAM,GACf,WAAW,KAAY,EACvB,OAAO,CAAC,mBAA0B,WAAkB,CAAC,EACrD,MAAM,MAAa,KAAK,QAAQ,EAChC,iBAAiB;AAEpB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,gBAAgB,IAAI,mBAAmB;AAAA,IACvC,UAAU,IAAI,aAAa;AAAA,EAC7B;AACF;AAEO,SAAS,6BAA6B,OAAiD;AAC5F,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI,CAAC,UAAU;AACb,QAAI,CAAC,sBAAsB,CAAC,0BAA0B;AACpD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,UAAU,mBAAmB;AAAA,MAC7B,gBAAgB,yBAAyB;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,sBAAsB,CAAC,iBAAiB,iBAAiB,SAAS,QAAQ,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,uEAAuE,OAAO,mBAAmB,IAAI,CAAC,SAAS,OAAO,SAAS,QAAQ,CAAC;AAAA,IAC1I;AAAA,EACF;AAEA,MAAI,4BAA4B,CAAC,iBAAiB,uBAAuB,SAAS,cAAc,GAAG;AACjG,UAAM,IAAI;AAAA,MACR,6EAA6E,OAAO,yBAAyB,IAAI,CAAC,SAAS,OAAO,SAAS,cAAc,CAAC;AAAA,IAC5J;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,qBAAsB,mBAAmB,OAAQ,SAAS;AAAA,IACpE,gBAAgB,2BAA4B,yBAAyB,OAAQ,SAAS;AAAA,EACxF;AACF;AAEO,SAAS,8BAA8B,OAG5C;AACA,MAAI,MAAM,aAAa,UAAa,MAAM,oBAAoB,MAAM;AAClE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,EACxB;AACF;AAEA,SAAS,iBAAiB,MAAiC,OAA+B;AACxF,UAAQ,QAAQ,UAAU;AAC5B;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { resolveRegisteredEntityTableName } from '@open-mercato/shared/lib/query/engine'\n\nexport type QueryIndexScope = {\n tenantId: string | null\n organizationId: string | null\n}\n\nexport type QueryIndexSourceMetadata = {\n table: string\n organizationColumn: string | null\n tenantColumn: string | null\n}\n\nexport type QueryIndexSourceScope =\n | { kind: 'global' }\n | { kind: 'missing' }\n | { kind: 'row'; scope: QueryIndexScope }\n\ntype QueryIndexEntityMetadata = {\n tableName?: unknown\n properties?: Record<string, { fieldNames?: unknown }>\n}\n\nexport class QueryIndexScopeError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'QueryIndexScopeError'\n }\n}\n\ntype ResolveRecordScopeInput = {\n payloadTenantId: string | null | undefined\n payloadOrganizationId: string | null | undefined\n hasPayloadTenantId: boolean\n hasPayloadOrganizationId: boolean\n sourceScope: QueryIndexSourceScope\n}\n\ntype ResolveReindexScopeInput = {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n allowAllTenants?: boolean\n}\n\nexport function resolveQueryIndexSourceMetadata(\n em: EntityManager,\n entityType: string,\n): QueryIndexSourceMetadata {\n const registeredEntityIds = Object.values(getEntityIds(false)).flatMap((moduleEntities) => Object.values(moduleEntities ?? {}))\n if (!registeredEntityIds.includes(entityType)) {\n throw new QueryIndexScopeError(`Query index entity type is not registered: ${entityType}`)\n }\n\n const table = resolveRegisteredEntityTableName(em, entityType as never)\n if (!table) {\n throw new QueryIndexScopeError(`Query index entity type has no registered ORM table: ${entityType}`)\n }\n\n const registry = em.getMetadata?.()\n const allMetadataRaw = typeof registry?.getAll === 'function' ? registry.getAll() : null\n const allMetadata: QueryIndexEntityMetadata[] = Array.isArray(allMetadataRaw)\n ? allMetadataRaw as QueryIndexEntityMetadata[]\n : allMetadataRaw instanceof Map\n ? Array.from(allMetadataRaw.values()) as QueryIndexEntityMetadata[]\n : Object.values(allMetadataRaw ?? {}) as QueryIndexEntityMetadata[]\n const metadata = allMetadata.find((candidate) => String(candidate.tableName ?? '') === table)\n if (!metadata) {\n throw new QueryIndexScopeError(`Query index entity metadata was not found for table: ${table}`)\n }\n\n return {\n table,\n organizationColumn: resolveScopeColumn(metadata, 'organizationId', table),\n tenantColumn: resolveScopeColumn(metadata, 'tenantId', table),\n }\n}\n\nexport async function loadQueryIndexRowScope(\n em: EntityManager,\n source: QueryIndexSourceMetadata,\n recordId: string,\n): Promise<QueryIndexSourceScope> {\n if (!source.organizationColumn && !source.tenantColumn) {\n return { kind: 'global' }\n }\n\n const db = em.getKysely<any>()\n const columns = [source.organizationColumn, source.tenantColumn].filter((column): column is string => column !== null)\n const row = await db\n .selectFrom(source.table as any)\n .select(columns as any)\n .where('id' as any, '=', recordId)\n .executeTakeFirst() as Record<string, string | null | undefined> | undefined\n\n if (!row) {\n return { kind: 'missing' }\n }\n\n return {\n kind: 'row',\n scope: {\n organizationId: source.organizationColumn ? row[source.organizationColumn] ?? null : null,\n tenantId: source.tenantColumn ? row[source.tenantColumn] ?? null : null,\n },\n }\n}\n\nexport function resolveQueryIndexRecordScope(input: ResolveRecordScopeInput): QueryIndexScope {\n const {\n payloadTenantId,\n payloadOrganizationId,\n hasPayloadTenantId,\n hasPayloadOrganizationId,\n sourceScope,\n } = input\n\n if (sourceScope.kind === 'global') {\n if (!hasPayloadTenantId || !hasPayloadOrganizationId || payloadTenantId !== null || payloadOrganizationId !== null) {\n throw new QueryIndexScopeError(\n 'Query index event for a global entity must explicitly provide tenantId and organizationId as null'\n )\n }\n\n return {\n tenantId: null,\n organizationId: null,\n }\n }\n\n if (sourceScope.kind === 'missing') {\n if (!hasPayloadTenantId || !hasPayloadOrganizationId) {\n throw new QueryIndexScopeError(\n 'Query index event is missing tenantId/organizationId and source row scope could not be resolved'\n )\n }\n\n return {\n tenantId: payloadTenantId ?? null,\n organizationId: payloadOrganizationId ?? null,\n }\n }\n\n const rowScope = sourceScope.scope\n\n if (hasPayloadTenantId && !isSameScopeValue(payloadTenantId, rowScope.tenantId)) {\n throw new QueryIndexScopeError(\n `Query index event tenantId does not match source row scope (payload=${String(payloadTenantId ?? null)}, row=${String(rowScope.tenantId)})`\n )\n }\n\n if (hasPayloadOrganizationId && !isSameScopeValue(payloadOrganizationId, rowScope.organizationId)) {\n throw new QueryIndexScopeError(\n `Query index event organizationId does not match source row scope (payload=${String(payloadOrganizationId ?? null)}, row=${String(rowScope.organizationId)})`\n )\n }\n\n return {\n tenantId: hasPayloadTenantId ? (payloadTenantId ?? null) : rowScope.tenantId,\n organizationId: hasPayloadOrganizationId ? (payloadOrganizationId ?? null) : rowScope.organizationId,\n }\n}\n\nexport function resolveQueryIndexReindexScope(input: ResolveReindexScopeInput): {\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n} {\n if (input.tenantId === undefined && input.allowAllTenants !== true) {\n throw new QueryIndexScopeError(\n 'Query index reindex requires tenantId to be set explicitly; all-tenant reindex must opt in with allowAllTenants=true'\n )\n }\n\n return {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n }\n}\n\nfunction isSameScopeValue(left: string | null | undefined, right: string | null): boolean {\n return (left ?? null) === right\n}\n\nfunction resolveScopeColumn(metadata: QueryIndexEntityMetadata, property: string, table: string): string | null {\n const scopeProperty = metadata.properties?.[property]\n if (!scopeProperty) return null\n const fieldNames = scopeProperty.fieldNames\n if (!Array.isArray(fieldNames) || fieldNames.length !== 1 || typeof fieldNames[0] !== 'string' || !fieldNames[0]) {\n throw new QueryIndexScopeError(\n `Query index ${property} metadata must map exactly one physical column for table: ${table}`\n )\n }\n return fieldNames[0]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,12 @@
1
1
  import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
2
- import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
3
2
  import { sql } from "kysely";
4
3
  import { markDeleted } from "../lib/indexer.js";
5
4
  import { applyCoverageAdjustments, createCoverageAdjustments } from "../lib/coverage.js";
6
- import { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from "../lib/subscriber-scope.js";
5
+ import {
6
+ loadQueryIndexRowScope,
7
+ resolveQueryIndexRecordScope,
8
+ resolveQueryIndexSourceMetadata
9
+ } from "../lib/subscriber-scope.js";
7
10
  const metadata = { event: "query_index.delete_one", persistent: false };
8
11
  const DELETE_COVERAGE_THROTTLE_MS = 5 * 60 * 1e3;
9
12
  const lastDeleteCoverageRefreshAt = /* @__PURE__ */ new Map();
@@ -28,13 +31,14 @@ async function handle(payload, ctx) {
28
31
  try {
29
32
  const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, "organizationId");
30
33
  const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, "tenantId");
31
- const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null);
34
+ const source = resolveQueryIndexSourceMetadata(em, entityType);
35
+ const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
32
36
  const resolvedScope = resolveQueryIndexRecordScope({
33
37
  payloadOrganizationId: payload?.organizationId,
34
38
  payloadTenantId: payload?.tenantId,
35
39
  hasPayloadOrganizationId,
36
40
  hasPayloadTenantId,
37
- rowScope
41
+ sourceScope
38
42
  });
39
43
  organizationId = resolvedScope.organizationId;
40
44
  tenantId = resolvedScope.tenantId;
@@ -43,8 +47,18 @@ async function handle(payload, ctx) {
43
47
  let baseCheckSucceeded = false;
44
48
  try {
45
49
  const db = em.getKysely();
46
- const table = resolveEntityTableName(em, entityType);
47
- const row = await db.selectFrom(table).select(["deleted_at"]).where("id", "=", recordId).where("organization_id", organizationId === null ? "is" : "=", organizationId).where(sql`tenant_id is not distinct from ${tenantId}`).executeTakeFirst();
50
+ let baseQuery = db.selectFrom(source.table).select(["deleted_at"]).where("id", "=", recordId);
51
+ if (source.organizationColumn) {
52
+ baseQuery = baseQuery.where(
53
+ source.organizationColumn,
54
+ organizationId === null ? "is" : "=",
55
+ organizationId
56
+ );
57
+ }
58
+ if (source.tenantColumn) {
59
+ baseQuery = baseQuery.where(sql`${sql.ref(source.tenantColumn)} is not distinct from ${tenantId}`);
60
+ }
61
+ const row = await baseQuery.executeTakeFirst();
48
62
  const baseMissing = !row;
49
63
  const baseDeleted = baseMissing || row && row.deleted_at != null;
50
64
  baseCheckSucceeded = true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/subscribers/delete_one.ts"],
4
- "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { sql } from 'kysely'\nimport { markDeleted } from '../lib/indexer'\nimport { applyCoverageAdjustments, createCoverageAdjustments } from '../lib/coverage'\nimport { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from '../lib/subscriber-scope'\n\nexport const metadata = { event: 'query_index.delete_one', persistent: false }\n\n// Mirrors `shouldTriggerCoverageRefresh()` in `@open-mercato/shared/lib/data/engine.ts`\n// as a small local throttle so per-record deletes stop firing an unconditional, immediate\n// full recount. Note: this Map and the shared engine's own throttle Map are independent,\n// so a rare race right after a 5-minute window resets on both could double-fire once \u2014\n// harmless (the recompute is idempotent) and far cheaper than today's per-delete cost.\nconst DELETE_COVERAGE_THROTTLE_MS = 5 * 60 * 1000\nconst lastDeleteCoverageRefreshAt = new Map<string, number>()\n\nfunction shouldAllowDeleteCoverageRefresh(entityType: string, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = lastDeleteCoverageRefreshAt.get(key) ?? 0\n if (now - last < DELETE_COVERAGE_THROTTLE_MS) return false\n lastDeleteCoverageRefreshAt.set(key, now)\n return true\n}\n\nexport default async function handle(payload: any, ctx: { resolve: <T=any>(name: string) => T }) {\n // Forked EntityManager \u2014 this awaited subscriber runs synchronously on the request\n // `em`; isolating it prevents our queries/writes from resetting the originating CRUD\n // write's UnitOfWork and dropping its pending changes. See upsert_one.ts for detail.\n const baseEm = ctx.resolve<any>('em')\n const em = typeof baseEm?.fork === 'function' ? baseEm.fork() : baseEm\n const entityType = String(payload?.entityType || '')\n const recordId = String(payload?.recordId || '')\n if (!entityType || !recordId) return\n let organizationId: string | null = payload?.organizationId ?? null\n let tenantId: string | null = payload?.tenantId ?? null\n const coverageDelayMs = typeof payload?.coverageDelayMs === 'number' ? payload.coverageDelayMs : undefined\n try {\n const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'organizationId')\n const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'tenantId')\n const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null)\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: payload?.organizationId,\n payloadTenantId: payload?.tenantId,\n hasPayloadOrganizationId,\n hasPayloadTenantId,\n rowScope,\n })\n organizationId = resolvedScope.organizationId\n tenantId = resolvedScope.tenantId\n\n const { wasActive } = await markDeleted(em, { entityType, recordId, organizationId, tenantId })\n\n let baseDelta = 0\n let baseCheckSucceeded = false\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['deleted_at' as any])\n .where('id' as any, '=', recordId)\n .where('organization_id' as any, organizationId === null ? 'is' : '=', organizationId as any)\n .where(sql`tenant_id is not distinct from ${tenantId}`)\n .executeTakeFirst() as { deleted_at: Date | null } | undefined\n const baseMissing = !row\n const baseDeleted = baseMissing || (row && row.deleted_at != null)\n baseCheckSucceeded = true\n if (baseDeleted) baseDelta = -1\n } catch {}\n if (!baseCheckSucceeded) baseDelta = -1\n\n const baseDeltaOverride =\n typeof payload?.coverageBaseDelta === 'number' ? payload.coverageBaseDelta : undefined\n const indexDeltaOverride =\n typeof payload?.coverageIndexDelta === 'number' ? payload.coverageIndexDelta : undefined\n let effectiveBaseDelta = baseDeltaOverride ?? baseDelta\n let effectiveIndexDelta = indexDeltaOverride ?? (wasActive ? -1 : 0)\n\n if (!Number.isFinite(effectiveBaseDelta)) effectiveBaseDelta = 0\n if (!Number.isFinite(effectiveIndexDelta)) effectiveIndexDelta = 0\n\n if (effectiveBaseDelta !== 0 || effectiveIndexDelta !== 0) {\n const adjustments = createCoverageAdjustments({\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n baseDelta: effectiveBaseDelta,\n indexDelta: effectiveIndexDelta,\n })\n if (adjustments.length) {\n await applyCoverageAdjustments(em, adjustments)\n }\n }\n\n // The projection row + token removal above are synchronous (the data engine\n // awaits this subscriber) so list reads are consistent immediately. The coverage\n // recompute (a COUNT, run inline when delayMs is 0) and the fulltext delete are\n // secondary, so defer them fire-and-forget to keep write/bulk-delete latency bounded.\n const suppressCoverage = payload?.suppressCoverage === true\n const explicitDelayRequested = typeof payload?.coverageDelayMs === 'number'\n const shouldRefreshCoverage =\n !suppressCoverage &&\n (coverageDelayMs === undefined || coverageDelayMs >= 0) &&\n (explicitDelayRequested || shouldAllowDeleteCoverageRefresh(entityType, tenantId))\n const coverageRefreshDelay = coverageDelayMs ?? 0\n void (async () => {\n try {\n const bus = ctx.resolve<any>('eventBus')\n if (shouldRefreshCoverage) {\n await bus.emitEvent('query_index.coverage.refresh', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n delayMs: coverageRefreshDelay,\n })\n }\n await bus.emitEvent('search.delete_record', { entityId: entityType, recordId, organizationId, tenantId })\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one:coverage_search',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n ).catch(() => {})\n }\n })()\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n )\n throw error\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { sql } from 'kysely'\nimport { markDeleted } from '../lib/indexer'\nimport { applyCoverageAdjustments, createCoverageAdjustments } from '../lib/coverage'\nimport {\n loadQueryIndexRowScope,\n resolveQueryIndexRecordScope,\n resolveQueryIndexSourceMetadata,\n} from '../lib/subscriber-scope'\n\nexport const metadata = { event: 'query_index.delete_one', persistent: false }\n\n// Mirrors `shouldTriggerCoverageRefresh()` in `@open-mercato/shared/lib/data/engine.ts`\n// as a small local throttle so per-record deletes stop firing an unconditional, immediate\n// full recount. Note: this Map and the shared engine's own throttle Map are independent,\n// so a rare race right after a 5-minute window resets on both could double-fire once \u2014\n// harmless (the recompute is idempotent) and far cheaper than today's per-delete cost.\nconst DELETE_COVERAGE_THROTTLE_MS = 5 * 60 * 1000\nconst lastDeleteCoverageRefreshAt = new Map<string, number>()\n\nfunction shouldAllowDeleteCoverageRefresh(entityType: string, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = lastDeleteCoverageRefreshAt.get(key) ?? 0\n if (now - last < DELETE_COVERAGE_THROTTLE_MS) return false\n lastDeleteCoverageRefreshAt.set(key, now)\n return true\n}\n\nexport default async function handle(payload: any, ctx: { resolve: <T=any>(name: string) => T }) {\n // Forked EntityManager \u2014 this awaited subscriber runs synchronously on the request\n // `em`; isolating it prevents our queries/writes from resetting the originating CRUD\n // write's UnitOfWork and dropping its pending changes. See upsert_one.ts for detail.\n const baseEm = ctx.resolve<any>('em')\n const em = typeof baseEm?.fork === 'function' ? baseEm.fork() : baseEm\n const entityType = String(payload?.entityType || '')\n const recordId = String(payload?.recordId || '')\n if (!entityType || !recordId) return\n let organizationId: string | null = payload?.organizationId ?? null\n let tenantId: string | null = payload?.tenantId ?? null\n const coverageDelayMs = typeof payload?.coverageDelayMs === 'number' ? payload.coverageDelayMs : undefined\n try {\n const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'organizationId')\n const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'tenantId')\n const source = resolveQueryIndexSourceMetadata(em, entityType)\n const sourceScope = await loadQueryIndexRowScope(em, source, recordId)\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: payload?.organizationId,\n payloadTenantId: payload?.tenantId,\n hasPayloadOrganizationId,\n hasPayloadTenantId,\n sourceScope,\n })\n organizationId = resolvedScope.organizationId\n tenantId = resolvedScope.tenantId\n\n const { wasActive } = await markDeleted(em, { entityType, recordId, organizationId, tenantId })\n\n let baseDelta = 0\n let baseCheckSucceeded = false\n try {\n const db = (em as any).getKysely()\n let baseQuery = db\n .selectFrom(source.table as any)\n .select(['deleted_at' as any])\n .where('id' as any, '=', recordId)\n if (source.organizationColumn) {\n baseQuery = baseQuery.where(\n source.organizationColumn as any,\n organizationId === null ? 'is' : '=',\n organizationId as any,\n )\n }\n if (source.tenantColumn) {\n baseQuery = baseQuery.where(sql`${sql.ref(source.tenantColumn)} is not distinct from ${tenantId}`)\n }\n const row = await baseQuery.executeTakeFirst() as { deleted_at: Date | null } | undefined\n const baseMissing = !row\n const baseDeleted = baseMissing || (row && row.deleted_at != null)\n baseCheckSucceeded = true\n if (baseDeleted) baseDelta = -1\n } catch {}\n if (!baseCheckSucceeded) baseDelta = -1\n\n const baseDeltaOverride =\n typeof payload?.coverageBaseDelta === 'number' ? payload.coverageBaseDelta : undefined\n const indexDeltaOverride =\n typeof payload?.coverageIndexDelta === 'number' ? payload.coverageIndexDelta : undefined\n let effectiveBaseDelta = baseDeltaOverride ?? baseDelta\n let effectiveIndexDelta = indexDeltaOverride ?? (wasActive ? -1 : 0)\n\n if (!Number.isFinite(effectiveBaseDelta)) effectiveBaseDelta = 0\n if (!Number.isFinite(effectiveIndexDelta)) effectiveIndexDelta = 0\n\n if (effectiveBaseDelta !== 0 || effectiveIndexDelta !== 0) {\n const adjustments = createCoverageAdjustments({\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n baseDelta: effectiveBaseDelta,\n indexDelta: effectiveIndexDelta,\n })\n if (adjustments.length) {\n await applyCoverageAdjustments(em, adjustments)\n }\n }\n\n // The projection row + token removal above are synchronous (the data engine\n // awaits this subscriber) so list reads are consistent immediately. The coverage\n // recompute (a COUNT, run inline when delayMs is 0) and the fulltext delete are\n // secondary, so defer them fire-and-forget to keep write/bulk-delete latency bounded.\n const suppressCoverage = payload?.suppressCoverage === true\n const explicitDelayRequested = typeof payload?.coverageDelayMs === 'number'\n const shouldRefreshCoverage =\n !suppressCoverage &&\n (coverageDelayMs === undefined || coverageDelayMs >= 0) &&\n (explicitDelayRequested || shouldAllowDeleteCoverageRefresh(entityType, tenantId))\n const coverageRefreshDelay = coverageDelayMs ?? 0\n void (async () => {\n try {\n const bus = ctx.resolve<any>('eventBus')\n if (shouldRefreshCoverage) {\n await bus.emitEvent('query_index.coverage.refresh', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n delayMs: coverageRefreshDelay,\n })\n }\n await bus.emitEvent('search.delete_record', { entityId: entityType, recordId, organizationId, tenantId })\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one:coverage_search',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n ).catch(() => {})\n }\n })()\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.delete_one',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n )\n throw error\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,11 @@
1
1
  import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
2
2
  import { upsertIndexRow, reindexSearchTokensForRecord } from "../lib/indexer.js";
3
3
  import { applyCoverageAdjustments, createCoverageAdjustments } from "../lib/coverage.js";
4
- import { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from "../lib/subscriber-scope.js";
4
+ import {
5
+ loadQueryIndexRowScope,
6
+ resolveQueryIndexRecordScope,
7
+ resolveQueryIndexSourceMetadata
8
+ } from "../lib/subscriber-scope.js";
5
9
  const metadata = { event: "query_index.upsert_one", persistent: false };
6
10
  async function handle(payload, ctx) {
7
11
  const baseEm = ctx.resolve("em");
@@ -16,13 +20,14 @@ async function handle(payload, ctx) {
16
20
  try {
17
21
  const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, "organizationId");
18
22
  const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, "tenantId");
19
- const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null);
23
+ const source = resolveQueryIndexSourceMetadata(em, entityType);
24
+ const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
20
25
  const resolvedScope = resolveQueryIndexRecordScope({
21
26
  payloadOrganizationId: payload?.organizationId,
22
27
  payloadTenantId: payload?.tenantId,
23
28
  hasPayloadOrganizationId,
24
29
  hasPayloadTenantId,
25
- rowScope
30
+ sourceScope
26
31
  });
27
32
  organizationId = resolvedScope.organizationId;
28
33
  tenantId = resolvedScope.tenantId;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/subscribers/upsert_one.ts"],
4
- "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { upsertIndexRow, reindexSearchTokensForRecord } from '../lib/indexer'\nimport { applyCoverageAdjustments, createCoverageAdjustments } from '../lib/coverage'\nimport { loadQueryIndexRowScope, resolveQueryIndexRecordScope } from '../lib/subscriber-scope'\n\nexport const metadata = { event: 'query_index.upsert_one', persistent: false }\n\nexport default async function handle(payload: any, ctx: { resolve: <T=any>(name: string) => T }) {\n // Run index maintenance on a FORKED EntityManager (fresh identity map + UnitOfWork)\n // so it can never disturb the originating CRUD write's `em`. The data engine awaits\n // this emit for read-your-writes consistency, which means the subscriber runs\n // synchronously on the request `em`; sharing it would let our `em.find` / raw\n // `getKysely()` queries reset the caller's UoW change-tracking and silently drop the\n // caller's pending write (e.g. the deal's `setCustomFields` insert). The fork reads\n // the same committed DB rows via the shared connection but keeps its own UoW.\n const baseEm = ctx.resolve<any>('em')\n const em = typeof baseEm?.fork === 'function' ? baseEm.fork() : baseEm\n const entityType = String(payload?.entityType || '')\n const recordId = String(payload?.recordId || '')\n if (!entityType || !recordId) return\n let organizationId: string | null = payload?.organizationId ?? null\n let tenantId: string | null = payload?.tenantId ?? null\n const suppressCoverage = payload?.suppressCoverage === true\n const coverageDelayMs = typeof payload?.coverageDelayMs === 'number' ? payload.coverageDelayMs : undefined\n try {\n const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'organizationId')\n const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'tenantId')\n const rowScope = await loadQueryIndexRowScope(em, entityType, recordId).catch(() => null)\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: payload?.organizationId,\n payloadTenantId: payload?.tenantId,\n hasPayloadOrganizationId,\n hasPayloadTenantId,\n rowScope,\n })\n organizationId = resolvedScope.organizationId\n tenantId = resolvedScope.tenantId\n\n const searchTokenDoc = typeof payload?.searchTokenDoc === 'object' && payload.searchTokenDoc && !Array.isArray(payload.searchTokenDoc)\n ? (payload.searchTokenDoc as Record<string, unknown>)\n : null\n // Update the projection row synchronously so list reads (`customValues`) are\n // consistent the moment the write returns; defer the heavy search-token rebuild.\n const result = await upsertIndexRow(em, {\n entityType,\n recordId,\n organizationId,\n tenantId,\n searchTokenDoc,\n deferSearchTokens: true,\n })\n if (!suppressCoverage) {\n const doc = result.doc\n const isActive = !!doc && (doc.deleted_at == null || doc.deleted_at === null)\n let baseDelta: number | undefined =\n typeof payload?.coverageBaseDelta === 'number' ? payload.coverageBaseDelta : undefined\n let indexDelta: number | undefined =\n typeof payload?.coverageIndexDelta === 'number' ? payload.coverageIndexDelta : undefined\n const crudAction = typeof payload?.crudAction === 'string' ? payload.crudAction : undefined\n\n if (baseDelta === undefined) {\n if (result.revived) baseDelta = 1\n else if (crudAction === 'created') baseDelta = 1\n else baseDelta = 0\n }\n\n if (indexDelta === undefined) {\n if (isActive && (result.created || result.revived)) indexDelta = 1\n else indexDelta = 0\n }\n\n if (!isActive && baseDelta > 0) baseDelta = 0\n if (!isActive && indexDelta > 0) indexDelta = 0\n if (!Number.isFinite(baseDelta)) baseDelta = 0\n if (!Number.isFinite(indexDelta)) indexDelta = 0\n\n const adjustments = createCoverageAdjustments({\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n baseDelta,\n indexDelta,\n })\n if (adjustments.length) {\n await applyCoverageAdjustments(em, adjustments)\n }\n if (coverageDelayMs !== undefined && coverageDelayMs >= 0) {\n try {\n const bus = ctx.resolve<any>('eventBus')\n await bus.emitEvent('query_index.coverage.refresh', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n delayMs: coverageDelayMs,\n })\n } catch {}\n }\n }\n // Defer the heavy, eventually-consistent tail: search-token rebuild + vectorize +\n // fulltext indexing. The data engine awaits this subscriber for projection\n // consistency, so this work runs fire-and-forget to keep write latency bounded.\n const deferredScope = { entityType, recordId, organizationId, tenantId }\n const resolvedDoc = result.doc\n void (async () => {\n try {\n await reindexSearchTokensForRecord(em, { ...deferredScope, doc: resolvedDoc, searchTokenDoc })\n const bus = ctx.resolve<any>('eventBus')\n await bus.emitEvent('query_index.vectorize_one', deferredScope)\n await bus.emitEvent('search.index_record', { entityId: entityType, recordId, organizationId, tenantId })\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.upsert_one:search_tokens',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n ).catch(() => {})\n }\n })()\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.upsert_one',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n )\n throw error\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { upsertIndexRow, reindexSearchTokensForRecord } from '../lib/indexer'\nimport { applyCoverageAdjustments, createCoverageAdjustments } from '../lib/coverage'\nimport {\n loadQueryIndexRowScope,\n resolveQueryIndexRecordScope,\n resolveQueryIndexSourceMetadata,\n} from '../lib/subscriber-scope'\n\nexport const metadata = { event: 'query_index.upsert_one', persistent: false }\n\nexport default async function handle(payload: any, ctx: { resolve: <T=any>(name: string) => T }) {\n // Run index maintenance on a FORKED EntityManager (fresh identity map + UnitOfWork)\n // so it can never disturb the originating CRUD write's `em`. The data engine awaits\n // this emit for read-your-writes consistency, which means the subscriber runs\n // synchronously on the request `em`; sharing it would let our `em.find` / raw\n // `getKysely()` queries reset the caller's UoW change-tracking and silently drop the\n // caller's pending write (e.g. the deal's `setCustomFields` insert). The fork reads\n // the same committed DB rows via the shared connection but keeps its own UoW.\n const baseEm = ctx.resolve<any>('em')\n const em = typeof baseEm?.fork === 'function' ? baseEm.fork() : baseEm\n const entityType = String(payload?.entityType || '')\n const recordId = String(payload?.recordId || '')\n if (!entityType || !recordId) return\n let organizationId: string | null = payload?.organizationId ?? null\n let tenantId: string | null = payload?.tenantId ?? null\n const suppressCoverage = payload?.suppressCoverage === true\n const coverageDelayMs = typeof payload?.coverageDelayMs === 'number' ? payload.coverageDelayMs : undefined\n try {\n const hasPayloadOrganizationId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'organizationId')\n const hasPayloadTenantId = Object.prototype.hasOwnProperty.call(payload ?? {}, 'tenantId')\n const source = resolveQueryIndexSourceMetadata(em, entityType)\n const sourceScope = await loadQueryIndexRowScope(em, source, recordId)\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: payload?.organizationId,\n payloadTenantId: payload?.tenantId,\n hasPayloadOrganizationId,\n hasPayloadTenantId,\n sourceScope,\n })\n organizationId = resolvedScope.organizationId\n tenantId = resolvedScope.tenantId\n\n const searchTokenDoc = typeof payload?.searchTokenDoc === 'object' && payload.searchTokenDoc && !Array.isArray(payload.searchTokenDoc)\n ? (payload.searchTokenDoc as Record<string, unknown>)\n : null\n // Update the projection row synchronously so list reads (`customValues`) are\n // consistent the moment the write returns; defer the heavy search-token rebuild.\n const result = await upsertIndexRow(em, {\n entityType,\n recordId,\n organizationId,\n tenantId,\n searchTokenDoc,\n deferSearchTokens: true,\n })\n if (!suppressCoverage) {\n const doc = result.doc\n const isActive = !!doc && (doc.deleted_at == null || doc.deleted_at === null)\n let baseDelta: number | undefined =\n typeof payload?.coverageBaseDelta === 'number' ? payload.coverageBaseDelta : undefined\n let indexDelta: number | undefined =\n typeof payload?.coverageIndexDelta === 'number' ? payload.coverageIndexDelta : undefined\n const crudAction = typeof payload?.crudAction === 'string' ? payload.crudAction : undefined\n\n if (baseDelta === undefined) {\n if (result.revived) baseDelta = 1\n else if (crudAction === 'created') baseDelta = 1\n else baseDelta = 0\n }\n\n if (indexDelta === undefined) {\n if (isActive && (result.created || result.revived)) indexDelta = 1\n else indexDelta = 0\n }\n\n if (!isActive && baseDelta > 0) baseDelta = 0\n if (!isActive && indexDelta > 0) indexDelta = 0\n if (!Number.isFinite(baseDelta)) baseDelta = 0\n if (!Number.isFinite(indexDelta)) indexDelta = 0\n\n const adjustments = createCoverageAdjustments({\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n baseDelta,\n indexDelta,\n })\n if (adjustments.length) {\n await applyCoverageAdjustments(em, adjustments)\n }\n if (coverageDelayMs !== undefined && coverageDelayMs >= 0) {\n try {\n const bus = ctx.resolve<any>('eventBus')\n await bus.emitEvent('query_index.coverage.refresh', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n delayMs: coverageDelayMs,\n })\n } catch {}\n }\n }\n // Defer the heavy, eventually-consistent tail: search-token rebuild + vectorize +\n // fulltext indexing. The data engine awaits this subscriber for projection\n // consistency, so this work runs fire-and-forget to keep write latency bounded.\n const deferredScope = { entityType, recordId, organizationId, tenantId }\n const resolvedDoc = result.doc\n void (async () => {\n try {\n await reindexSearchTokensForRecord(em, { ...deferredScope, doc: resolvedDoc, searchTokenDoc })\n const bus = ctx.resolve<any>('eventBus')\n await bus.emitEvent('query_index.vectorize_one', deferredScope)\n await bus.emitEvent('search.index_record', { entityId: entityType, recordId, organizationId, tenantId })\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.upsert_one:search_tokens',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n ).catch(() => {})\n }\n })()\n } catch (error) {\n await recordIndexerError(\n { em },\n {\n source: 'query_index',\n handler: 'event:query_index.upsert_one',\n error,\n entityType,\n recordId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload,\n },\n )\n throw error\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from "@op
7
7
  import { WorkflowDefinition } from "../../../../data/entities.js";
8
8
  import { serializeWorkflowDefinition } from "../../serialize.js";
9
9
  import { getCodeWorkflow } from "../../../../lib/code-registry.js";
10
+ import { invalidateTriggerCache } from "../../../../lib/event-trigger-service.js";
10
11
  import { createLogger } from "@open-mercato/shared/lib/logger";
11
12
  const logger = createLogger("workflows");
12
13
  const metadata = {
@@ -88,6 +89,7 @@ async function POST(request, context) {
88
89
  await em.flush();
89
90
  saved = override;
90
91
  }
92
+ if (saved.tenantId) invalidateTriggerCache(saved.tenantId, saved.organizationId ?? void 0);
91
93
  if (guardResult?.shouldRunAfterSuccess) {
92
94
  await runCrudMutationGuardAfterSuccess(container, {
93
95
  tenantId: tenantId ?? "",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/workflows/api/definitions/%5Bid%5D/customize/route.ts"],
4
- "sourcesContent": ["/**\n * Customize Code-Based Workflow Definition\n *\n * POST /api/workflows/definitions/[id]/customize\n *\n * Creates a DB override row for a code-based workflow definition, seeded from\n * the current code registry values. The id param must be of the form\n * \"code:<workflowId>\".\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { WorkflowDefinition } from '../../../../data/entities'\nimport { serializeWorkflowDefinition } from '../../serialize'\nimport { getCodeWorkflow } from '../../../../lib/code-registry'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('workflows')\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.edit'],\n}\n\ninterface RouteContext {\n params: Promise<{\n id: string\n }>\n}\n\nexport async function POST(request: NextRequest, context: RouteContext) {\n try {\n const params = await context.params\n const container = await createRequestContainer()\n const em = container.resolve('em')\n const auth = await getAuthFromRequest(request)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const tenantId = auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId\n\n if (!params.id.startsWith('code:')) {\n return NextResponse.json(\n { error: 'Customize is only supported for code-based workflow definitions' },\n { status: 400 },\n )\n }\n\n const workflowId = params.id.slice(5)\n const codeDef = getCodeWorkflow(workflowId)\n if (!codeDef) {\n return NextResponse.json({ error: 'Workflow definition not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: params.id,\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const existingOverride = await em.findOne(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n tenantId,\n })\n\n let saved: WorkflowDefinition\n if (existingOverride) {\n existingOverride.deletedAt = null\n existingOverride.workflowName = codeDef.workflowName\n existingOverride.description = codeDef.description\n existingOverride.version = codeDef.version\n existingOverride.definition = codeDef.definition\n existingOverride.metadata = codeDef.metadata ?? null\n existingOverride.enabled = codeDef.enabled\n existingOverride.codeWorkflowId = codeDef.workflowId\n existingOverride.updatedBy = auth.sub\n existingOverride.updatedAt = new Date()\n await em.flush()\n saved = existingOverride\n } else {\n const override = em.create(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n workflowName: codeDef.workflowName,\n description: codeDef.description,\n version: codeDef.version,\n definition: codeDef.definition,\n metadata: codeDef.metadata,\n enabled: codeDef.enabled,\n codeWorkflowId: codeDef.workflowId,\n tenantId,\n organizationId,\n createdBy: auth.sub,\n updatedBy: auth.sub,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(override)\n await em.flush()\n saved = override\n }\n\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: String(saved.id),\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n metadata: guardResult.metadata,\n })\n }\n\n try {\n const eventBus = container.resolve('eventBus') as\n | { emitEvent(event: string, payload: unknown, options?: unknown): Promise<void> }\n | undefined\n if (eventBus && typeof eventBus.emitEvent === 'function') {\n await eventBus.emitEvent(\n 'workflows.definition.customized',\n {\n id: saved.id,\n workflowId: saved.workflowId,\n codeWorkflowId: saved.codeWorkflowId ?? null,\n tenantId: saved.tenantId,\n organizationId: saved.organizationId,\n userId: auth.sub ?? null,\n },\n { tenantId: saved.tenantId, organizationId: saved.organizationId, persistent: true },\n )\n }\n } catch (eventError) {\n logger.error('Failed to emit workflows.definition.customized event', { err: eventError })\n }\n\n return NextResponse.json({\n data: serializeWorkflowDefinition(saved),\n message: 'Workflow definition customized successfully',\n })\n } catch (error) {\n logger.error('Error customizing workflow definition', { err: error })\n return NextResponse.json({ error: 'Failed to customize workflow definition' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n summary: 'Customize code-based workflow definition',\n description: 'Creates a DB override for a code-based workflow definition, seeded from the current code registry values. The id param must be of the form \"code:<workflowId>\".',\n tags: ['Workflows'],\n pathParams: z.object({\n id: z.string().describe('Must be of the form \"code:<workflowId>\"'),\n }),\n responses: [\n {\n status: 200,\n description: 'Workflow definition customized successfully',\n example: {\n data: {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n source: 'code_override',\n },\n message: 'Workflow definition customized successfully',\n },\n },\n {\n status: 400,\n description: 'Not a code-based id',\n example: { error: 'Customize is only supported for code-based workflow definitions' },\n },\n {\n status: 404,\n description: 'Code workflow not found',\n example: { error: 'Workflow definition not found' },\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Customize Code-Based Workflow Definition\n *\n * POST /api/workflows/definitions/[id]/customize\n *\n * Creates a DB override row for a code-based workflow definition, seeded from\n * the current code registry values. The id param must be of the form\n * \"code:<workflowId>\".\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { WorkflowDefinition } from '../../../../data/entities'\nimport { serializeWorkflowDefinition } from '../../serialize'\nimport { getCodeWorkflow } from '../../../../lib/code-registry'\nimport { invalidateTriggerCache } from '../../../../lib/event-trigger-service'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('workflows')\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.edit'],\n}\n\ninterface RouteContext {\n params: Promise<{\n id: string\n }>\n}\n\nexport async function POST(request: NextRequest, context: RouteContext) {\n try {\n const params = await context.params\n const container = await createRequestContainer()\n const em = container.resolve('em')\n const auth = await getAuthFromRequest(request)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const tenantId = auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId\n\n if (!params.id.startsWith('code:')) {\n return NextResponse.json(\n { error: 'Customize is only supported for code-based workflow definitions' },\n { status: 400 },\n )\n }\n\n const workflowId = params.id.slice(5)\n const codeDef = getCodeWorkflow(workflowId)\n if (!codeDef) {\n return NextResponse.json({ error: 'Workflow definition not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: params.id,\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const existingOverride = await em.findOne(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n tenantId,\n })\n\n let saved: WorkflowDefinition\n if (existingOverride) {\n existingOverride.deletedAt = null\n existingOverride.workflowName = codeDef.workflowName\n existingOverride.description = codeDef.description\n existingOverride.version = codeDef.version\n existingOverride.definition = codeDef.definition\n existingOverride.metadata = codeDef.metadata ?? null\n existingOverride.enabled = codeDef.enabled\n existingOverride.codeWorkflowId = codeDef.workflowId\n existingOverride.updatedBy = auth.sub\n existingOverride.updatedAt = new Date()\n await em.flush()\n saved = existingOverride\n } else {\n const override = em.create(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n workflowName: codeDef.workflowName,\n description: codeDef.description,\n version: codeDef.version,\n definition: codeDef.definition,\n metadata: codeDef.metadata,\n enabled: codeDef.enabled,\n codeWorkflowId: codeDef.workflowId,\n tenantId,\n organizationId,\n createdBy: auth.sub,\n updatedBy: auth.sub,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(override)\n await em.flush()\n saved = override\n }\n\n // Materializing the override moves trigger ownership from the code registry\n // to the new `workflow_definitions` row, so the cached snapshot the wildcard\n // subscriber reads is stale until TRIGGER_CACHE_TTL expires (#4425). Scope\n // the invalidation to the saved row's own organization: reviving an existing\n // override belonging to a sibling organization changes triggers for THAT\n // organization, not for the caller's.\n if (saved.tenantId) invalidateTriggerCache(saved.tenantId, saved.organizationId ?? undefined)\n\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: String(saved.id),\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n metadata: guardResult.metadata,\n })\n }\n\n try {\n const eventBus = container.resolve('eventBus') as\n | { emitEvent(event: string, payload: unknown, options?: unknown): Promise<void> }\n | undefined\n if (eventBus && typeof eventBus.emitEvent === 'function') {\n await eventBus.emitEvent(\n 'workflows.definition.customized',\n {\n id: saved.id,\n workflowId: saved.workflowId,\n codeWorkflowId: saved.codeWorkflowId ?? null,\n tenantId: saved.tenantId,\n organizationId: saved.organizationId,\n userId: auth.sub ?? null,\n },\n { tenantId: saved.tenantId, organizationId: saved.organizationId, persistent: true },\n )\n }\n } catch (eventError) {\n logger.error('Failed to emit workflows.definition.customized event', { err: eventError })\n }\n\n return NextResponse.json({\n data: serializeWorkflowDefinition(saved),\n message: 'Workflow definition customized successfully',\n })\n } catch (error) {\n logger.error('Error customizing workflow definition', { err: error })\n return NextResponse.json({ error: 'Failed to customize workflow definition' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n summary: 'Customize code-based workflow definition',\n description: 'Creates a DB override for a code-based workflow definition, seeded from the current code registry values. The id param must be of the form \"code:<workflowId>\".',\n tags: ['Workflows'],\n pathParams: z.object({\n id: z.string().describe('Must be of the form \"code:<workflowId>\"'),\n }),\n responses: [\n {\n status: 200,\n description: 'Workflow definition customized successfully',\n example: {\n data: {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n source: 'code_override',\n },\n message: 'Workflow definition customized successfully',\n },\n },\n {\n status: 400,\n description: 'Not a code-based id',\n example: { error: 'Customize is only supported for code-based workflow definitions' },\n },\n {\n status: 404,\n description: 'Code workflow not found',\n example: { error: 'Workflow definition not found' },\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAUA,SAAsB,oBAAoB;AAC1C,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B,wCAAwC;AAC5E,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAC5C,SAAS,uBAAuB;AAChC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AAEhC,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,4BAA4B;AAChD;AAQA,eAAsB,KAAK,SAAsB,SAAuB;AACtE,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ;AAC7B,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,UAAM,OAAO,MAAM,mBAAmB,OAAO;AAE7C,QAAI,CAAC,MAAM;AACT,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AAEA,UAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,QAAQ,CAAC;AACnF,UAAM,WAAW,KAAK;AACtB,UAAM,iBAAiB,OAAO,cAAc,KAAK;AAEjD,QAAI,CAAC,OAAO,GAAG,WAAW,OAAO,GAAG;AAClC,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,kEAAkE;AAAA,QAC3E,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,aAAa,OAAO,GAAG,MAAM,CAAC;AACpC,UAAM,UAAU,gBAAgB,UAAU;AAC1C,QAAI,CAAC,SAAS;AACZ,aAAO,aAAa,KAAK,EAAE,OAAO,gCAAgC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACtF;AAEA,UAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,MAC7D,UAAU,YAAY;AAAA,MACtB,gBAAgB,kBAAkB;AAAA,MAClC,QAAQ,KAAK,OAAO;AAAA,MACpB,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,eAAe;AAAA,MACf,gBAAgB,QAAQ;AAAA,IAC1B,CAAC;AACD,QAAI,eAAe,CAAC,YAAY,IAAI;AAClC,aAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,IAC3E;AAEA,UAAM,mBAAmB,MAAM,GAAG,QAAQ,oBAAoB;AAAA,MAC5D,YAAY,QAAQ;AAAA,MACpB;AAAA,IACF,CAAC;AAED,QAAI;AACJ,QAAI,kBAAkB;AACpB,uBAAiB,YAAY;AAC7B,uBAAiB,eAAe,QAAQ;AACxC,uBAAiB,cAAc,QAAQ;AACvC,uBAAiB,UAAU,QAAQ;AACnC,uBAAiB,aAAa,QAAQ;AACtC,uBAAiB,WAAW,QAAQ,YAAY;AAChD,uBAAiB,UAAU,QAAQ;AACnC,uBAAiB,iBAAiB,QAAQ;AAC1C,uBAAiB,YAAY,KAAK;AAClC,uBAAiB,YAAY,oBAAI,KAAK;AACtC,YAAM,GAAG,MAAM;AACf,cAAQ;AAAA,IACV,OAAO;AACL,YAAM,WAAW,GAAG,OAAO,oBAAoB;AAAA,QAC7C,YAAY,QAAQ;AAAA,QACpB,cAAc,QAAQ;AAAA,QACtB,aAAa,QAAQ;AAAA,QACrB,SAAS,QAAQ;AAAA,QACjB,YAAY,QAAQ;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,SAAS,QAAQ;AAAA,QACjB,gBAAgB,QAAQ;AAAA,QACxB;AAAA,QACA;AAAA,QACA,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK;AAAA,QAChB,WAAW,oBAAI,KAAK;AAAA,QACpB,WAAW,oBAAI,KAAK;AAAA,MACtB,CAAC;AACD,SAAG,QAAQ,QAAQ;AACnB,YAAM,GAAG,MAAM;AACf,cAAQ;AAAA,IACV;AAQA,QAAI,MAAM,SAAU,wBAAuB,MAAM,UAAU,MAAM,kBAAkB,MAAS;AAE5F,QAAI,aAAa,uBAAuB;AACtC,YAAM,iCAAiC,WAAW;AAAA,QAChD,UAAU,YAAY;AAAA,QACtB,gBAAgB,kBAAkB;AAAA,QAClC,QAAQ,KAAK,OAAO;AAAA,QACpB,cAAc;AAAA,QACd,YAAY,OAAO,MAAM,EAAE;AAAA,QAC3B,WAAW;AAAA,QACX,eAAe;AAAA,QACf,gBAAgB,QAAQ;AAAA,QACxB,UAAU,YAAY;AAAA,MACxB,CAAC;AAAA,IACH;AAEA,QAAI;AACF,YAAM,WAAW,UAAU,QAAQ,UAAU;AAG7C,UAAI,YAAY,OAAO,SAAS,cAAc,YAAY;AACxD,cAAM,SAAS;AAAA,UACb;AAAA,UACA;AAAA,YACE,IAAI,MAAM;AAAA,YACV,YAAY,MAAM;AAAA,YAClB,gBAAgB,MAAM,kBAAkB;AAAA,YACxC,UAAU,MAAM;AAAA,YAChB,gBAAgB,MAAM;AAAA,YACtB,QAAQ,KAAK,OAAO;AAAA,UACtB;AAAA,UACA,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,gBAAgB,YAAY,KAAK;AAAA,QACrF;AAAA,MACF;AAAA,IACF,SAAS,YAAY;AACnB,aAAO,MAAM,wDAAwD,EAAE,KAAK,WAAW,CAAC;AAAA,IAC1F;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,MAAM,4BAA4B,KAAK;AAAA,MACvC,SAAS;AAAA,IACX,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,MAAM,yCAAyC,EAAE,KAAK,MAAM,CAAC;AACpE,WAAO,aAAa,KAAK,EAAE,OAAO,0CAA0C,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChG;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,MAAM,CAAC,WAAW;AAAA,MAClB,YAAY,EAAE,OAAO;AAAA,QACnB,IAAI,EAAE,OAAO,EAAE,SAAS,yCAAyC;AAAA,MACnE,CAAC;AAAA,MACD,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,SAAS;AAAA,YACP,MAAM;AAAA,cACJ,IAAI;AAAA,cACJ,YAAY;AAAA,cACZ,cAAc;AAAA,cACd,QAAQ;AAAA,YACV;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,SAAS,EAAE,OAAO,kEAAkE;AAAA,QACtF;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,SAAS,EAAE,OAAO,gCAAgC;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from "@op
7
7
  import { WorkflowDefinition, WorkflowInstance } from "../../../../data/entities.js";
8
8
  import { serializeCodeWorkflowDefinition } from "../../serialize.js";
9
9
  import { getCodeWorkflow } from "../../../../lib/code-registry.js";
10
+ import { invalidateTriggerCache } from "../../../../lib/event-trigger-service.js";
10
11
  import { createLogger } from "@open-mercato/shared/lib/logger";
11
12
  const logger = createLogger("workflows");
12
13
  const metadata = {
@@ -93,6 +94,9 @@ async function POST(request, context) {
93
94
  };
94
95
  em.remove(definition);
95
96
  await em.flush();
97
+ if (removedSnapshot.tenantId) {
98
+ invalidateTriggerCache(removedSnapshot.tenantId, removedSnapshot.organizationId ?? void 0);
99
+ }
96
100
  if (guardResult?.shouldRunAfterSuccess) {
97
101
  await runCrudMutationGuardAfterSuccess(container, {
98
102
  tenantId: tenantId ?? "",