@open-mercato/core 0.6.7-develop.6653.1.b5bc7194a0 → 0.6.7-develop.6661.1.0043ed1d03

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 (73) 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/communication_channels/lib/pg-errors.js +1 -7
  5. package/dist/modules/communication_channels/lib/pg-errors.js.map +2 -2
  6. package/dist/modules/currencies/commands/currencies.js +22 -8
  7. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  8. package/dist/modules/currencies/commands/exchange-rates.js +22 -8
  9. package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
  10. package/dist/modules/currencies/commands/scope.js +22 -0
  11. package/dist/modules/currencies/commands/scope.js.map +7 -0
  12. package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
  13. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  14. package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
  15. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
  16. package/dist/modules/feature_toggles/api/global/route.js +6 -0
  17. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  18. package/dist/modules/feature_toggles/commands/global.js +9 -9
  19. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  20. package/dist/modules/query_index/api/openapi.js +2 -2
  21. package/dist/modules/query_index/api/openapi.js.map +2 -2
  22. package/dist/modules/query_index/api/status.js +6 -4
  23. package/dist/modules/query_index/api/status.js.map +2 -2
  24. package/dist/modules/query_index/cli.js +31 -28
  25. package/dist/modules/query_index/cli.js.map +2 -2
  26. package/dist/modules/query_index/components/QueryIndexesTable.js +4 -1
  27. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  28. package/dist/modules/query_index/lib/batch.js +163 -44
  29. package/dist/modules/query_index/lib/batch.js.map +3 -3
  30. package/dist/modules/query_index/lib/jobs.js +3 -2
  31. package/dist/modules/query_index/lib/jobs.js.map +2 -2
  32. package/dist/modules/query_index/lib/reindexer.js +50 -16
  33. package/dist/modules/query_index/lib/reindexer.js.map +2 -2
  34. package/dist/modules/query_index/lib/stale.js +3 -0
  35. package/dist/modules/query_index/lib/stale.js.map +2 -2
  36. package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
  37. package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
  38. package/dist/modules/query_index/subscribers/delete_one.js +20 -6
  39. package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
  40. package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
  41. package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
  42. package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
  43. package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
  44. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
  45. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
  46. package/package.json +7 -7
  47. package/src/modules/business_rules/lib/value-resolver.ts +16 -0
  48. package/src/modules/communication_channels/lib/pg-errors.ts +3 -10
  49. package/src/modules/currencies/commands/currencies.ts +26 -8
  50. package/src/modules/currencies/commands/exchange-rates.ts +26 -8
  51. package/src/modules/currencies/commands/scope.ts +32 -0
  52. package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
  53. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
  54. package/src/modules/feature_toggles/api/global/route.ts +7 -1
  55. package/src/modules/feature_toggles/commands/global.ts +9 -12
  56. package/src/modules/query_index/api/openapi.ts +2 -2
  57. package/src/modules/query_index/api/status.ts +14 -5
  58. package/src/modules/query_index/cli.ts +36 -28
  59. package/src/modules/query_index/components/QueryIndexesTable.tsx +7 -4
  60. package/src/modules/query_index/i18n/de.json +2 -0
  61. package/src/modules/query_index/i18n/en.json +2 -0
  62. package/src/modules/query_index/i18n/es.json +2 -0
  63. package/src/modules/query_index/i18n/pl.json +2 -0
  64. package/src/modules/query_index/lib/batch.ts +244 -67
  65. package/src/modules/query_index/lib/jobs.ts +11 -0
  66. package/src/modules/query_index/lib/reindexer.ts +69 -16
  67. package/src/modules/query_index/lib/stale.ts +10 -0
  68. package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
  69. package/src/modules/query_index/subscribers/delete_one.ts +21 -10
  70. package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
  71. package/src/modules/workflows/AGENTS.md +22 -0
  72. package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
  73. 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
  }
@@ -7,7 +7,7 @@ const queryIndexErrorSchema = z.object({
7
7
  const queryIndexPartitionSchema = z.object({
8
8
  partitionIndex: z.number().int().nonnegative().nullable().optional(),
9
9
  partitionCount: z.number().int().positive().nullable().optional(),
10
- status: z.enum(["reindexing", "purging", "stalled", "completed"]),
10
+ status: z.enum(["reindexing", "purging", "stalled", "completed", "failed"]),
11
11
  startedAt: z.string().nullable().optional(),
12
12
  finishedAt: z.string().nullable().optional(),
13
13
  heartbeatAt: z.string().nullable().optional(),
@@ -15,7 +15,7 @@ const queryIndexPartitionSchema = z.object({
15
15
  totalCount: z.number().int().nonnegative().nullable().optional()
16
16
  });
17
17
  const queryIndexJobSchema = z.object({
18
- status: z.enum(["idle", "reindexing", "purging", "stalled"]),
18
+ status: z.enum(["idle", "reindexing", "purging", "stalled", "failed"]),
19
19
  startedAt: z.string().nullable().optional(),
20
20
  finishedAt: z.string().nullable().optional(),
21
21
  heartbeatAt: z.string().nullable().optional(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/api/openapi.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport { ENTITY_ID_PATTERN } from '@open-mercato/shared/lib/query/engine'\n\nexport const queryIndexTag = 'Query Index'\n\nexport const queryIndexErrorSchema = z.object({\n error: z.string(),\n}).passthrough()\n\nexport const queryIndexPartitionSchema = z.object({\n partitionIndex: z.number().int().nonnegative().nullable().optional(),\n partitionCount: z.number().int().positive().nullable().optional(),\n status: z.enum(['reindexing', 'purging', 'stalled', 'completed']),\n startedAt: z.string().nullable().optional(),\n finishedAt: z.string().nullable().optional(),\n heartbeatAt: z.string().nullable().optional(),\n processedCount: z.number().int().nonnegative().nullable().optional(),\n totalCount: z.number().int().nonnegative().nullable().optional(),\n})\n\nexport const queryIndexJobSchema = z.object({\n status: z.enum(['idle', 'reindexing', 'purging', 'stalled']),\n startedAt: z.string().nullable().optional(),\n finishedAt: z.string().nullable().optional(),\n heartbeatAt: z.string().nullable().optional(),\n processedCount: z.number().int().nonnegative().nullable().optional(),\n totalCount: z.number().int().nonnegative().nullable().optional(),\n partitions: z.array(queryIndexPartitionSchema).optional(),\n scope: queryIndexPartitionSchema.pick({\n status: true,\n processedCount: true,\n totalCount: true,\n })\n .nullable()\n .optional(),\n})\n\nexport const queryIndexStatusItemSchema = z.object({\n entityId: z.string(),\n label: z.string(),\n baseCount: z.number().int().nonnegative().nullable(),\n indexCount: z.number().int().nonnegative().nullable(),\n vectorCount: z.number().int().nonnegative().nullable().optional(),\n vectorEnabled: z.boolean().optional(),\n ok: z.boolean(),\n job: queryIndexJobSchema,\n})\n\nexport const queryIndexErrorLogSchema = z.object({\n id: z.string(),\n source: z.string(),\n handler: z.string(),\n entityType: z.string().nullable(),\n recordId: z.string().nullable(),\n tenantId: z.string().nullable(),\n organizationId: z.string().nullable(),\n message: z.string(),\n stack: z.string().nullable(),\n payload: z.unknown().nullable(),\n occurredAt: z.string(),\n})\n\nexport const queryIndexStatusLogSchema = z.object({\n id: z.string(),\n source: z.string(),\n handler: z.string(),\n level: z.enum(['info', 'warn']),\n entityType: z.string().nullable(),\n recordId: z.string().nullable(),\n tenantId: z.string().nullable(),\n organizationId: z.string().nullable(),\n message: z.string(),\n details: z.unknown().nullable(),\n occurredAt: z.string(),\n})\n\nexport const queryIndexStatusResponseSchema = z.object({\n items: z.array(queryIndexStatusItemSchema),\n errors: z.array(queryIndexErrorLogSchema),\n logs: z.array(queryIndexStatusLogSchema),\n})\n\nexport const queryIndexReindexRequestSchema = z.object({\n entityType: z.string().min(1).regex(ENTITY_ID_PATTERN),\n force: z.boolean().optional(),\n batchSize: z.number().int().positive().optional(),\n partitionCount: z.number().int().positive().optional(),\n partitionIndex: z.number().int().nonnegative().optional(),\n})\n\nexport const queryIndexPurgeRequestSchema = z.object({\n entityType: z.string().min(1),\n})\n\nexport const queryIndexOkSchema = z.object({\n ok: z.literal(true),\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { ENTITY_ID_PATTERN } from '@open-mercato/shared/lib/query/engine'\n\nexport const queryIndexTag = 'Query Index'\n\nexport const queryIndexErrorSchema = z.object({\n error: z.string(),\n}).passthrough()\n\nexport const queryIndexPartitionSchema = z.object({\n partitionIndex: z.number().int().nonnegative().nullable().optional(),\n partitionCount: z.number().int().positive().nullable().optional(),\n status: z.enum(['reindexing', 'purging', 'stalled', 'completed', 'failed']),\n startedAt: z.string().nullable().optional(),\n finishedAt: z.string().nullable().optional(),\n heartbeatAt: z.string().nullable().optional(),\n processedCount: z.number().int().nonnegative().nullable().optional(),\n totalCount: z.number().int().nonnegative().nullable().optional(),\n})\n\nexport const queryIndexJobSchema = z.object({\n status: z.enum(['idle', 'reindexing', 'purging', 'stalled', 'failed']),\n startedAt: z.string().nullable().optional(),\n finishedAt: z.string().nullable().optional(),\n heartbeatAt: z.string().nullable().optional(),\n processedCount: z.number().int().nonnegative().nullable().optional(),\n totalCount: z.number().int().nonnegative().nullable().optional(),\n partitions: z.array(queryIndexPartitionSchema).optional(),\n scope: queryIndexPartitionSchema.pick({\n status: true,\n processedCount: true,\n totalCount: true,\n })\n .nullable()\n .optional(),\n})\n\nexport const queryIndexStatusItemSchema = z.object({\n entityId: z.string(),\n label: z.string(),\n baseCount: z.number().int().nonnegative().nullable(),\n indexCount: z.number().int().nonnegative().nullable(),\n vectorCount: z.number().int().nonnegative().nullable().optional(),\n vectorEnabled: z.boolean().optional(),\n ok: z.boolean(),\n job: queryIndexJobSchema,\n})\n\nexport const queryIndexErrorLogSchema = z.object({\n id: z.string(),\n source: z.string(),\n handler: z.string(),\n entityType: z.string().nullable(),\n recordId: z.string().nullable(),\n tenantId: z.string().nullable(),\n organizationId: z.string().nullable(),\n message: z.string(),\n stack: z.string().nullable(),\n payload: z.unknown().nullable(),\n occurredAt: z.string(),\n})\n\nexport const queryIndexStatusLogSchema = z.object({\n id: z.string(),\n source: z.string(),\n handler: z.string(),\n level: z.enum(['info', 'warn']),\n entityType: z.string().nullable(),\n recordId: z.string().nullable(),\n tenantId: z.string().nullable(),\n organizationId: z.string().nullable(),\n message: z.string(),\n details: z.unknown().nullable(),\n occurredAt: z.string(),\n})\n\nexport const queryIndexStatusResponseSchema = z.object({\n items: z.array(queryIndexStatusItemSchema),\n errors: z.array(queryIndexErrorLogSchema),\n logs: z.array(queryIndexStatusLogSchema),\n})\n\nexport const queryIndexReindexRequestSchema = z.object({\n entityType: z.string().min(1).regex(ENTITY_ID_PATTERN),\n force: z.boolean().optional(),\n batchSize: z.number().int().positive().optional(),\n partitionCount: z.number().int().positive().optional(),\n partitionIndex: z.number().int().nonnegative().optional(),\n})\n\nexport const queryIndexPurgeRequestSchema = z.object({\n entityType: z.string().min(1),\n})\n\nexport const queryIndexOkSchema = z.object({\n ok: z.literal(true),\n})\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -170,7 +170,7 @@ async function GET(req) {
170
170
  const startedDate = row.started_at ? new Date(row.started_at) : null;
171
171
  const finishedDate = row.finished_at ? new Date(row.finished_at) : null;
172
172
  const stalled = !finishedDate && (!heartbeatDate || Date.now() - heartbeatDate.getTime() > HEARTBEAT_STALE_MS);
173
- const state = finishedDate ? "completed" : stalled ? "stalled" : row.status || "reindexing";
173
+ const state = finishedDate ? row.status === "failed" ? "failed" : "completed" : stalled ? "stalled" : row.status || "reindexing";
174
174
  return {
175
175
  partitionIndex: row.partition_index ?? null,
176
176
  partitionCount: row.partition_count ?? null,
@@ -187,6 +187,7 @@ async function GET(req) {
187
187
  (p) => p.status === "reindexing" || p.status === "purging"
188
188
  );
189
189
  const stalledPartitions = activePartitions.filter((p) => p.status === "stalled");
190
+ const scopeCandidate = !preferOrg || !scopeRow || scopeRow.orgMatch ? scopeRow : null;
190
191
  let status = "idle";
191
192
  if (activePartitions.length) {
192
193
  if (runningPartitions.length) {
@@ -194,14 +195,15 @@ async function GET(req) {
194
195
  } else if (stalledPartitions.length) {
195
196
  status = "stalled";
196
197
  }
198
+ } else if (partitions.some((p) => p.status === "failed") || scopeCandidate?.row.finished_at && scopeCandidate.row.status === "failed") {
199
+ status = "failed";
197
200
  }
198
201
  const startedAt = activePartitions[0]?.startedAt ?? partitions[0]?.startedAt ?? null;
199
- const finishedAt = status === "idle" ? partitions.find((p) => p.finishedAt)?.finishedAt ?? null : null;
202
+ const finishedAt = status === "idle" || status === "failed" ? partitions.find((p) => p.finishedAt)?.finishedAt ?? null : null;
200
203
  const heartbeatAt = activePartitions[0]?.heartbeatAt ?? partitions[0]?.heartbeatAt ?? null;
201
204
  const jobTotalCount = partitions.reduce((sum, p) => sum + (p.totalCount ?? 0), 0);
202
205
  const processedSum = partitions.reduce((sum, p) => sum + (p.processedCount ?? 0), 0);
203
206
  const processedCount = jobTotalCount ? Math.min(jobTotalCount, processedSum) : processedSum || null;
204
- const scopeCandidate = !preferOrg || !scopeRow || scopeRow.orgMatch ? scopeRow : null;
205
207
  return {
206
208
  status,
207
209
  startedAt,
@@ -214,7 +216,7 @@ async function GET(req) {
214
216
  status: (() => {
215
217
  const heartbeatDate = scopeCandidate.row.heartbeat_at ? new Date(scopeCandidate.row.heartbeat_at) : null;
216
218
  const finishedDate = scopeCandidate.row.finished_at ? new Date(scopeCandidate.row.finished_at) : null;
217
- if (finishedDate) return "completed";
219
+ if (finishedDate) return scopeCandidate.row.status === "failed" ? "failed" : "completed";
218
220
  if (!heartbeatDate || Date.now() - heartbeatDate.getTime() > HEARTBEAT_STALE_MS) {
219
221
  return "stalled";
220
222
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/api/status.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from 'kysely'\nimport { readCoverageSnapshots, refreshCoverageSnapshot, type CoverageSnapshot } from '../lib/coverage'\nimport { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'\nimport type { FullTextSearchStrategy } from '@open-mercato/search/strategies'\nimport type { SearchModuleConfig } from '@open-mercato/shared/modules/search'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexStatusResponseSchema } from './openapi'\nimport { flattenSystemEntityIds } from '@open-mercato/shared/lib/entities/system-entities'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['query_index.status.view'] },\n}\n\nconst STATUS_REFRESH_COOLDOWN_MS = 60_000\n\nfunction getCoverageSnapshotRefreshedAt(snapshot: Pick<CoverageSnapshot, 'refreshed_at'> | null | undefined): number | null {\n const value = snapshot?.refreshed_at\n if (value instanceof Date) {\n const time = value.getTime()\n return Number.isFinite(time) ? time : null\n }\n if (typeof value === 'string') {\n const time = new Date(value).getTime()\n return Number.isFinite(time) ? time : null\n }\n return null\n}\n\nfunction hasFreshCoverageSnapshots(\n snapshots: Map<string, CoverageSnapshot>,\n entityIds: string[],\n now: number,\n): boolean {\n for (const entityId of entityIds) {\n const refreshedAt = getCoverageSnapshotRefreshedAt(snapshots.get(entityId))\n if (refreshedAt === null || now - refreshedAt >= STATUS_REFRESH_COOLDOWN_MS) return false\n }\n return entityIds.length > 0\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const db = (em as any).getKysely()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n\n const organizationId = scope.selectedId ?? auth.orgId ?? null\n const tenantId = typeof scope.tenantId === 'string' && scope.tenantId.trim().length > 0\n ? scope.tenantId.trim()\n : (typeof auth.tenantId === 'string' && auth.tenantId.trim().length > 0 ? auth.tenantId.trim() : null)\n if (!tenantId) {\n return NextResponse.json({ error: 'Tenant context is required' }, { status: 400 })\n }\n\n const organizationFilter =\n scope.filterIds === null\n ? null\n : Array.isArray(scope.filterIds) && scope.filterIds.length > 0\n ? scope.filterIds\n : organizationId\n ? [organizationId]\n : []\n\n if (Array.isArray(organizationFilter) && organizationFilter.length === 0) {\n return NextResponse.json({ error: 'Organization access denied' }, { status: 403 })\n }\n\n const organizationScopeIds = organizationFilter === null\n ? null\n : Array.from(\n new Set(\n organizationFilter.filter(\n (value): value is string => typeof value === 'string' && value.length > 0,\n ),\n ),\n )\n\n if (Array.isArray(organizationScopeIds) && organizationScopeIds.length === 0) {\n return NextResponse.json({ error: 'Organization access denied' }, { status: 403 })\n }\n\n const url = new URL(req.url)\n const forceRefresh = url.searchParams.has('refresh') && url.searchParams.get('refresh') !== '0'\n\n const generatedIds = flattenSystemEntityIds(getEntityIds() as Record<string, Record<string, string>>)\n const generated = generatedIds.map((entityId) => ({ entityId, label: entityId }))\n\n const byId = new Map<string, { entityId: string; label: string }>()\n for (const g of generated) byId.set(g.entityId, g)\n\n let entityIds = generatedIds.slice()\n\n // Resolve search module configs to determine vector-enabled entities\n // Entities with buildSource defined are vector-search enabled\n let searchModuleConfigs: SearchModuleConfig[] = []\n try {\n searchModuleConfigs = container.resolve('searchModuleConfigs') as SearchModuleConfig[]\n } catch {\n // Search module configs not available\n }\n\n const vectorEnabledEntities = new Set<string>()\n const fulltextEnabledEntities = new Set<string>()\n for (const moduleConfig of searchModuleConfigs) {\n for (const entity of moduleConfig.entities ?? []) {\n if (entity.enabled !== false) {\n // Vector: entities with buildSource defined\n if (typeof entity.buildSource === 'function') {\n vectorEnabledEntities.add(entity.entityId)\n }\n // Fulltext: entities with fieldPolicy defined\n if (entity.fieldPolicy && typeof entity.fieldPolicy === 'object') {\n fulltextEnabledEntities.add(entity.entityId)\n }\n }\n }\n }\n\n // Resolve fulltext strategy for entity counts\n let fulltextStrategy: FullTextSearchStrategy | null = null\n try {\n const searchStrategies = container.resolve('searchStrategies') as unknown[]\n fulltextStrategy = (searchStrategies?.find(\n (s: unknown) => (s as { id?: string })?.id === 'fulltext',\n ) as FullTextSearchStrategy) ?? null\n } catch {\n fulltextStrategy = null\n }\n\n // Fetch fulltext entity counts\n let fulltextEntityCounts: Record<string, number> | null = null\n if (fulltextStrategy) {\n try {\n fulltextEntityCounts = await fulltextStrategy.getEntityCounts(tenantId)\n } catch {\n fulltextEntityCounts = null\n }\n }\n\n // Limit to entities that have active custom field definitions in current scope\n try {\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .where('is_active' as any, '=', true)\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n if (Array.isArray(organizationScopeIds)) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, 'in', organizationScopeIds),\n eb('organization_id' as any, 'is', null),\n ]))\n }\n const cfRows = await cfQuery.execute() as Array<{ entity_id: string }>\n const enabled = new Set<string>((cfRows || []).map((r) => String(r.entity_id)))\n entityIds = entityIds.filter((id) => enabled.has(id))\n } catch {}\n\n const HEARTBEAT_STALE_MS = 60_000\n const COVERAGE_STALE_MS = 60_000\n const COVERAGE_REFRESH_CONCURRENCY = 8\n\n async function fetchJobSummary(entityType: string, tenantIdParam: string | null, organizationIdParam: string | null) {\n try {\n let jobQuery = db\n .selectFrom('entity_index_jobs' as any)\n .selectAll()\n .where('entity_type' as any, '=', entityType)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantIdParam ?? null}`)\n if (organizationIdParam != null) {\n jobQuery = jobQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', organizationIdParam),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n jobQuery = jobQuery.where(sql<boolean>`organization_id is not distinct from ${null}`)\n }\n const rows = await jobQuery\n .orderBy('started_at' as any, 'desc')\n .execute() as Array<Record<string, any>>\n\n if (!rows.length) {\n return { status: 'idle' as const, partitions: [] as any[] }\n }\n\n const preferOrg =\n organizationIdParam != null && rows.some((row: any) => row.organization_id === organizationIdParam)\n const pickPreferred = <T extends { startedTs: number; tenantMatch: boolean; orgMatch: boolean }>(\n existing: T | null,\n candidate: T,\n ): T => {\n if (!existing) return candidate\n if (preferOrg) {\n if (candidate.orgMatch && !existing.orgMatch) return candidate\n if (!candidate.orgMatch && existing.orgMatch) return existing\n }\n if (candidate.tenantMatch && !existing.tenantMatch) return candidate\n if (!candidate.tenantMatch && existing.tenantMatch) return existing\n return candidate.startedTs > existing.startedTs ? candidate : existing\n }\n\n const partitionRows = new Map<string, { row: any; startedTs: number; tenantMatch: boolean; orgMatch: boolean }>()\n let scopeRow: { row: any; startedTs: number; tenantMatch: boolean; orgMatch: boolean } | null = null\n for (const row of rows) {\n const key = String(row.partition_index ?? '__null__')\n const startedTs = row.started_at ? new Date(row.started_at).getTime() : 0\n const tenantMatch = tenantIdParam != null ? row.tenant_id === tenantIdParam : true\n const orgMatch = organizationIdParam != null ? row.organization_id === organizationIdParam : row.organization_id == null\n const candidate = { row, startedTs, tenantMatch, orgMatch }\n if (row.partition_index == null) {\n scopeRow = pickPreferred(scopeRow, candidate)\n continue\n }\n const existing = partitionRows.get(key)\n partitionRows.set(key, pickPreferred(existing ?? null, candidate))\n }\n\n const partitions = Array.from(partitionRows.values())\n .filter((entry) => !preferOrg || entry.orgMatch)\n .map(({ row }) => {\n const heartbeatDate = row.heartbeat_at ? new Date(row.heartbeat_at) : null\n const startedDate = row.started_at ? new Date(row.started_at) : null\n const finishedDate = row.finished_at ? new Date(row.finished_at) : null\n const stalled =\n !finishedDate && (!heartbeatDate || Date.now() - heartbeatDate.getTime() > HEARTBEAT_STALE_MS)\n const state = finishedDate\n ? 'completed'\n : stalled\n ? 'stalled'\n : (row.status as string) || 'reindexing'\n return {\n partitionIndex: row.partition_index ?? null,\n partitionCount: row.partition_count ?? null,\n status: state,\n startedAt: startedDate ? startedDate.toISOString() : null,\n finishedAt: finishedDate ? finishedDate.toISOString() : null,\n heartbeatAt: heartbeatDate ? heartbeatDate.toISOString() : null,\n processedCount: row.processed_count ?? null,\n totalCount: row.total_count ?? null,\n }\n })\n .sort((a, b) => (a.partitionIndex ?? 0) - (b.partitionIndex ?? 0))\n const activePartitions = partitions.filter((p) => !p.finishedAt)\n const runningPartitions = activePartitions.filter(\n (p) => p.status === 'reindexing' || p.status === 'purging',\n )\n const stalledPartitions = activePartitions.filter((p) => p.status === 'stalled')\n let status: 'idle' | 'reindexing' | 'purging' | 'stalled' = 'idle'\n if (activePartitions.length) {\n if (runningPartitions.length) {\n status = runningPartitions.some((p) => p.status === 'purging') ? 'purging' : 'reindexing'\n } else if (stalledPartitions.length) {\n status = 'stalled'\n }\n }\n\n const startedAt = activePartitions[0]?.startedAt ?? partitions[0]?.startedAt ?? null\n const finishedAt = status === 'idle' ? (partitions.find((p) => p.finishedAt)?.finishedAt ?? null) : null\n const heartbeatAt = activePartitions[0]?.heartbeatAt ?? partitions[0]?.heartbeatAt ?? null\n const jobTotalCount = partitions.reduce((sum, p) => sum + (p.totalCount ?? 0), 0)\n const processedSum = partitions.reduce((sum, p) => sum + (p.processedCount ?? 0), 0)\n const processedCount = jobTotalCount ? Math.min(jobTotalCount, processedSum) : processedSum || null\n const scopeCandidate = !preferOrg || !scopeRow || scopeRow.orgMatch ? scopeRow : null\n\n return {\n status,\n startedAt,\n finishedAt,\n heartbeatAt,\n processedCount: jobTotalCount ? processedCount : scopeCandidate?.row?.processed_count ?? null,\n totalCount: jobTotalCount ? jobTotalCount : scopeCandidate?.row?.total_count ?? null,\n partitions,\n scope: scopeCandidate\n ? {\n status: (() => {\n const heartbeatDate = scopeCandidate!.row.heartbeat_at ? new Date(scopeCandidate!.row.heartbeat_at) : null\n const finishedDate = scopeCandidate!.row.finished_at ? new Date(scopeCandidate!.row.finished_at) : null\n if (finishedDate) return 'completed'\n if (\n !heartbeatDate ||\n Date.now() - heartbeatDate.getTime() > HEARTBEAT_STALE_MS\n ) {\n return 'stalled'\n }\n return (scopeCandidate!.row.status as string) || 'reindexing'\n })(),\n processedCount: scopeCandidate.row.processed_count ?? null,\n totalCount: scopeCandidate.row.total_count ?? null,\n }\n : null,\n }\n } catch {\n return { status: 'idle' as const, partitions: [] as any[] }\n }\n }\n\n const normalizeCount = (value: unknown): number | null => {\n if (value == null) return null\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n\n const coverageScope = {\n tenantId: tenantId ?? null,\n organizationId,\n withDeleted: false,\n } as const\n const entitiesNeedingRefresh = new Set<string>()\n\n // Read every entity's coverage snapshot in a single batched query. This endpoint is\n // polled by the status table every few seconds, so the poll path must stay read-cheap:\n // stale snapshots are refreshed asynchronously via the query_index.coverage.refresh\n // event emitted below, never inline per entity.\n const snapshotByEntity = await readCoverageSnapshots(db, { entityTypes: entityIds, ...coverageScope })\n\n // An explicit refresh action (?refresh) may block, but only when the durable\n // coverage snapshots are stale. Recent persisted snapshots survive workers/restarts,\n // so repeated refresh requests use them instead of hammering base-table counts.\n if (forceRefresh && entityIds.length > 0 && !hasFreshCoverageSnapshots(snapshotByEntity, entityIds, Date.now())) {\n await mapWithConcurrency(entityIds, COVERAGE_REFRESH_CONCURRENCY, (entityId) =>\n refreshCoverageSnapshot(em, { entityType: entityId, ...coverageScope }).catch(() => undefined),\n )\n const refreshed = await readCoverageSnapshots(db, { entityTypes: entityIds, ...coverageScope })\n for (const [entityId, snapshot] of refreshed) snapshotByEntity.set(entityId, snapshot)\n }\n\n const coverageSnapshots = entityIds.map((entityId) => snapshotByEntity.get(entityId) ?? null)\n\n const jobs = await Promise.all(entityIds.map((eid) => fetchJobSummary(eid, tenantId, organizationId)))\n\n const items: any[] = []\n for (let idx = 0; idx < entityIds.length; idx += 1) {\n const eid = entityIds[idx]\n let coverage = coverageSnapshots[idx]\n\n const refreshedAt = coverage?.refreshed_at instanceof Date ? coverage.refreshed_at : coverage?.refreshed_at ? new Date(coverage.refreshed_at) : null\n const isStale = !coverage || !refreshedAt || (Date.now() - refreshedAt.getTime() > COVERAGE_STALE_MS)\n if (isStale) entitiesNeedingRefresh.add(eid)\n\n const job = jobs[idx]\n const label = (byId.get(eid)?.label) || eid\n const baseCountNumber = normalizeCount(coverage?.baseCount)\n const indexCountNumber = normalizeCount(coverage?.indexedCount)\n const vectorEnabled = vectorEnabledEntities.has(eid)\n const vectorCountNumber = vectorEnabled ? normalizeCount((coverage as any)?.vectorIndexedCount ?? (coverage as any)?.vector_indexed_count) : null\n const fulltextEnabled = fulltextEnabledEntities.has(eid)\n const fulltextCountNumber = fulltextEnabled ? (fulltextEntityCounts?.[eid] ?? 0) : null\n const ok = (() => {\n if (baseCountNumber == null || indexCountNumber == null) return false\n if (baseCountNumber !== indexCountNumber) return false\n if (!vectorEnabled) return true\n return vectorCountNumber != null && vectorCountNumber === baseCountNumber\n })()\n items.push({\n entityId: eid,\n label,\n baseCount: baseCountNumber,\n indexCount: indexCountNumber,\n vectorCount: vectorEnabled ? vectorCountNumber : null,\n vectorEnabled,\n fulltextCount: fulltextCountNumber,\n fulltextEnabled,\n ok,\n job,\n refreshedAt: refreshedAt ?? null,\n })\n }\n\n if (!forceRefresh) {\n try {\n const eventBus = container.resolve('eventBus')\n if (entitiesNeedingRefresh.size > 0) {\n await Promise.all(\n Array.from(entitiesNeedingRefresh).map((entityId) =>\n eventBus\n .emitEvent('query_index.coverage.refresh', {\n entityType: entityId,\n tenantId: tenantId ?? null,\n organizationId,\n delayMs: 0,\n })\n .catch(() => undefined)\n )\n )\n }\n } catch {}\n }\n\n let errorQuery = db\n .selectFrom('indexer_error_logs' as any)\n .selectAll()\n if (tenantId != null) {\n errorQuery = errorQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n errorQuery = errorQuery.where('tenant_id' as any, 'is', null as any)\n }\n if (Array.isArray(organizationScopeIds) && organizationScopeIds.length) {\n errorQuery = errorQuery.where('organization_id' as any, 'in', organizationScopeIds)\n } else {\n errorQuery = errorQuery.where('organization_id' as any, 'is', null as any)\n }\n const errorRows = await errorQuery\n .orderBy('occurred_at' as any, 'desc')\n .limit(100)\n .execute() as Array<Record<string, any>>\n\n const errors = errorRows.map((row: any) => {\n const occurredAt = row.occurred_at instanceof Date ? row.occurred_at : row.occurred_at ? new Date(row.occurred_at) : null\n return {\n id: String(row.id),\n source: String(row.source ?? ''),\n handler: String(row.handler ?? ''),\n entityType: row.entity_type ?? null,\n recordId: row.record_id ?? null,\n tenantId: row.tenant_id ?? null,\n organizationId: row.organization_id ?? null,\n message: String(row.message ?? ''),\n stack: row.stack ?? null,\n payload: row.payload ?? null,\n occurredAt: occurredAt ? occurredAt.toISOString() : new Date().toISOString(),\n }\n })\n\n let logsQuery = db\n .selectFrom('indexer_status_logs' as any)\n .selectAll()\n if (tenantId != null) {\n logsQuery = logsQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n logsQuery = logsQuery.where('tenant_id' as any, 'is', null as any)\n }\n if (Array.isArray(organizationScopeIds) && organizationScopeIds.length) {\n logsQuery = logsQuery.where('organization_id' as any, 'in', organizationScopeIds)\n } else {\n logsQuery = logsQuery.where('organization_id' as any, 'is', null as any)\n }\n const logRows = await logsQuery\n .orderBy('occurred_at' as any, 'desc')\n .limit(100)\n .execute() as Array<Record<string, any>>\n\n const logs = logRows.map((row: any) => {\n const occurredAt = row.occurred_at instanceof Date ? row.occurred_at : row.occurred_at ? new Date(row.occurred_at) : null\n const level = row.level === 'warn' ? 'warn' : 'info'\n return {\n id: String(row.id),\n source: String(row.source ?? ''),\n handler: String(row.handler ?? ''),\n level,\n entityType: row.entity_type ?? null,\n recordId: row.record_id ?? null,\n tenantId: row.tenant_id ?? null,\n organizationId: row.organization_id ?? null,\n message: String(row.message ?? ''),\n details: row.details ?? null,\n occurredAt: occurredAt ? occurredAt.toISOString() : new Date().toISOString(),\n }\n })\n\n const response = NextResponse.json({ items, errors, logs })\n const partial = items.find((item) => {\n // Coverage not computed yet (no snapshot) \u2014 pending an async refresh, not a partial\n // index. Do not raise the partial-index warning while counts are still unknown.\n if (item.baseCount == null && item.indexCount == null) return false\n if (item.baseCount == null || item.indexCount == null) return true\n return item.baseCount !== item.indexCount\n })\n if (partial) {\n response.headers.set(\n 'x-om-partial-index',\n JSON.stringify({\n type: 'partial_index',\n entity: partial.entityId,\n entityLabel: partial.label ?? partial.entityId,\n baseCount: partial.baseCount,\n indexedCount: partial.indexCount,\n scope: organizationId,\n })\n )\n }\n return response\n}\n\nconst queryIndexStatusDoc: OpenApiMethodDoc = {\n summary: 'Inspect query index coverage',\n description: 'Returns entity counts comparing base tables with the query index along with the latest job status.',\n tags: [queryIndexTag],\n responses: [\n { status: 200, description: 'Current query index status.', schema: queryIndexStatusResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Tenant or organization context required', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Query index status',\n methods: {\n GET: queryIndexStatusDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from 'kysely'\nimport { readCoverageSnapshots, refreshCoverageSnapshot, type CoverageSnapshot } from '../lib/coverage'\nimport { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'\nimport type { FullTextSearchStrategy } from '@open-mercato/search/strategies'\nimport type { SearchModuleConfig } from '@open-mercato/shared/modules/search'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { queryIndexTag, queryIndexErrorSchema, queryIndexStatusResponseSchema } from './openapi'\nimport { flattenSystemEntityIds } from '@open-mercato/shared/lib/entities/system-entities'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['query_index.status.view'] },\n}\n\nconst STATUS_REFRESH_COOLDOWN_MS = 60_000\n\nfunction getCoverageSnapshotRefreshedAt(snapshot: Pick<CoverageSnapshot, 'refreshed_at'> | null | undefined): number | null {\n const value = snapshot?.refreshed_at\n if (value instanceof Date) {\n const time = value.getTime()\n return Number.isFinite(time) ? time : null\n }\n if (typeof value === 'string') {\n const time = new Date(value).getTime()\n return Number.isFinite(time) ? time : null\n }\n return null\n}\n\nfunction hasFreshCoverageSnapshots(\n snapshots: Map<string, CoverageSnapshot>,\n entityIds: string[],\n now: number,\n): boolean {\n for (const entityId of entityIds) {\n const refreshedAt = getCoverageSnapshotRefreshedAt(snapshots.get(entityId))\n if (refreshedAt === null || now - refreshedAt >= STATUS_REFRESH_COOLDOWN_MS) return false\n }\n return entityIds.length > 0\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const db = (em as any).getKysely()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n\n const organizationId = scope.selectedId ?? auth.orgId ?? null\n const tenantId = typeof scope.tenantId === 'string' && scope.tenantId.trim().length > 0\n ? scope.tenantId.trim()\n : (typeof auth.tenantId === 'string' && auth.tenantId.trim().length > 0 ? auth.tenantId.trim() : null)\n if (!tenantId) {\n return NextResponse.json({ error: 'Tenant context is required' }, { status: 400 })\n }\n\n const organizationFilter =\n scope.filterIds === null\n ? null\n : Array.isArray(scope.filterIds) && scope.filterIds.length > 0\n ? scope.filterIds\n : organizationId\n ? [organizationId]\n : []\n\n if (Array.isArray(organizationFilter) && organizationFilter.length === 0) {\n return NextResponse.json({ error: 'Organization access denied' }, { status: 403 })\n }\n\n const organizationScopeIds = organizationFilter === null\n ? null\n : Array.from(\n new Set(\n organizationFilter.filter(\n (value): value is string => typeof value === 'string' && value.length > 0,\n ),\n ),\n )\n\n if (Array.isArray(organizationScopeIds) && organizationScopeIds.length === 0) {\n return NextResponse.json({ error: 'Organization access denied' }, { status: 403 })\n }\n\n const url = new URL(req.url)\n const forceRefresh = url.searchParams.has('refresh') && url.searchParams.get('refresh') !== '0'\n\n const generatedIds = flattenSystemEntityIds(getEntityIds() as Record<string, Record<string, string>>)\n const generated = generatedIds.map((entityId) => ({ entityId, label: entityId }))\n\n const byId = new Map<string, { entityId: string; label: string }>()\n for (const g of generated) byId.set(g.entityId, g)\n\n let entityIds = generatedIds.slice()\n\n // Resolve search module configs to determine vector-enabled entities\n // Entities with buildSource defined are vector-search enabled\n let searchModuleConfigs: SearchModuleConfig[] = []\n try {\n searchModuleConfigs = container.resolve('searchModuleConfigs') as SearchModuleConfig[]\n } catch {\n // Search module configs not available\n }\n\n const vectorEnabledEntities = new Set<string>()\n const fulltextEnabledEntities = new Set<string>()\n for (const moduleConfig of searchModuleConfigs) {\n for (const entity of moduleConfig.entities ?? []) {\n if (entity.enabled !== false) {\n // Vector: entities with buildSource defined\n if (typeof entity.buildSource === 'function') {\n vectorEnabledEntities.add(entity.entityId)\n }\n // Fulltext: entities with fieldPolicy defined\n if (entity.fieldPolicy && typeof entity.fieldPolicy === 'object') {\n fulltextEnabledEntities.add(entity.entityId)\n }\n }\n }\n }\n\n // Resolve fulltext strategy for entity counts\n let fulltextStrategy: FullTextSearchStrategy | null = null\n try {\n const searchStrategies = container.resolve('searchStrategies') as unknown[]\n fulltextStrategy = (searchStrategies?.find(\n (s: unknown) => (s as { id?: string })?.id === 'fulltext',\n ) as FullTextSearchStrategy) ?? null\n } catch {\n fulltextStrategy = null\n }\n\n // Fetch fulltext entity counts\n let fulltextEntityCounts: Record<string, number> | null = null\n if (fulltextStrategy) {\n try {\n fulltextEntityCounts = await fulltextStrategy.getEntityCounts(tenantId)\n } catch {\n fulltextEntityCounts = null\n }\n }\n\n // Limit to entities that have active custom field definitions in current scope\n try {\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .where('is_active' as any, '=', true)\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n if (Array.isArray(organizationScopeIds)) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, 'in', organizationScopeIds),\n eb('organization_id' as any, 'is', null),\n ]))\n }\n const cfRows = await cfQuery.execute() as Array<{ entity_id: string }>\n const enabled = new Set<string>((cfRows || []).map((r) => String(r.entity_id)))\n entityIds = entityIds.filter((id) => enabled.has(id))\n } catch {}\n\n const HEARTBEAT_STALE_MS = 60_000\n const COVERAGE_STALE_MS = 60_000\n const COVERAGE_REFRESH_CONCURRENCY = 8\n\n async function fetchJobSummary(entityType: string, tenantIdParam: string | null, organizationIdParam: string | null) {\n try {\n let jobQuery = db\n .selectFrom('entity_index_jobs' as any)\n .selectAll()\n .where('entity_type' as any, '=', entityType)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantIdParam ?? null}`)\n if (organizationIdParam != null) {\n jobQuery = jobQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', organizationIdParam),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n jobQuery = jobQuery.where(sql<boolean>`organization_id is not distinct from ${null}`)\n }\n const rows = await jobQuery\n .orderBy('started_at' as any, 'desc')\n .execute() as Array<Record<string, any>>\n\n if (!rows.length) {\n return { status: 'idle' as const, partitions: [] as any[] }\n }\n\n const preferOrg =\n organizationIdParam != null && rows.some((row: any) => row.organization_id === organizationIdParam)\n const pickPreferred = <T extends { startedTs: number; tenantMatch: boolean; orgMatch: boolean }>(\n existing: T | null,\n candidate: T,\n ): T => {\n if (!existing) return candidate\n if (preferOrg) {\n if (candidate.orgMatch && !existing.orgMatch) return candidate\n if (!candidate.orgMatch && existing.orgMatch) return existing\n }\n if (candidate.tenantMatch && !existing.tenantMatch) return candidate\n if (!candidate.tenantMatch && existing.tenantMatch) return existing\n return candidate.startedTs > existing.startedTs ? candidate : existing\n }\n\n const partitionRows = new Map<string, { row: any; startedTs: number; tenantMatch: boolean; orgMatch: boolean }>()\n let scopeRow: { row: any; startedTs: number; tenantMatch: boolean; orgMatch: boolean } | null = null\n for (const row of rows) {\n const key = String(row.partition_index ?? '__null__')\n const startedTs = row.started_at ? new Date(row.started_at).getTime() : 0\n const tenantMatch = tenantIdParam != null ? row.tenant_id === tenantIdParam : true\n const orgMatch = organizationIdParam != null ? row.organization_id === organizationIdParam : row.organization_id == null\n const candidate = { row, startedTs, tenantMatch, orgMatch }\n if (row.partition_index == null) {\n scopeRow = pickPreferred(scopeRow, candidate)\n continue\n }\n const existing = partitionRows.get(key)\n partitionRows.set(key, pickPreferred(existing ?? null, candidate))\n }\n\n const partitions = Array.from(partitionRows.values())\n .filter((entry) => !preferOrg || entry.orgMatch)\n .map(({ row }) => {\n const heartbeatDate = row.heartbeat_at ? new Date(row.heartbeat_at) : null\n const startedDate = row.started_at ? new Date(row.started_at) : null\n const finishedDate = row.finished_at ? new Date(row.finished_at) : null\n const stalled =\n !finishedDate && (!heartbeatDate || Date.now() - heartbeatDate.getTime() > HEARTBEAT_STALE_MS)\n const state = finishedDate\n ? (row.status === 'failed' ? 'failed' : 'completed')\n : stalled\n ? 'stalled'\n : (row.status as string) || 'reindexing'\n return {\n partitionIndex: row.partition_index ?? null,\n partitionCount: row.partition_count ?? null,\n status: state,\n startedAt: startedDate ? startedDate.toISOString() : null,\n finishedAt: finishedDate ? finishedDate.toISOString() : null,\n heartbeatAt: heartbeatDate ? heartbeatDate.toISOString() : null,\n processedCount: row.processed_count ?? null,\n totalCount: row.total_count ?? null,\n }\n })\n .sort((a, b) => (a.partitionIndex ?? 0) - (b.partitionIndex ?? 0))\n const activePartitions = partitions.filter((p) => !p.finishedAt)\n const runningPartitions = activePartitions.filter(\n (p) => p.status === 'reindexing' || p.status === 'purging',\n )\n const stalledPartitions = activePartitions.filter((p) => p.status === 'stalled')\n const scopeCandidate = !preferOrg || !scopeRow || scopeRow.orgMatch ? scopeRow : null\n let status: 'idle' | 'reindexing' | 'purging' | 'stalled' | 'failed' = 'idle'\n if (activePartitions.length) {\n if (runningPartitions.length) {\n status = runningPartitions.some((p) => p.status === 'purging') ? 'purging' : 'reindexing'\n } else if (stalledPartitions.length) {\n status = 'stalled'\n }\n } else if (\n partitions.some((p) => p.status === 'failed')\n || (scopeCandidate?.row.finished_at && scopeCandidate.row.status === 'failed')\n ) {\n // The run finished but lost records; without this it reports \"idle\" and the only\n // hint that anything went wrong is the coverage percentage.\n status = 'failed'\n }\n\n const startedAt = activePartitions[0]?.startedAt ?? partitions[0]?.startedAt ?? null\n const finishedAt = status === 'idle' || status === 'failed'\n ? (partitions.find((p) => p.finishedAt)?.finishedAt ?? null)\n : null\n const heartbeatAt = activePartitions[0]?.heartbeatAt ?? partitions[0]?.heartbeatAt ?? null\n const jobTotalCount = partitions.reduce((sum, p) => sum + (p.totalCount ?? 0), 0)\n const processedSum = partitions.reduce((sum, p) => sum + (p.processedCount ?? 0), 0)\n const processedCount = jobTotalCount ? Math.min(jobTotalCount, processedSum) : processedSum || null\n\n return {\n status,\n startedAt,\n finishedAt,\n heartbeatAt,\n processedCount: jobTotalCount ? processedCount : scopeCandidate?.row?.processed_count ?? null,\n totalCount: jobTotalCount ? jobTotalCount : scopeCandidate?.row?.total_count ?? null,\n partitions,\n scope: scopeCandidate\n ? {\n status: (() => {\n const heartbeatDate = scopeCandidate!.row.heartbeat_at ? new Date(scopeCandidate!.row.heartbeat_at) : null\n const finishedDate = scopeCandidate!.row.finished_at ? new Date(scopeCandidate!.row.finished_at) : null\n if (finishedDate) return scopeCandidate!.row.status === 'failed' ? 'failed' : 'completed'\n if (\n !heartbeatDate ||\n Date.now() - heartbeatDate.getTime() > HEARTBEAT_STALE_MS\n ) {\n return 'stalled'\n }\n return (scopeCandidate!.row.status as string) || 'reindexing'\n })(),\n processedCount: scopeCandidate.row.processed_count ?? null,\n totalCount: scopeCandidate.row.total_count ?? null,\n }\n : null,\n }\n } catch {\n return { status: 'idle' as const, partitions: [] as any[] }\n }\n }\n\n const normalizeCount = (value: unknown): number | null => {\n if (value == null) return null\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n\n const coverageScope = {\n tenantId: tenantId ?? null,\n organizationId,\n withDeleted: false,\n } as const\n const entitiesNeedingRefresh = new Set<string>()\n\n // Read every entity's coverage snapshot in a single batched query. This endpoint is\n // polled by the status table every few seconds, so the poll path must stay read-cheap:\n // stale snapshots are refreshed asynchronously via the query_index.coverage.refresh\n // event emitted below, never inline per entity.\n const snapshotByEntity = await readCoverageSnapshots(db, { entityTypes: entityIds, ...coverageScope })\n\n // An explicit refresh action (?refresh) may block, but only when the durable\n // coverage snapshots are stale. Recent persisted snapshots survive workers/restarts,\n // so repeated refresh requests use them instead of hammering base-table counts.\n if (forceRefresh && entityIds.length > 0 && !hasFreshCoverageSnapshots(snapshotByEntity, entityIds, Date.now())) {\n await mapWithConcurrency(entityIds, COVERAGE_REFRESH_CONCURRENCY, (entityId) =>\n refreshCoverageSnapshot(em, { entityType: entityId, ...coverageScope }).catch(() => undefined),\n )\n const refreshed = await readCoverageSnapshots(db, { entityTypes: entityIds, ...coverageScope })\n for (const [entityId, snapshot] of refreshed) snapshotByEntity.set(entityId, snapshot)\n }\n\n const coverageSnapshots = entityIds.map((entityId) => snapshotByEntity.get(entityId) ?? null)\n\n const jobs = await Promise.all(entityIds.map((eid) => fetchJobSummary(eid, tenantId, organizationId)))\n\n const items: any[] = []\n for (let idx = 0; idx < entityIds.length; idx += 1) {\n const eid = entityIds[idx]\n let coverage = coverageSnapshots[idx]\n\n const refreshedAt = coverage?.refreshed_at instanceof Date ? coverage.refreshed_at : coverage?.refreshed_at ? new Date(coverage.refreshed_at) : null\n const isStale = !coverage || !refreshedAt || (Date.now() - refreshedAt.getTime() > COVERAGE_STALE_MS)\n if (isStale) entitiesNeedingRefresh.add(eid)\n\n const job = jobs[idx]\n const label = (byId.get(eid)?.label) || eid\n const baseCountNumber = normalizeCount(coverage?.baseCount)\n const indexCountNumber = normalizeCount(coverage?.indexedCount)\n const vectorEnabled = vectorEnabledEntities.has(eid)\n const vectorCountNumber = vectorEnabled ? normalizeCount((coverage as any)?.vectorIndexedCount ?? (coverage as any)?.vector_indexed_count) : null\n const fulltextEnabled = fulltextEnabledEntities.has(eid)\n const fulltextCountNumber = fulltextEnabled ? (fulltextEntityCounts?.[eid] ?? 0) : null\n const ok = (() => {\n if (baseCountNumber == null || indexCountNumber == null) return false\n if (baseCountNumber !== indexCountNumber) return false\n if (!vectorEnabled) return true\n return vectorCountNumber != null && vectorCountNumber === baseCountNumber\n })()\n items.push({\n entityId: eid,\n label,\n baseCount: baseCountNumber,\n indexCount: indexCountNumber,\n vectorCount: vectorEnabled ? vectorCountNumber : null,\n vectorEnabled,\n fulltextCount: fulltextCountNumber,\n fulltextEnabled,\n ok,\n job,\n refreshedAt: refreshedAt ?? null,\n })\n }\n\n if (!forceRefresh) {\n try {\n const eventBus = container.resolve('eventBus')\n if (entitiesNeedingRefresh.size > 0) {\n await Promise.all(\n Array.from(entitiesNeedingRefresh).map((entityId) =>\n eventBus\n .emitEvent('query_index.coverage.refresh', {\n entityType: entityId,\n tenantId: tenantId ?? null,\n organizationId,\n delayMs: 0,\n })\n .catch(() => undefined)\n )\n )\n }\n } catch {}\n }\n\n let errorQuery = db\n .selectFrom('indexer_error_logs' as any)\n .selectAll()\n if (tenantId != null) {\n errorQuery = errorQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n errorQuery = errorQuery.where('tenant_id' as any, 'is', null as any)\n }\n if (Array.isArray(organizationScopeIds) && organizationScopeIds.length) {\n errorQuery = errorQuery.where('organization_id' as any, 'in', organizationScopeIds)\n } else {\n errorQuery = errorQuery.where('organization_id' as any, 'is', null as any)\n }\n const errorRows = await errorQuery\n .orderBy('occurred_at' as any, 'desc')\n .limit(100)\n .execute() as Array<Record<string, any>>\n\n const errors = errorRows.map((row: any) => {\n const occurredAt = row.occurred_at instanceof Date ? row.occurred_at : row.occurred_at ? new Date(row.occurred_at) : null\n return {\n id: String(row.id),\n source: String(row.source ?? ''),\n handler: String(row.handler ?? ''),\n entityType: row.entity_type ?? null,\n recordId: row.record_id ?? null,\n tenantId: row.tenant_id ?? null,\n organizationId: row.organization_id ?? null,\n message: String(row.message ?? ''),\n stack: row.stack ?? null,\n payload: row.payload ?? null,\n occurredAt: occurredAt ? occurredAt.toISOString() : new Date().toISOString(),\n }\n })\n\n let logsQuery = db\n .selectFrom('indexer_status_logs' as any)\n .selectAll()\n if (tenantId != null) {\n logsQuery = logsQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n logsQuery = logsQuery.where('tenant_id' as any, 'is', null as any)\n }\n if (Array.isArray(organizationScopeIds) && organizationScopeIds.length) {\n logsQuery = logsQuery.where('organization_id' as any, 'in', organizationScopeIds)\n } else {\n logsQuery = logsQuery.where('organization_id' as any, 'is', null as any)\n }\n const logRows = await logsQuery\n .orderBy('occurred_at' as any, 'desc')\n .limit(100)\n .execute() as Array<Record<string, any>>\n\n const logs = logRows.map((row: any) => {\n const occurredAt = row.occurred_at instanceof Date ? row.occurred_at : row.occurred_at ? new Date(row.occurred_at) : null\n const level = row.level === 'warn' ? 'warn' : 'info'\n return {\n id: String(row.id),\n source: String(row.source ?? ''),\n handler: String(row.handler ?? ''),\n level,\n entityType: row.entity_type ?? null,\n recordId: row.record_id ?? null,\n tenantId: row.tenant_id ?? null,\n organizationId: row.organization_id ?? null,\n message: String(row.message ?? ''),\n details: row.details ?? null,\n occurredAt: occurredAt ? occurredAt.toISOString() : new Date().toISOString(),\n }\n })\n\n const response = NextResponse.json({ items, errors, logs })\n const partial = items.find((item) => {\n // Coverage not computed yet (no snapshot) \u2014 pending an async refresh, not a partial\n // index. Do not raise the partial-index warning while counts are still unknown.\n if (item.baseCount == null && item.indexCount == null) return false\n if (item.baseCount == null || item.indexCount == null) return true\n return item.baseCount !== item.indexCount\n })\n if (partial) {\n response.headers.set(\n 'x-om-partial-index',\n JSON.stringify({\n type: 'partial_index',\n entity: partial.entityId,\n entityLabel: partial.label ?? partial.entityId,\n baseCount: partial.baseCount,\n indexedCount: partial.indexCount,\n scope: organizationId,\n })\n )\n }\n return response\n}\n\nconst queryIndexStatusDoc: OpenApiMethodDoc = {\n summary: 'Inspect query index coverage',\n description: 'Returns entity counts comparing base tables with the query index along with the latest job status.',\n tags: [queryIndexTag],\n responses: [\n { status: 200, description: 'Current query index status.', schema: queryIndexStatusResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Tenant or organization context required', schema: queryIndexErrorSchema },\n { status: 401, description: 'Authentication required', schema: queryIndexErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: queryIndexTag,\n summary: 'Query index status',\n methods: {\n GET: queryIndexStatusDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,7 +7,12 @@ import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
7
7
  import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
8
8
  import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
9
9
  import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
10
- import { upsertIndexBatch } from "./lib/batch.js";
10
+ import {
11
+ upsertIndexBatch,
12
+ assertIndexBatchWritesLanded,
13
+ createEmptyUpsertIndexBatchResult,
14
+ mergeUpsertIndexBatchResults
15
+ } from "./lib/batch.js";
11
16
  import { reindexEntity, DEFAULT_REINDEX_PARTITIONS } from "./lib/reindexer.js";
12
17
  import { purgeIndexScope } from "./lib/purge.js";
13
18
  import { flattenSystemEntityIds } from "@open-mercato/shared/lib/entities/system-entities";
@@ -136,29 +141,21 @@ async function rebuildEntityIndexes(options) {
136
141
  const encryption = resolveTenantEncryptionService(em);
137
142
  const dekKeyCache = /* @__PURE__ */ new Map();
138
143
  const encryptDoc = async (targetEntity, doc, scope) => {
139
- try {
140
- return await encryptIndexDocForStorage(
141
- targetEntity,
142
- doc,
143
- { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },
144
- encryption
145
- );
146
- } catch {
147
- return doc;
148
- }
144
+ return await encryptIndexDocForStorage(
145
+ targetEntity,
146
+ doc,
147
+ { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },
148
+ encryption
149
+ );
149
150
  };
150
151
  const decryptDoc = async (targetEntity, doc, scope) => {
151
- try {
152
- return await decryptIndexDocForSearch(
153
- targetEntity,
154
- doc,
155
- { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },
156
- encryption,
157
- dekKeyCache
158
- );
159
- } catch {
160
- return doc;
161
- }
152
+ return await decryptIndexDocForSearch(
153
+ targetEntity,
154
+ doc,
155
+ { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },
156
+ encryption,
157
+ dekKeyCache
158
+ );
162
159
  };
163
160
  const applyFilters = (q) => {
164
161
  let chain = q;
@@ -181,10 +178,11 @@ async function rebuildEntityIndexes(options) {
181
178
  ).where("id", "=", recordId).executeTakeFirst();
182
179
  if (!row) return { processed: 0, matched: 0 };
183
180
  const bar2 = createProgressBar(progressLabel ?? `Rebuilding ${entityType}`, 1);
184
- await upsertIndexBatch(db, entityType, [row], { orgId: orgOverride, tenantId: tenantOverride }, { encryptDoc, decryptDoc });
185
- bar2.update(1);
181
+ const singleResult = await upsertIndexBatch(db, entityType, [row], { orgId: orgOverride, tenantId: tenantOverride }, { encryptDoc, decryptDoc });
182
+ bar2.update(singleResult.written);
186
183
  bar2.complete();
187
- return { processed: 1, matched: 1 };
184
+ assertIndexBatchWritesLanded(entityType, singleResult);
185
+ return { processed: singleResult.written, matched: 1 };
188
186
  }
189
187
  const countRow = await applyFilters(
190
188
  db.selectFrom(tableName).select(sql`count(*)`.as("count"))
@@ -200,19 +198,23 @@ async function rebuildEntityIndexes(options) {
200
198
  }
201
199
  const bar = createProgressBar(progressLabel ?? `Rebuilding ${entityType}`, intended);
202
200
  let processed = 0;
201
+ let fetched = 0;
203
202
  let cursorOffset = effectiveOffset;
204
203
  let remaining = limitValue;
205
- while (processed < intended) {
204
+ const writeTotals = createEmptyUpsertIndexBatchResult();
205
+ while (fetched < intended) {
206
206
  const chunkLimit = remaining !== void 0 ? Math.min(batchSize, remaining) : batchSize;
207
207
  const chunk = await applyFilters(
208
208
  db.selectFrom(tableName).selectAll().orderBy("id").limit(chunkLimit).offset(cursorOffset)
209
209
  ).execute();
210
210
  if (!chunk.length) break;
211
- await upsertIndexBatch(db, entityType, chunk, {
211
+ const chunkResult = await upsertIndexBatch(db, entityType, chunk, {
212
212
  orgId: orgOverride,
213
213
  tenantId: tenantOverride
214
214
  }, { encryptDoc, decryptDoc });
215
- processed += chunk.length;
215
+ mergeUpsertIndexBatchResults(writeTotals, chunkResult);
216
+ processed += chunkResult.written;
217
+ fetched += chunk.length;
216
218
  cursorOffset += chunk.length;
217
219
  if (remaining !== void 0) remaining -= chunk.length;
218
220
  bar.update(processed);
@@ -222,6 +224,7 @@ async function rebuildEntityIndexes(options) {
222
224
  bar.update(processed);
223
225
  }
224
226
  bar.complete();
227
+ assertIndexBatchWritesLanded(entityType, writeTotals);
225
228
  return { processed, matched: intended };
226
229
  }
227
230
  async function getColumnSet(db, tableName) {