@open-mercato/core 0.6.6-develop.6256.1.9fc16aedc4 → 0.6.6-develop.6286.1.d985c5c374
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/carrier_shipment_idempotency_key/index.js +19 -0
- package/dist/generated/entities/carrier_shipment_idempotency_key/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +10 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/lib/productResolution.js +45 -0
- package/dist/modules/catalog/lib/productResolution.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +1 -1
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +2 -2
- package/dist/modules/currencies/api/fetch-configs/route.js +34 -0
- package/dist/modules/currencies/api/fetch-configs/route.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +17 -15
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +176 -161
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +75 -59
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +17 -10
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +6 -4
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js +5 -2
- package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +19 -21
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +1 -1
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +9 -45
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +11 -17
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +12 -10
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/lib/id-mapping.js +46 -1
- package/dist/modules/data_sync/lib/id-mapping.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/syncRunStatus.js +30 -0
- package/dist/modules/data_sync/lib/syncRunStatus.js.map +7 -0
- package/dist/modules/entities/api/records.js +6 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/feature_toggles/api/global/route.js +17 -15
- package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
- package/dist/modules/feature_toggles/api/openapi.js +2 -2
- package/dist/modules/feature_toggles/api/openapi.js.map +2 -2
- package/dist/modules/feature_toggles/commands/global.js +14 -0
- package/dist/modules/feature_toggles/commands/global.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +6 -0
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/archive/route.js +25 -7
- package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/conversation/read/route.js +24 -6
- package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +13 -1
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/openapi.js +3 -1
- package/dist/modules/messages/api/openapi.js.map +2 -2
- package/dist/modules/messages/commands/conversation.js +77 -0
- package/dist/modules/messages/commands/conversation.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +16 -4
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +3 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +29 -4
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +10 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +13 -11
- package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +5 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +5 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/refund/route.js +5 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +52 -25
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/payment_gateways/lib/status-machine.js +13 -0
- package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +8 -2
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/perspectives/data/entities.js +33 -3
- package/dist/modules/perspectives/data/entities.js.map +2 -2
- package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js +63 -0
- package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js.map +7 -0
- package/dist/modules/perspectives/services/perspectiveService.js +17 -0
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/planner/commands/availability-weekly.js +33 -1
- package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +14 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/progress/api/active/route.js +1 -1
- package/dist/modules/progress/api/active/route.js.map +2 -2
- package/dist/modules/progress/lib/progressService.js.map +1 -1
- package/dist/modules/progress/lib/progressServiceImpl.js +33 -8
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/query_index/api/status.js +19 -24
- package/dist/modules/query_index/api/status.js.map +3 -3
- package/dist/modules/query_index/lib/coverage.js +35 -0
- package/dist/modules/query_index/lib/coverage.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +11 -6
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/resources/api/resource-types.js +5 -37
- package/dist/modules/resources/api/resource-types.js.map +2 -2
- package/dist/modules/resources/api/resourceTypeCounts.js +35 -0
- package/dist/modules/resources/api/resourceTypeCounts.js.map +7 -0
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +1 -1
- package/dist/modules/resources/backend/resources/resource-types/page.js +2 -1
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnDialog.js +4 -1
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +17 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/di.js +4 -0
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/sales/services/salesOrderService.js +26 -0
- package/dist/modules/sales/services/salesOrderService.js.map +7 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js +8 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/data/entities.js +38 -0
- package/dist/modules/shipping_carriers/data/entities.js.map +2 -2
- package/dist/modules/shipping_carriers/data/validators.js +2 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-idempotency.js +76 -0
- package/dist/modules/shipping_carriers/lib/shipment-idempotency.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js +2 -1
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +20 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +82 -43
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js +14 -0
- package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +15 -6
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +9 -18
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/generated/entities/carrier_shipment_idempotency_key/index.ts +8 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +10 -0
- package/package.json +9 -8
- package/src/modules/catalog/lib/productResolution.ts +75 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +1 -1
- package/src/modules/currencies/api/fetch-configs/route.ts +36 -0
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +20 -17
- package/src/modules/currencies/i18n/de.json +2 -1
- package/src/modules/currencies/i18n/en.json +2 -1
- package/src/modules/currencies/i18n/es.json +2 -1
- package/src/modules/currencies/i18n/pl.json +2 -1
- package/src/modules/customers/api/companies/[id]/route.ts +214 -189
- package/src/modules/customers/api/people/[id]/route.ts +106 -80
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +19 -12
- package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +8 -6
- package/src/modules/customers/components/detail/AiActionChips.tsx +6 -3
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +16 -18
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +11 -58
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +13 -20
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +10 -8
- package/src/modules/data_sync/lib/id-mapping.ts +58 -1
- package/src/modules/data_sync/lib/sync-engine.ts +2 -2
- package/src/modules/data_sync/lib/syncRunStatus.ts +39 -0
- package/src/modules/entities/api/records.ts +14 -0
- package/src/modules/feature_toggles/api/global/route.ts +16 -14
- package/src/modules/feature_toggles/api/openapi.ts +2 -2
- package/src/modules/feature_toggles/commands/global.ts +15 -0
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +7 -0
- package/src/modules/messages/api/[id]/conversation/archive/route.ts +29 -6
- package/src/modules/messages/api/[id]/conversation/read/route.ts +29 -6
- package/src/modules/messages/api/[id]/route.ts +15 -0
- package/src/modules/messages/api/openapi.ts +2 -0
- package/src/modules/messages/commands/conversation.ts +87 -0
- package/src/modules/messages/components/MessageDetailPageClient.tsx +16 -4
- package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +2 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +39 -4
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +15 -1
- package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +29 -14
- package/src/modules/messages/components/message-detail/types.ts +2 -0
- package/src/modules/messages/i18n/de.json +4 -0
- package/src/modules/messages/i18n/en.json +4 -0
- package/src/modules/messages/i18n/es.json +4 -0
- package/src/modules/messages/i18n/pl.json +4 -0
- package/src/modules/payment_gateways/api/cancel/route.ts +5 -0
- package/src/modules/payment_gateways/api/capture/route.ts +5 -0
- package/src/modules/payment_gateways/api/refund/route.ts +5 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +58 -25
- package/src/modules/payment_gateways/lib/status-machine.ts +16 -0
- package/src/modules/perspectives/api/[tableId]/route.ts +8 -2
- package/src/modules/perspectives/data/entities.ts +43 -3
- package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +69 -25
- package/src/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.ts +65 -0
- package/src/modules/perspectives/services/perspectiveService.ts +17 -0
- package/src/modules/planner/commands/availability-weekly.ts +48 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +31 -10
- package/src/modules/progress/api/active/route.ts +1 -1
- package/src/modules/progress/lib/progressService.ts +2 -2
- package/src/modules/progress/lib/progressServiceImpl.ts +33 -8
- package/src/modules/query_index/api/status.ts +28 -32
- package/src/modules/query_index/lib/coverage.ts +63 -0
- package/src/modules/query_index/lib/engine.ts +37 -24
- package/src/modules/resources/api/resource-types.ts +4 -44
- package/src/modules/resources/api/resourceTypeCounts.ts +74 -0
- package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
- package/src/modules/resources/backend/resources/resource-types/page.tsx +1 -0
- package/src/modules/sales/components/documents/ReturnDialog.tsx +4 -1
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +20 -2
- package/src/modules/sales/di.ts +6 -0
- package/src/modules/sales/services/salesOrderService.ts +38 -0
- package/src/modules/shipping_carriers/api/shipments/route.ts +8 -0
- package/src/modules/shipping_carriers/data/entities.ts +33 -0
- package/src/modules/shipping_carriers/data/validators.ts +1 -0
- package/src/modules/shipping_carriers/i18n/de.json +1 -0
- package/src/modules/shipping_carriers/i18n/en.json +1 -0
- package/src/modules/shipping_carriers/i18n/es.json +1 -0
- package/src/modules/shipping_carriers/i18n/pl.json +1 -0
- package/src/modules/shipping_carriers/lib/shipment-idempotency.ts +119 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts +3 -1
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +24 -2
- package/src/modules/shipping_carriers/lib/shipping-service.ts +87 -43
- package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +197 -0
- package/src/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.ts +14 -0
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +15 -7
- package/src/modules/translations/components/TranslationManager.tsx +17 -24
- package/src/modules/translations/i18n/de.json +1 -0
- package/src/modules/translations/i18n/en.json +1 -0
- package/src/modules/translations/i18n/es.json +1 -0
- package/src/modules/translations/i18n/pl.json +1 -0
|
@@ -13,7 +13,7 @@ async function GET(req) {
|
|
|
13
13
|
const container = await createRequestContainer();
|
|
14
14
|
const progressService = container.resolve("progressService");
|
|
15
15
|
const ctx = { tenantId: auth.tenantId, organizationId: auth.orgId };
|
|
16
|
-
await progressService.markStaleJobsFailed(auth.tenantId);
|
|
16
|
+
await progressService.markStaleJobsFailed(auth.tenantId, void 0, auth.orgId);
|
|
17
17
|
const [jobs, recentlyCompleted] = await Promise.all([
|
|
18
18
|
progressService.getActiveJobs(ctx),
|
|
19
19
|
progressService.getRecentlyCompletedJobs(ctx)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/progress/api/active/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { ProgressJob } from '../../data/entities'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ active: [], recentlyCompleted: [] }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const ctx = { tenantId: auth.tenantId, organizationId: auth.orgId }\n\n await progressService.markStaleJobsFailed(auth.tenantId)\n\n const [jobs, recentlyCompleted] = await Promise.all([\n progressService.getActiveJobs(ctx),\n progressService.getRecentlyCompletedJobs(ctx),\n ])\n\n return NextResponse.json({\n active: jobs.map(formatJob),\n recentlyCompleted: recentlyCompleted.map(formatJob),\n })\n}\n\nfunction formatJob(job: ProgressJob) {\n return {\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta ?? null,\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n errorMessage: job.errorMessage,\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get active progress jobs',\n description: 'Returns currently running jobs and recently completed jobs for the progress top bar.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Active and recently completed jobs' },\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEO,MAAM,WAAW;AAExB,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,WAAO,aAAa,KAAK,EAAE,QAAQ,CAAC,GAAG,mBAAmB,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,kBAAkB,UAAU,QAAQ,iBAAiB;AAE3D,QAAM,MAAM,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM;AAElE,QAAM,gBAAgB,oBAAoB,KAAK,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { ProgressJob } from '../../data/entities'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ active: [], recentlyCompleted: [] }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const ctx = { tenantId: auth.tenantId, organizationId: auth.orgId }\n\n await progressService.markStaleJobsFailed(auth.tenantId, undefined, auth.orgId)\n\n const [jobs, recentlyCompleted] = await Promise.all([\n progressService.getActiveJobs(ctx),\n progressService.getRecentlyCompletedJobs(ctx),\n ])\n\n return NextResponse.json({\n active: jobs.map(formatJob),\n recentlyCompleted: recentlyCompleted.map(formatJob),\n })\n}\n\nfunction formatJob(job: ProgressJob) {\n return {\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta ?? null,\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n errorMessage: job.errorMessage,\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get active progress jobs',\n description: 'Returns currently running jobs and recently completed jobs for the progress top bar.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Active and recently completed jobs' },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEO,MAAM,WAAW;AAExB,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,WAAO,aAAa,KAAK,EAAE,QAAQ,CAAC,GAAG,mBAAmB,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,kBAAkB,UAAU,QAAQ,iBAAiB;AAE3D,QAAM,MAAM,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM;AAElE,QAAM,gBAAgB,oBAAoB,KAAK,UAAU,QAAW,KAAK,KAAK;AAE9E,QAAM,CAAC,MAAM,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClD,gBAAgB,cAAc,GAAG;AAAA,IACjC,gBAAgB,yBAAyB,GAAG;AAAA,EAC9C,CAAC;AAED,SAAO,aAAa,KAAK;AAAA,IACvB,QAAQ,KAAK,IAAI,SAAS;AAAA,IAC1B,mBAAmB,kBAAkB,IAAI,SAAS;AAAA,EACpD,CAAC;AACH;AAEA,SAAS,UAAU,KAAkB;AACnC,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,SAAS,IAAI;AAAA,IACb,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,gBAAgB,IAAI;AAAA,IACpB,YAAY,IAAI;AAAA,IAChB,YAAY,IAAI;AAAA,IAChB,aAAa,IAAI;AAAA,IACjB,MAAM,IAAI,QAAQ;AAAA,IAClB,WAAW,IAAI,WAAW,YAAY,KAAK;AAAA,IAC3C,YAAY,IAAI,YAAY,YAAY,KAAK;AAAA,IAC7C,cAAc,IAAI;AAAA,EACpB;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,aAAa;AAAA,IACb,MAAM,CAAC,UAAU;AAAA,IACjB,WAAW;AAAA,MACT,KAAK,EAAE,aAAa,qCAAqC;AAAA,IAC3D;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/progress/lib/progressService.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ProgressJob } from '../data/entities'\nimport type { CreateProgressJobInput, UpdateProgressInput, CompleteJobInput, FailJobInput } from '../data/validators'\n\nexport interface ProgressServiceContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface ProgressService {\n createJob(input: CreateProgressJobInput, ctx: ProgressServiceContext): Promise<ProgressJob>\n startJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>\n updateProgress(jobId: string, input: UpdateProgressInput, ctx: ProgressServiceContext): Promise<ProgressJob>\n incrementProgress(jobId: string, delta: number, ctx: ProgressServiceContext): Promise<ProgressJob>\n completeJob(jobId: string, input: CompleteJobInput | undefined, ctx: ProgressServiceContext): Promise<ProgressJob>\n failJob(jobId: string, input: FailJobInput, ctx: ProgressServiceContext): Promise<ProgressJob>\n cancelJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>\n markCancelled(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>\n isCancellationRequested(jobId: string, tenantId: string): Promise<boolean>\n getActiveJobs(ctx: ProgressServiceContext): Promise<ProgressJob[]>\n getRecentlyCompletedJobs(ctx: ProgressServiceContext, sinceSeconds?: number): Promise<ProgressJob[]>\n getJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob | null>\n markStaleJobsFailed(tenantId: string, timeoutSeconds?: number): Promise<number>\n}\n\nexport const HEARTBEAT_INTERVAL_MS = 5000\nexport const STALE_JOB_TIMEOUT_SECONDS = 60\n\nexport function calculateEta(\n processedCount: number,\n totalCount: number,\n startedAt: Date,\n): number | null {\n if (processedCount === 0 || totalCount === 0) return null\n\n const elapsedMs = Date.now() - startedAt.getTime()\n const rate = processedCount / elapsedMs\n const remaining = totalCount - processedCount\n\n if (rate <= 0) return null\n\n return Math.ceil(remaining / rate / 1000)\n}\n\nexport function calculateProgressPercent(processedCount: number, totalCount: number | null): number {\n if (!totalCount || totalCount <= 0) return 0\n return Math.min(100, Math.round((processedCount / totalCount) * 100))\n}\n"],
|
|
4
|
+
"sourcesContent": ["import type { ProgressJob } from '../data/entities'\nimport type { CreateProgressJobInput, UpdateProgressInput, CompleteJobInput, FailJobInput } from '../data/validators'\n\nexport interface ProgressServiceContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface ProgressService {\n createJob(input: CreateProgressJobInput, ctx: ProgressServiceContext): Promise<ProgressJob>\n startJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>\n updateProgress(jobId: string, input: UpdateProgressInput, ctx: ProgressServiceContext): Promise<ProgressJob>\n incrementProgress(jobId: string, delta: number, ctx: ProgressServiceContext): Promise<ProgressJob>\n completeJob(jobId: string, input: CompleteJobInput | undefined, ctx: ProgressServiceContext): Promise<ProgressJob>\n failJob(jobId: string, input: FailJobInput, ctx: ProgressServiceContext): Promise<ProgressJob>\n cancelJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>\n markCancelled(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>\n isCancellationRequested(jobId: string, tenantId: string, organizationId?: string | null): Promise<boolean>\n getActiveJobs(ctx: ProgressServiceContext): Promise<ProgressJob[]>\n getRecentlyCompletedJobs(ctx: ProgressServiceContext, sinceSeconds?: number): Promise<ProgressJob[]>\n getJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob | null>\n markStaleJobsFailed(tenantId: string, timeoutSeconds?: number, organizationId?: string | null): Promise<number>\n}\n\nexport const HEARTBEAT_INTERVAL_MS = 5000\nexport const STALE_JOB_TIMEOUT_SECONDS = 60\n\nexport function calculateEta(\n processedCount: number,\n totalCount: number,\n startedAt: Date,\n): number | null {\n if (processedCount === 0 || totalCount === 0) return null\n\n const elapsedMs = Date.now() - startedAt.getTime()\n const rate = processedCount / elapsedMs\n const remaining = totalCount - processedCount\n\n if (rate <= 0) return null\n\n return Math.ceil(remaining / rate / 1000)\n}\n\nexport function calculateProgressPercent(processedCount: number, totalCount: number | null): number {\n if (!totalCount || totalCount <= 0) return 0\n return Math.min(100, Math.round((processedCount / totalCount) * 100))\n}\n"],
|
|
5
5
|
"mappings": "AAyBO,MAAM,wBAAwB;AAC9B,MAAM,4BAA4B;AAElC,SAAS,aACd,gBACA,YACA,WACe;AACf,MAAI,mBAAmB,KAAK,eAAe,EAAG,QAAO;AAErD,QAAM,YAAY,KAAK,IAAI,IAAI,UAAU,QAAQ;AACjD,QAAM,OAAO,iBAAiB;AAC9B,QAAM,YAAY,aAAa;AAE/B,MAAI,QAAQ,EAAG,QAAO;AAEtB,SAAO,KAAK,KAAK,YAAY,OAAO,GAAI;AAC1C;AAEO,SAAS,yBAAyB,gBAAwB,YAAmC;AAClG,MAAI,CAAC,cAAc,cAAc,EAAG,QAAO;AAC3C,SAAO,KAAK,IAAI,KAAK,KAAK,MAAO,iBAAiB,aAAc,GAAG,CAAC;AACtE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -46,7 +46,11 @@ function createProgressService(em, eventBus) {
|
|
|
46
46
|
return job;
|
|
47
47
|
},
|
|
48
48
|
async startJob(jobId, ctx) {
|
|
49
|
-
const job = await em.findOneOrFail(ProgressJob, {
|
|
49
|
+
const job = await em.findOneOrFail(ProgressJob, {
|
|
50
|
+
id: jobId,
|
|
51
|
+
tenantId: ctx.tenantId,
|
|
52
|
+
...ctx.organizationId ? { organizationId: ctx.organizationId } : {}
|
|
53
|
+
});
|
|
50
54
|
if (job.status === "cancelled") {
|
|
51
55
|
return job;
|
|
52
56
|
}
|
|
@@ -99,7 +103,11 @@ function createProgressService(em, eventBus) {
|
|
|
99
103
|
return job;
|
|
100
104
|
},
|
|
101
105
|
async incrementProgress(jobId, delta, ctx) {
|
|
102
|
-
const job = await em.findOneOrFail(ProgressJob, {
|
|
106
|
+
const job = await em.findOneOrFail(ProgressJob, {
|
|
107
|
+
id: jobId,
|
|
108
|
+
tenantId: ctx.tenantId,
|
|
109
|
+
...ctx.organizationId ? { organizationId: ctx.organizationId } : {}
|
|
110
|
+
});
|
|
103
111
|
if (job.status === "completed" || job.status === "failed" || job.status === "cancelled") {
|
|
104
112
|
return job;
|
|
105
113
|
}
|
|
@@ -120,7 +128,11 @@ function createProgressService(em, eventBus) {
|
|
|
120
128
|
return job;
|
|
121
129
|
},
|
|
122
130
|
async completeJob(jobId, input, ctx) {
|
|
123
|
-
const job = await em.findOne(ProgressJob, {
|
|
131
|
+
const job = await em.findOne(ProgressJob, {
|
|
132
|
+
id: jobId,
|
|
133
|
+
tenantId: ctx.tenantId,
|
|
134
|
+
...ctx.organizationId ? { organizationId: ctx.organizationId } : {}
|
|
135
|
+
});
|
|
124
136
|
if (!job) throw new Error(`Job ${jobId} not found`);
|
|
125
137
|
if (job.status === "cancelled") {
|
|
126
138
|
return job;
|
|
@@ -142,7 +154,11 @@ function createProgressService(em, eventBus) {
|
|
|
142
154
|
return job;
|
|
143
155
|
},
|
|
144
156
|
async failJob(jobId, input, ctx) {
|
|
145
|
-
const job = await em.findOne(ProgressJob, {
|
|
157
|
+
const job = await em.findOne(ProgressJob, {
|
|
158
|
+
id: jobId,
|
|
159
|
+
tenantId: ctx.tenantId,
|
|
160
|
+
...ctx.organizationId ? { organizationId: ctx.organizationId } : {}
|
|
161
|
+
});
|
|
146
162
|
if (!job) throw new Error(`Job ${jobId} not found`);
|
|
147
163
|
if (job.status === "cancelled") {
|
|
148
164
|
return job;
|
|
@@ -183,7 +199,11 @@ function createProgressService(em, eventBus) {
|
|
|
183
199
|
return job;
|
|
184
200
|
},
|
|
185
201
|
async markCancelled(jobId, ctx) {
|
|
186
|
-
const job = await em.findOne(ProgressJob, {
|
|
202
|
+
const job = await em.findOne(ProgressJob, {
|
|
203
|
+
id: jobId,
|
|
204
|
+
tenantId: ctx.tenantId,
|
|
205
|
+
...ctx.organizationId ? { organizationId: ctx.organizationId } : {}
|
|
206
|
+
});
|
|
187
207
|
if (!job) throw new Error(`Job ${jobId} not found`);
|
|
188
208
|
if (job.status === "cancelled") {
|
|
189
209
|
return job;
|
|
@@ -201,8 +221,12 @@ function createProgressService(em, eventBus) {
|
|
|
201
221
|
});
|
|
202
222
|
return job;
|
|
203
223
|
},
|
|
204
|
-
async isCancellationRequested(jobId, tenantId) {
|
|
205
|
-
const job = await findOneWithDecryption(em, ProgressJob, {
|
|
224
|
+
async isCancellationRequested(jobId, tenantId, organizationId) {
|
|
225
|
+
const job = await findOneWithDecryption(em, ProgressJob, {
|
|
226
|
+
id: jobId,
|
|
227
|
+
tenantId,
|
|
228
|
+
...organizationId ? { organizationId } : {}
|
|
229
|
+
});
|
|
206
230
|
return job?.cancelRequestedAt != null;
|
|
207
231
|
},
|
|
208
232
|
async getActiveJobs(ctx) {
|
|
@@ -236,10 +260,11 @@ function createProgressService(em, eventBus) {
|
|
|
236
260
|
...ctx.organizationId ? { organizationId: ctx.organizationId } : {}
|
|
237
261
|
});
|
|
238
262
|
},
|
|
239
|
-
async markStaleJobsFailed(tenantId, timeoutSeconds = STALE_JOB_TIMEOUT_SECONDS) {
|
|
263
|
+
async markStaleJobsFailed(tenantId, timeoutSeconds = STALE_JOB_TIMEOUT_SECONDS, organizationId) {
|
|
240
264
|
const cutoff = new Date(Date.now() - timeoutSeconds * 1e3);
|
|
241
265
|
const staleJobs = await em.find(ProgressJob, {
|
|
242
266
|
tenantId,
|
|
267
|
+
...organizationId ? { organizationId } : {},
|
|
243
268
|
status: "running",
|
|
244
269
|
$or: [
|
|
245
270
|
{ heartbeatAt: { $lt: cutoff } },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/progress/lib/progressServiceImpl.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { ProgressJob } from '../data/entities'\nimport type { ProgressService } from './progressService'\nimport { calculateEta, calculateProgressPercent, STALE_JOB_TIMEOUT_SECONDS } from './progressService'\nimport { PROGRESS_EVENTS } from './events'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nfunction buildJobPayload(job: ProgressJob): Record<string, unknown> {\n return {\n jobId: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description ?? null,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount ?? null,\n etaSeconds: job.etaSeconds ?? null,\n cancellable: job.cancellable,\n meta: job.meta ?? null,\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n }\n}\n\nexport function createProgressService(em: EntityManager, eventBus: { emit: (event: string, payload: Record<string, unknown>) => Promise<void> }): ProgressService {\n return {\n async createJob(input, ctx) {\n const job = em.create(ProgressJob, {\n jobType: input.jobType,\n name: input.name,\n description: input.description,\n totalCount: input.totalCount,\n cancellable: input.cancellable ?? false,\n meta: input.meta,\n parentJobId: input.parentJobId,\n partitionIndex: input.partitionIndex,\n partitionCount: input.partitionCount,\n startedByUserId: ctx.userId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n status: 'pending',\n })\n\n await em.persist(job).flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_CREATED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n\n return job\n },\n\n async startJob(jobId, ctx) {\n const job = await em.findOneOrFail(ProgressJob, { id: jobId, tenantId: ctx.tenantId })\n if (job.status === 'cancelled') {\n return job\n }\n\n job.status = 'running'\n job.startedAt = new Date()\n job.heartbeatAt = new Date()\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_STARTED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async updateProgress(jobId, input, ctx) {\n const job = await em.findOneOrFail(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (job.status === 'completed' || job.status === 'failed' || job.status === 'cancelled') {\n return job\n }\n\n if (input.processedCount !== undefined) {\n job.processedCount = input.processedCount\n }\n if (input.totalCount !== undefined) {\n job.totalCount = input.totalCount\n }\n if (input.meta !== undefined) {\n job.meta = { ...job.meta, ...input.meta }\n }\n\n if (input.progressPercent !== undefined) {\n job.progressPercent = input.progressPercent\n } else if (job.totalCount) {\n job.progressPercent = calculateProgressPercent(job.processedCount, job.totalCount)\n }\n\n if (input.etaSeconds !== undefined) {\n job.etaSeconds = input.etaSeconds\n } else if (job.startedAt && job.totalCount) {\n job.etaSeconds = calculateEta(job.processedCount, job.totalCount, job.startedAt)\n }\n\n job.heartbeatAt = new Date()\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_UPDATED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async incrementProgress(jobId, delta, ctx) {\n const job = await em.findOneOrFail(ProgressJob, { id: jobId, tenantId: ctx.tenantId })\n if (job.status === 'completed' || job.status === 'failed' || job.status === 'cancelled') {\n return job\n }\n\n job.processedCount += delta\n job.heartbeatAt = new Date()\n\n if (job.totalCount) {\n job.progressPercent = calculateProgressPercent(job.processedCount, job.totalCount)\n if (job.startedAt) {\n job.etaSeconds = calculateEta(job.processedCount, job.totalCount, job.startedAt)\n }\n }\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_UPDATED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async completeJob(jobId, input, ctx) {\n const job = await em.findOne(ProgressJob, { id: jobId, tenantId: ctx.tenantId })\n if (!job) throw new Error(`Job ${jobId} not found`)\n if (job.status === 'cancelled') {\n return job\n }\n\n job.status = 'completed'\n job.finishedAt = new Date()\n job.progressPercent = 100\n job.etaSeconds = 0\n if (input?.resultSummary) {\n job.resultSummary = input.resultSummary\n }\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_COMPLETED, {\n ...buildJobPayload(job),\n resultSummary: job.resultSummary,\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async failJob(jobId, input, ctx) {\n const job = await em.findOne(ProgressJob, { id: jobId, tenantId: ctx.tenantId })\n if (!job) throw new Error(`Job ${jobId} not found`)\n if (job.status === 'cancelled') {\n return job\n }\n\n job.status = 'failed'\n job.finishedAt = new Date()\n job.errorMessage = input.errorMessage\n job.errorStack = input.errorStack\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_FAILED, {\n ...buildJobPayload(job),\n errorMessage: job.errorMessage,\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async cancelJob(jobId, ctx) {\n const job = await em.findOneOrFail(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n cancellable: true,\n status: { $in: ['pending', 'running'] },\n })\n\n job.cancelRequestedAt = new Date()\n job.cancelledByUserId = ctx.userId\n\n if (job.status === 'pending') {\n job.status = 'cancelled'\n job.finishedAt = new Date()\n }\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_CANCELLED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async markCancelled(jobId, ctx) {\n const job = await em.findOne(ProgressJob, { id: jobId, tenantId: ctx.tenantId })\n if (!job) throw new Error(`Job ${jobId} not found`)\n if (job.status === 'cancelled') {\n return job\n }\n\n job.cancelRequestedAt = job.cancelRequestedAt ?? new Date()\n job.cancelledByUserId = ctx.userId\n job.status = 'cancelled'\n job.finishedAt = job.finishedAt ?? new Date()\n job.etaSeconds = 0\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_CANCELLED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async isCancellationRequested(jobId, tenantId) {\n const job = await findOneWithDecryption(em, ProgressJob, { id: jobId, tenantId })\n return job?.cancelRequestedAt != null\n },\n\n async getActiveJobs(ctx) {\n return em.find(ProgressJob, {\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n status: { $in: ['pending', 'running'] },\n parentJobId: null,\n }, {\n orderBy: { createdAt: 'DESC' },\n limit: 50,\n })\n },\n\n async getRecentlyCompletedJobs(ctx, sinceSeconds = 30) {\n const cutoff = new Date(Date.now() - sinceSeconds * 1000)\n return em.find(ProgressJob, {\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n status: { $in: ['completed', 'failed'] },\n finishedAt: { $gte: cutoff },\n parentJobId: null,\n }, {\n orderBy: { finishedAt: 'DESC' },\n limit: 10,\n })\n },\n\n async getJob(jobId, ctx) {\n return em.findOne(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n },\n\n async markStaleJobsFailed(tenantId: string, timeoutSeconds = STALE_JOB_TIMEOUT_SECONDS) {\n const cutoff = new Date(Date.now() - timeoutSeconds * 1000)\n\n const staleJobs = await em.find(ProgressJob, {\n tenantId,\n status: 'running',\n $or: [\n { heartbeatAt: { $lt: cutoff } },\n {\n heartbeatAt: null,\n startedAt: { $lt: cutoff },\n },\n ],\n })\n\n for (const job of staleJobs) {\n job.status = 'failed'\n job.finishedAt = new Date()\n job.errorMessage = `Job stale: no heartbeat for ${timeoutSeconds} seconds`\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_FAILED, {\n ...buildJobPayload(job),\n errorMessage: job.errorMessage,\n tenantId: job.tenantId,\n stale: true,\n organizationId: job.organizationId ?? null,\n })\n }\n\n await em.flush()\n return staleJobs.length\n },\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,mBAAmB;AAE5B,SAAS,cAAc,0BAA0B,iCAAiC;AAClF,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AAEtC,SAAS,gBAAgB,KAA2C;AAClE,SAAO;AAAA,IACL,OAAO,IAAI;AAAA,IACX,SAAS,IAAI;AAAA,IACb,MAAM,IAAI;AAAA,IACV,aAAa,IAAI,eAAe;AAAA,IAChC,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,gBAAgB,IAAI;AAAA,IACpB,YAAY,IAAI,cAAc;AAAA,IAC9B,YAAY,IAAI,cAAc;AAAA,IAC9B,aAAa,IAAI;AAAA,IACjB,MAAM,IAAI,QAAQ;AAAA,IAClB,WAAW,IAAI,WAAW,YAAY,KAAK;AAAA,IAC3C,YAAY,IAAI,YAAY,YAAY,KAAK;AAAA,EAC/C;AACF;AAEO,SAAS,sBAAsB,IAAmB,UAAyG;AAChK,SAAO;AAAA,IACL,MAAM,UAAU,OAAO,KAAK;AAC1B,YAAM,MAAM,GAAG,OAAO,aAAa;AAAA,QACjC,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,aAAa,MAAM;AAAA,QACnB,YAAY,MAAM;AAAA,QAClB,aAAa,MAAM,eAAe;AAAA,QAClC,MAAM,MAAM;AAAA,QACZ,aAAa,MAAM;AAAA,QACnB,gBAAgB,MAAM;AAAA,QACtB,gBAAgB,MAAM;AAAA,QACtB,iBAAiB,IAAI;AAAA,QACrB,UAAU,IAAI;AAAA,QACd,gBAAgB,IAAI;AAAA,QACpB,QAAQ;AAAA,MACV,CAAC;AAED,YAAM,GAAG,QAAQ,GAAG,EAAE,MAAM;AAE5B,YAAM,SAAS,KAAK,gBAAgB,aAAa;AAAA,QAC/C,GAAG,gBAAgB,GAAG;AAAA,QACtB,UAAU,IAAI;AAAA,QACd,gBAAgB,IAAI;AAAA,MACtB,CAAC;AAED,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,SAAS,OAAO,KAAK;AACzB,YAAM,MAAM,MAAM,GAAG,cAAc,aAAa,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { ProgressJob } from '../data/entities'\nimport type { ProgressService } from './progressService'\nimport { calculateEta, calculateProgressPercent, STALE_JOB_TIMEOUT_SECONDS } from './progressService'\nimport { PROGRESS_EVENTS } from './events'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nfunction buildJobPayload(job: ProgressJob): Record<string, unknown> {\n return {\n jobId: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description ?? null,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount ?? null,\n etaSeconds: job.etaSeconds ?? null,\n cancellable: job.cancellable,\n meta: job.meta ?? null,\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n }\n}\n\nexport function createProgressService(em: EntityManager, eventBus: { emit: (event: string, payload: Record<string, unknown>) => Promise<void> }): ProgressService {\n return {\n async createJob(input, ctx) {\n const job = em.create(ProgressJob, {\n jobType: input.jobType,\n name: input.name,\n description: input.description,\n totalCount: input.totalCount,\n cancellable: input.cancellable ?? false,\n meta: input.meta,\n parentJobId: input.parentJobId,\n partitionIndex: input.partitionIndex,\n partitionCount: input.partitionCount,\n startedByUserId: ctx.userId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n status: 'pending',\n })\n\n await em.persist(job).flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_CREATED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n\n return job\n },\n\n async startJob(jobId, ctx) {\n const job = await em.findOneOrFail(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (job.status === 'cancelled') {\n return job\n }\n\n job.status = 'running'\n job.startedAt = new Date()\n job.heartbeatAt = new Date()\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_STARTED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async updateProgress(jobId, input, ctx) {\n const job = await em.findOneOrFail(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (job.status === 'completed' || job.status === 'failed' || job.status === 'cancelled') {\n return job\n }\n\n if (input.processedCount !== undefined) {\n job.processedCount = input.processedCount\n }\n if (input.totalCount !== undefined) {\n job.totalCount = input.totalCount\n }\n if (input.meta !== undefined) {\n job.meta = { ...job.meta, ...input.meta }\n }\n\n if (input.progressPercent !== undefined) {\n job.progressPercent = input.progressPercent\n } else if (job.totalCount) {\n job.progressPercent = calculateProgressPercent(job.processedCount, job.totalCount)\n }\n\n if (input.etaSeconds !== undefined) {\n job.etaSeconds = input.etaSeconds\n } else if (job.startedAt && job.totalCount) {\n job.etaSeconds = calculateEta(job.processedCount, job.totalCount, job.startedAt)\n }\n\n job.heartbeatAt = new Date()\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_UPDATED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async incrementProgress(jobId, delta, ctx) {\n const job = await em.findOneOrFail(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (job.status === 'completed' || job.status === 'failed' || job.status === 'cancelled') {\n return job\n }\n\n job.processedCount += delta\n job.heartbeatAt = new Date()\n\n if (job.totalCount) {\n job.progressPercent = calculateProgressPercent(job.processedCount, job.totalCount)\n if (job.startedAt) {\n job.etaSeconds = calculateEta(job.processedCount, job.totalCount, job.startedAt)\n }\n }\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_UPDATED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async completeJob(jobId, input, ctx) {\n const job = await em.findOne(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (!job) throw new Error(`Job ${jobId} not found`)\n if (job.status === 'cancelled') {\n return job\n }\n\n job.status = 'completed'\n job.finishedAt = new Date()\n job.progressPercent = 100\n job.etaSeconds = 0\n if (input?.resultSummary) {\n job.resultSummary = input.resultSummary\n }\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_COMPLETED, {\n ...buildJobPayload(job),\n resultSummary: job.resultSummary,\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async failJob(jobId, input, ctx) {\n const job = await em.findOne(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (!job) throw new Error(`Job ${jobId} not found`)\n if (job.status === 'cancelled') {\n return job\n }\n\n job.status = 'failed'\n job.finishedAt = new Date()\n job.errorMessage = input.errorMessage\n job.errorStack = input.errorStack\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_FAILED, {\n ...buildJobPayload(job),\n errorMessage: job.errorMessage,\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async cancelJob(jobId, ctx) {\n const job = await em.findOneOrFail(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n cancellable: true,\n status: { $in: ['pending', 'running'] },\n })\n\n job.cancelRequestedAt = new Date()\n job.cancelledByUserId = ctx.userId\n\n if (job.status === 'pending') {\n job.status = 'cancelled'\n job.finishedAt = new Date()\n }\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_CANCELLED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async markCancelled(jobId, ctx) {\n const job = await em.findOne(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n if (!job) throw new Error(`Job ${jobId} not found`)\n if (job.status === 'cancelled') {\n return job\n }\n\n job.cancelRequestedAt = job.cancelRequestedAt ?? new Date()\n job.cancelledByUserId = ctx.userId\n job.status = 'cancelled'\n job.finishedAt = job.finishedAt ?? new Date()\n job.etaSeconds = 0\n\n await em.flush()\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_CANCELLED, {\n ...buildJobPayload(job),\n tenantId: ctx.tenantId,\n organizationId: job.organizationId ?? null,\n })\n\n return job\n },\n\n async isCancellationRequested(jobId, tenantId, organizationId) {\n const job = await findOneWithDecryption(em, ProgressJob, {\n id: jobId,\n tenantId,\n ...(organizationId ? { organizationId } : {}),\n })\n return job?.cancelRequestedAt != null\n },\n\n async getActiveJobs(ctx) {\n return em.find(ProgressJob, {\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n status: { $in: ['pending', 'running'] },\n parentJobId: null,\n }, {\n orderBy: { createdAt: 'DESC' },\n limit: 50,\n })\n },\n\n async getRecentlyCompletedJobs(ctx, sinceSeconds = 30) {\n const cutoff = new Date(Date.now() - sinceSeconds * 1000)\n return em.find(ProgressJob, {\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n status: { $in: ['completed', 'failed'] },\n finishedAt: { $gte: cutoff },\n parentJobId: null,\n }, {\n orderBy: { finishedAt: 'DESC' },\n limit: 10,\n })\n },\n\n async getJob(jobId, ctx) {\n return em.findOne(ProgressJob, {\n id: jobId,\n tenantId: ctx.tenantId,\n ...(ctx.organizationId ? { organizationId: ctx.organizationId } : {}),\n })\n },\n\n async markStaleJobsFailed(tenantId: string, timeoutSeconds = STALE_JOB_TIMEOUT_SECONDS, organizationId?: string | null) {\n const cutoff = new Date(Date.now() - timeoutSeconds * 1000)\n\n const staleJobs = await em.find(ProgressJob, {\n tenantId,\n ...(organizationId ? { organizationId } : {}),\n status: 'running',\n $or: [\n { heartbeatAt: { $lt: cutoff } },\n {\n heartbeatAt: null,\n startedAt: { $lt: cutoff },\n },\n ],\n })\n\n for (const job of staleJobs) {\n job.status = 'failed'\n job.finishedAt = new Date()\n job.errorMessage = `Job stale: no heartbeat for ${timeoutSeconds} seconds`\n\n await eventBus.emit(PROGRESS_EVENTS.JOB_FAILED, {\n ...buildJobPayload(job),\n errorMessage: job.errorMessage,\n tenantId: job.tenantId,\n stale: true,\n organizationId: job.organizationId ?? null,\n })\n }\n\n await em.flush()\n return staleJobs.length\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,8 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { getEntityIds } from "@open-mercato/shared/lib/encryption/entityIds";
|
|
5
5
|
import { sql } from "kysely";
|
|
6
|
-
import {
|
|
6
|
+
import { readCoverageSnapshots, refreshCoverageSnapshot } from "../lib/coverage.js";
|
|
7
|
+
import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decrypt";
|
|
7
8
|
import { queryIndexTag, queryIndexErrorSchema, queryIndexStatusResponseSchema } from "./openapi.js";
|
|
8
9
|
import { flattenSystemEntityIds } from "@open-mercato/shared/lib/entities/system-entities";
|
|
9
10
|
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
@@ -102,6 +103,7 @@ async function GET(req) {
|
|
|
102
103
|
}
|
|
103
104
|
const HEARTBEAT_STALE_MS = 6e4;
|
|
104
105
|
const COVERAGE_STALE_MS = 6e4;
|
|
106
|
+
const COVERAGE_REFRESH_CONCURRENCY = 8;
|
|
105
107
|
async function fetchJobSummary(entityType, tenantIdParam, organizationIdParam) {
|
|
106
108
|
try {
|
|
107
109
|
let jobQuery = db.selectFrom("entity_index_jobs").selectAll().where("entity_type", "=", entityType).where(sql`tenant_id is not distinct from ${tenantIdParam ?? null}`);
|
|
@@ -212,31 +214,23 @@ async function GET(req) {
|
|
|
212
214
|
const parsed = Number(value);
|
|
213
215
|
return Number.isFinite(parsed) ? parsed : null;
|
|
214
216
|
};
|
|
215
|
-
const
|
|
217
|
+
const coverageScope = {
|
|
218
|
+
tenantId: tenantId ?? null,
|
|
219
|
+
organizationId,
|
|
220
|
+
withDeleted: false
|
|
221
|
+
};
|
|
216
222
|
const entitiesNeedingRefresh = /* @__PURE__ */ new Set();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
const refreshedAt = snapshot?.refreshed_at instanceof Date ? snapshot.refreshed_at : snapshot?.refreshed_at ? new Date(snapshot.refreshed_at) : null;
|
|
227
|
-
const stale = !snapshot || !refreshedAt || Date.now() - refreshedAt.getTime() > COVERAGE_STALE_MS;
|
|
228
|
-
if (forceRefresh || stale) {
|
|
229
|
-
await refreshCoverageSnapshot(em, scope2).catch(() => void 0);
|
|
230
|
-
snapshot = await readCoverageSnapshot(db, scope2);
|
|
231
|
-
}
|
|
232
|
-
const finalRefreshed = snapshot?.refreshed_at instanceof Date ? snapshot.refreshed_at : snapshot?.refreshed_at ? new Date(snapshot.refreshed_at) : null;
|
|
233
|
-
if (!snapshot || !finalRefreshed || Date.now() - finalRefreshed.getTime() > COVERAGE_STALE_MS) {
|
|
234
|
-
entitiesNeedingRefresh.add(entityId);
|
|
235
|
-
}
|
|
236
|
-
return snapshot;
|
|
237
|
-
};
|
|
238
|
-
coverageSnapshots.push(await ensureSnapshot());
|
|
223
|
+
const snapshotByEntity = await readCoverageSnapshots(db, { entityTypes: entityIds, ...coverageScope });
|
|
224
|
+
if (forceRefresh && entityIds.length > 0) {
|
|
225
|
+
await mapWithConcurrency(
|
|
226
|
+
entityIds,
|
|
227
|
+
COVERAGE_REFRESH_CONCURRENCY,
|
|
228
|
+
(entityId) => refreshCoverageSnapshot(em, { entityType: entityId, ...coverageScope }).catch(() => void 0)
|
|
229
|
+
);
|
|
230
|
+
const refreshed = await readCoverageSnapshots(db, { entityTypes: entityIds, ...coverageScope });
|
|
231
|
+
for (const [entityId, snapshot] of refreshed) snapshotByEntity.set(entityId, snapshot);
|
|
239
232
|
}
|
|
233
|
+
const coverageSnapshots = entityIds.map((entityId) => snapshotByEntity.get(entityId) ?? null);
|
|
240
234
|
const jobs = await Promise.all(entityIds.map((eid) => fetchJobSummary(eid, tenantId, organizationId)));
|
|
241
235
|
const items = [];
|
|
242
236
|
for (let idx = 0; idx < entityIds.length; idx += 1) {
|
|
@@ -362,6 +356,7 @@ async function GET(req) {
|
|
|
362
356
|
});
|
|
363
357
|
const response = NextResponse.json({ items, errors, logs });
|
|
364
358
|
const partial = items.find((item) => {
|
|
359
|
+
if (item.baseCount == null && item.indexCount == null) return false;
|
|
365
360
|
if (item.baseCount == null || item.indexCount == null) return true;
|
|
366
361
|
return item.baseCount !== item.indexCount;
|
|
367
362
|
});
|
|
@@ -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 { readCoverageSnapshot, refreshCoverageSnapshot } from '../lib/coverage'\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\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\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 coverageSnapshots: Array<Awaited<ReturnType<typeof readCoverageSnapshot>>> = []\n const entitiesNeedingRefresh = new Set<string>()\n for (const entityId of entityIds) {\n const scope = {\n entityType: entityId,\n tenantId: tenantId ?? null,\n organizationId,\n withDeleted: false,\n } as const\n const ensureSnapshot = async () => {\n let snapshot = await readCoverageSnapshot(db, scope)\n const refreshedAt = snapshot?.refreshed_at instanceof Date\n ? snapshot.refreshed_at\n : snapshot?.refreshed_at\n ? new Date(snapshot.refreshed_at)\n : null\n const stale = !snapshot || !refreshedAt || (Date.now() - refreshedAt.getTime() > COVERAGE_STALE_MS)\n if (forceRefresh || stale) {\n await refreshCoverageSnapshot(em, scope).catch(() => undefined)\n snapshot = await readCoverageSnapshot(db, scope)\n }\n const finalRefreshed = snapshot?.refreshed_at instanceof Date\n ? snapshot.refreshed_at\n : snapshot?.refreshed_at\n ? new Date(snapshot.refreshed_at)\n : null\n if (!snapshot || !finalRefreshed || (Date.now() - finalRefreshed.getTime() > COVERAGE_STALE_MS)) {\n entitiesNeedingRefresh.add(entityId)\n }\n return snapshot\n }\n coverageSnapshots.push(await ensureSnapshot())\n }\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((eb: any) => eb.or([\n eb('organization_id' as any, 'is', null),\n eb('organization_id' as any, 'in', organizationScopeIds),\n ]))\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((eb: any) => eb.or([\n eb('organization_id' as any, 'is', null),\n eb('organization_id' as any, 'in', organizationScopeIds),\n ]))\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 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
|
-
"names": [
|
|
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 } 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\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) is allowed to block: recompute every entity's\n // coverage with bounded concurrency, then re-read the freshly written snapshots.\n if (forceRefresh && entityIds.length > 0) {\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((eb: any) => eb.or([\n eb('organization_id' as any, 'is', null),\n eb('organization_id' as any, 'in', organizationScopeIds),\n ]))\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((eb: any) => eb.or([\n eb('organization_id' as any, 'is', null),\n eb('organization_id' as any, 'in', organizationScopeIds),\n ]))\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": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAE7B,SAAS,WAAW;AACpB,SAAS,uBAAuB,+BAA+B;AAC/D,SAAS,0BAA0B;AAInC,SAAS,eAAe,uBAAuB,sCAAsC;AACrF,SAAS,8BAA8B;AACvC,SAAS,0CAA0C;AAE5C,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AACzE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE9E,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,KAAM,GAAW,UAAU;AACjC,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AAExF,QAAM,iBAAiB,MAAM,cAAc,KAAK,SAAS;AACzD,QAAM,WAAW,OAAO,MAAM,aAAa,YAAY,MAAM,SAAS,KAAK,EAAE,SAAS,IAClF,MAAM,SAAS,KAAK,IACnB,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,KAAK,EAAE,SAAS,IAAI,KAAK,SAAS,KAAK,IAAI;AACnG,MAAI,CAAC,UAAU;AACb,WAAO,aAAa,KAAK,EAAE,OAAO,6BAA6B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,qBACJ,MAAM,cAAc,OAChB,OACA,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM,UAAU,SAAS,IACzD,MAAM,YACN,iBACE,CAAC,cAAc,IACf,CAAC;AAEX,MAAI,MAAM,QAAQ,kBAAkB,KAAK,mBAAmB,WAAW,GAAG;AACxE,WAAO,aAAa,KAAK,EAAE,OAAO,6BAA6B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,uBAAuB,uBAAuB,OAChD,OACA,MAAM;AAAA,IACN,IAAI;AAAA,MACF,mBAAmB;AAAA,QACjB,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AAEF,MAAI,MAAM,QAAQ,oBAAoB,KAAK,qBAAqB,WAAW,GAAG;AAC5E,WAAO,aAAa,KAAK,EAAE,OAAO,6BAA6B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,eAAe,IAAI,aAAa,IAAI,SAAS,KAAK,IAAI,aAAa,IAAI,SAAS,MAAM;AAE5F,QAAM,eAAe,uBAAuB,aAAa,CAA2C;AACpG,QAAM,YAAY,aAAa,IAAI,CAAC,cAAc,EAAE,UAAU,OAAO,SAAS,EAAE;AAEhF,QAAM,OAAO,oBAAI,IAAiD;AAClE,aAAW,KAAK,UAAW,MAAK,IAAI,EAAE,UAAU,CAAC;AAEjD,MAAI,YAAY,aAAa,MAAM;AAInC,MAAI,sBAA4C,CAAC;AACjD,MAAI;AACF,0BAAsB,UAAU,QAAQ,qBAAqB;AAAA,EAC/D,QAAQ;AAAA,EAER;AAEA,QAAM,wBAAwB,oBAAI,IAAY;AAC9C,QAAM,0BAA0B,oBAAI,IAAY;AAChD,aAAW,gBAAgB,qBAAqB;AAC9C,eAAW,UAAU,aAAa,YAAY,CAAC,GAAG;AAChD,UAAI,OAAO,YAAY,OAAO;AAE5B,YAAI,OAAO,OAAO,gBAAgB,YAAY;AAC5C,gCAAsB,IAAI,OAAO,QAAQ;AAAA,QAC3C;AAEA,YAAI,OAAO,eAAe,OAAO,OAAO,gBAAgB,UAAU;AAChE,kCAAwB,IAAI,OAAO,QAAQ;AAAA,QAC7C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,mBAAkD;AACtD,MAAI;AACF,UAAM,mBAAmB,UAAU,QAAQ,kBAAkB;AAC7D,uBAAoB,kBAAkB;AAAA,MACpC,CAAC,MAAgB,GAAuB,OAAO;AAAA,IACjD,KAAgC;AAAA,EAClC,QAAQ;AACN,uBAAmB;AAAA,EACrB;AAGA,MAAI,uBAAsD;AAC1D,MAAI,kBAAkB;AACpB,QAAI;AACF,6BAAuB,MAAM,iBAAiB,gBAAgB,QAAQ;AAAA,IACxE,QAAQ;AACN,6BAAuB;AAAA,IACzB;AAAA,EACF;AAGA,MAAI;AACF,QAAI,UAAU,GACX,WAAW,mBAA0B,EACrC,OAAO,CAAC,WAAkB,CAAC,EAC3B,SAAS,EACT,MAAM,aAAoB,KAAK,IAAI;AACtC,QAAI,YAAY,MAAM;AACpB,gBAAU,QAAQ,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,QACzC,GAAG,aAAoB,KAAK,QAAQ;AAAA,QACpC,GAAG,aAAoB,MAAM,IAAI;AAAA,MACnC,CAAC,CAAC;AAAA,IACJ,OAAO;AACL,gBAAU,QAAQ,MAAM,aAAoB,MAAM,IAAW;AAAA,IAC/D;AACA,QAAI,MAAM,QAAQ,oBAAoB,GAAG;AACvC,gBAAU,QAAQ,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,QACzC,GAAG,mBAA0B,MAAM,oBAAoB;AAAA,QACvD,GAAG,mBAA0B,MAAM,IAAI;AAAA,MACzC,CAAC,CAAC;AAAA,IACJ;AACA,UAAM,SAAS,MAAM,QAAQ,QAAQ;AACrC,UAAM,UAAU,IAAI,KAAa,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9E,gBAAY,UAAU,OAAO,CAAC,OAAO,QAAQ,IAAI,EAAE,CAAC;AAAA,EACtD,QAAQ;AAAA,EAAC;AAET,QAAM,qBAAqB;AAC3B,QAAM,oBAAoB;AAC1B,QAAM,+BAA+B;AAErC,iBAAe,gBAAgB,YAAoB,eAA8B,qBAAoC;AACnH,QAAI;AACF,UAAI,WAAW,GACZ,WAAW,mBAA0B,EACrC,UAAU,EACV,MAAM,eAAsB,KAAK,UAAU,EAC3C,MAAM,qCAA8C,iBAAiB,IAAI,EAAE;AAC9E,UAAI,uBAAuB,MAAM;AAC/B,mBAAW,SAAS,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,UAC3C,GAAG,mBAA0B,KAAK,mBAAmB;AAAA,UACrD,GAAG,mBAA0B,MAAM,IAAI;AAAA,QACzC,CAAC,CAAC;AAAA,MACJ,OAAO;AACL,mBAAW,SAAS,MAAM,2CAAoD,IAAI,EAAE;AAAA,MACtF;AACA,YAAM,OAAO,MAAM,SAChB,QAAQ,cAAqB,MAAM,EACnC,QAAQ;AAEX,UAAI,CAAC,KAAK,QAAQ;AAChB,eAAO,EAAE,QAAQ,QAAiB,YAAY,CAAC,EAAW;AAAA,MAC5D;AAEA,YAAM,YACJ,uBAAuB,QAAQ,KAAK,KAAK,CAAC,QAAa,IAAI,oBAAoB,mBAAmB;AACpG,YAAM,gBAAgB,CACpB,UACA,cACM;AACN,YAAI,CAAC,SAAU,QAAO;AACtB,YAAI,WAAW;AACb,cAAI,UAAU,YAAY,CAAC,SAAS,SAAU,QAAO;AACrD,cAAI,CAAC,UAAU,YAAY,SAAS,SAAU,QAAO;AAAA,QACvD;AACA,YAAI,UAAU,eAAe,CAAC,SAAS,YAAa,QAAO;AAC3D,YAAI,CAAC,UAAU,eAAe,SAAS,YAAa,QAAO;AAC3D,eAAO,UAAU,YAAY,SAAS,YAAY,YAAY;AAAA,MAChE;AAEA,YAAM,gBAAgB,oBAAI,IAAsF;AAChH,UAAI,WAA4F;AAChG,iBAAW,OAAO,MAAM;AACtB,cAAM,MAAM,OAAO,IAAI,mBAAmB,UAAU;AACpD,cAAM,YAAY,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,EAAE,QAAQ,IAAI;AACxE,cAAM,cAAc,iBAAiB,OAAO,IAAI,cAAc,gBAAgB;AAC9E,cAAM,WAAW,uBAAuB,OAAO,IAAI,oBAAoB,sBAAsB,IAAI,mBAAmB;AACpH,cAAM,YAAY,EAAE,KAAK,WAAW,aAAa,SAAS;AAC1D,YAAI,IAAI,mBAAmB,MAAM;AAC/B,qBAAW,cAAc,UAAU,SAAS;AAC5C;AAAA,QACF;AACA,cAAM,WAAW,cAAc,IAAI,GAAG;AACtC,sBAAc,IAAI,KAAK,cAAc,YAAY,MAAM,SAAS,CAAC;AAAA,MACnE;AAEA,YAAM,aAAa,MAAM,KAAK,cAAc,OAAO,CAAC,EACjD,OAAO,CAAC,UAAU,CAAC,aAAa,MAAM,QAAQ,EAC9C,IAAI,CAAC,EAAE,IAAI,MAAM;AAChB,cAAM,gBAAgB,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,IAAI;AACtE,cAAM,cAAc,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,IAAI;AAChE,cAAM,eAAe,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI;AACnE,cAAM,UACJ,CAAC,iBAAiB,CAAC,iBAAiB,KAAK,IAAI,IAAI,cAAc,QAAQ,IAAI;AAC7E,cAAM,QAAQ,eACV,cACA,UACE,YACC,IAAI,UAAqB;AAChC,eAAO;AAAA,UACL,gBAAgB,IAAI,mBAAmB;AAAA,UACvC,gBAAgB,IAAI,mBAAmB;AAAA,UACvC,QAAQ;AAAA,UACR,WAAW,cAAc,YAAY,YAAY,IAAI;AAAA,UACrD,YAAY,eAAe,aAAa,YAAY,IAAI;AAAA,UACxD,aAAa,gBAAgB,cAAc,YAAY,IAAI;AAAA,UAC3D,gBAAgB,IAAI,mBAAmB;AAAA,UACvC,YAAY,IAAI,eAAe;AAAA,QACjC;AAAA,MACF,CAAC,EACA,KAAK,CAAC,GAAG,OAAO,EAAE,kBAAkB,MAAM,EAAE,kBAAkB,EAAE;AACnE,YAAM,mBAAmB,WAAW,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU;AAC/D,YAAM,oBAAoB,iBAAiB;AAAA,QACzC,CAAC,MAAM,EAAE,WAAW,gBAAgB,EAAE,WAAW;AAAA,MACnD;AACA,YAAM,oBAAoB,iBAAiB,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS;AAC/E,UAAI,SAAwD;AAC5D,UAAI,iBAAiB,QAAQ;AAC3B,YAAI,kBAAkB,QAAQ;AAC5B,mBAAS,kBAAkB,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,IAAI,YAAY;AAAA,QAC/E,WAAW,kBAAkB,QAAQ;AACnC,mBAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,YAAY,iBAAiB,CAAC,GAAG,aAAa,WAAW,CAAC,GAAG,aAAa;AAChF,YAAM,aAAa,WAAW,SAAU,WAAW,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,OAAQ;AACpG,YAAM,cAAc,iBAAiB,CAAC,GAAG,eAAe,WAAW,CAAC,GAAG,eAAe;AACtF,YAAM,gBAAgB,WAAW,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,cAAc,IAAI,CAAC;AAChF,YAAM,eAAe,WAAW,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,kBAAkB,IAAI,CAAC;AACnF,YAAM,iBAAiB,gBAAgB,KAAK,IAAI,eAAe,YAAY,IAAI,gBAAgB;AAC/F,YAAM,iBAAiB,CAAC,aAAa,CAAC,YAAY,SAAS,WAAW,WAAW;AAEjF,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB,gBAAgB,iBAAiB,gBAAgB,KAAK,mBAAmB;AAAA,QACzF,YAAY,gBAAgB,gBAAgB,gBAAgB,KAAK,eAAe;AAAA,QAChF;AAAA,QACA,OAAO,iBACH;AAAA,UACE,SAAS,MAAM;AACb,kBAAM,gBAAgB,eAAgB,IAAI,eAAe,IAAI,KAAK,eAAgB,IAAI,YAAY,IAAI;AACtG,kBAAM,eAAe,eAAgB,IAAI,cAAc,IAAI,KAAK,eAAgB,IAAI,WAAW,IAAI;AACnG,gBAAI,aAAc,QAAO;AACzB,gBACE,CAAC,iBACD,KAAK,IAAI,IAAI,cAAc,QAAQ,IAAI,oBACvC;AACA,qBAAO;AAAA,YACT;AACA,mBAAQ,eAAgB,IAAI,UAAqB;AAAA,UACnD,GAAG;AAAA,UACH,gBAAgB,eAAe,IAAI,mBAAmB;AAAA,UACtD,YAAY,eAAe,IAAI,eAAe;AAAA,QAChD,IACA;AAAA,MACN;AAAA,IACF,QAAQ;AACN,aAAO,EAAE,QAAQ,QAAiB,YAAY,CAAC,EAAW;AAAA,IAC5D;AAAA,EACF;AAEA,QAAM,iBAAiB,CAAC,UAAkC;AACxD,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,UAAM,SAAS,OAAO,KAAK;AAC3B,WAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EAC5C;AAEA,QAAM,gBAAgB;AAAA,IACpB,UAAU,YAAY;AAAA,IACtB;AAAA,IACA,aAAa;AAAA,EACf;AACA,QAAM,yBAAyB,oBAAI,IAAY;AAM/C,QAAM,mBAAmB,MAAM,sBAAsB,IAAI,EAAE,aAAa,WAAW,GAAG,cAAc,CAAC;AAIrG,MAAI,gBAAgB,UAAU,SAAS,GAAG;AACxC,UAAM;AAAA,MAAmB;AAAA,MAAW;AAAA,MAA8B,CAAC,aACjE,wBAAwB,IAAI,EAAE,YAAY,UAAU,GAAG,cAAc,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC/F;AACA,UAAM,YAAY,MAAM,sBAAsB,IAAI,EAAE,aAAa,WAAW,GAAG,cAAc,CAAC;AAC9F,eAAW,CAAC,UAAU,QAAQ,KAAK,UAAW,kBAAiB,IAAI,UAAU,QAAQ;AAAA,EACvF;AAEA,QAAM,oBAAoB,UAAU,IAAI,CAAC,aAAa,iBAAiB,IAAI,QAAQ,KAAK,IAAI;AAE5F,QAAM,OAAO,MAAM,QAAQ,IAAI,UAAU,IAAI,CAAC,QAAQ,gBAAgB,KAAK,UAAU,cAAc,CAAC,CAAC;AAErG,QAAM,QAAe,CAAC;AACtB,WAAS,MAAM,GAAG,MAAM,UAAU,QAAQ,OAAO,GAAG;AAClD,UAAM,MAAM,UAAU,GAAG;AACzB,QAAI,WAAW,kBAAkB,GAAG;AAEpC,UAAM,cAAc,UAAU,wBAAwB,OAAO,SAAS,eAAe,UAAU,eAAe,IAAI,KAAK,SAAS,YAAY,IAAI;AAChJ,UAAM,UAAU,CAAC,YAAY,CAAC,eAAgB,KAAK,IAAI,IAAI,YAAY,QAAQ,IAAI;AACnF,QAAI,QAAS,wBAAuB,IAAI,GAAG;AAE3C,UAAM,MAAM,KAAK,GAAG;AACpB,UAAM,QAAS,KAAK,IAAI,GAAG,GAAG,SAAU;AACxC,UAAM,kBAAkB,eAAe,UAAU,SAAS;AAC1D,UAAM,mBAAmB,eAAe,UAAU,YAAY;AAC9D,UAAM,gBAAgB,sBAAsB,IAAI,GAAG;AACnD,UAAM,oBAAoB,gBAAgB,eAAgB,UAAkB,sBAAuB,UAAkB,oBAAoB,IAAI;AAC7I,UAAM,kBAAkB,wBAAwB,IAAI,GAAG;AACvD,UAAM,sBAAsB,kBAAmB,uBAAuB,GAAG,KAAK,IAAK;AACnF,UAAM,MAAM,MAAM;AAChB,UAAI,mBAAmB,QAAQ,oBAAoB,KAAM,QAAO;AAChE,UAAI,oBAAoB,iBAAkB,QAAO;AACjD,UAAI,CAAC,cAAe,QAAO;AAC3B,aAAO,qBAAqB,QAAQ,sBAAsB;AAAA,IAC5D,GAAG;AACH,UAAM,KAAK;AAAA,MACT,UAAU;AAAA,MACV;AAAA,MACA,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,aAAa,gBAAgB,oBAAoB;AAAA,MACjD;AAAA,MACA,eAAe;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,eAAe;AAAA,IAC9B,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,cAAc;AACjB,QAAI;AACF,YAAM,WAAW,UAAU,QAAQ,UAAU;AAC7C,UAAI,uBAAuB,OAAO,GAAG;AACnC,cAAM,QAAQ;AAAA,UACZ,MAAM,KAAK,sBAAsB,EAAE;AAAA,YAAI,CAAC,aACtC,SACG,UAAU,gCAAgC;AAAA,cACzC,YAAY;AAAA,cACZ,UAAU,YAAY;AAAA,cACtB;AAAA,cACA,SAAS;AAAA,YACX,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,MAAI,aAAa,GACd,WAAW,oBAA2B,EACtC,UAAU;AACb,MAAI,YAAY,MAAM;AACpB,iBAAa,WAAW,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,MAC/C,GAAG,aAAoB,KAAK,QAAQ;AAAA,MACpC,GAAG,aAAoB,MAAM,IAAI;AAAA,IACnC,CAAC,CAAC;AAAA,EACJ,OAAO;AACL,iBAAa,WAAW,MAAM,aAAoB,MAAM,IAAW;AAAA,EACrE;AACA,MAAI,MAAM,QAAQ,oBAAoB,KAAK,qBAAqB,QAAQ;AACtE,iBAAa,WAAW,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,MAC/C,GAAG,mBAA0B,MAAM,IAAI;AAAA,MACvC,GAAG,mBAA0B,MAAM,oBAAoB;AAAA,IACzD,CAAC,CAAC;AAAA,EACJ,OAAO;AACL,iBAAa,WAAW,MAAM,mBAA0B,MAAM,IAAW;AAAA,EAC3E;AACA,QAAM,YAAY,MAAM,WACrB,QAAQ,eAAsB,MAAM,EACpC,MAAM,GAAG,EACT,QAAQ;AAEX,QAAM,SAAS,UAAU,IAAI,CAAC,QAAa;AACzC,UAAM,aAAa,IAAI,uBAAuB,OAAO,IAAI,cAAc,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI;AACrH,WAAO;AAAA,MACL,IAAI,OAAO,IAAI,EAAE;AAAA,MACjB,QAAQ,OAAO,IAAI,UAAU,EAAE;AAAA,MAC/B,SAAS,OAAO,IAAI,WAAW,EAAE;AAAA,MACjC,YAAY,IAAI,eAAe;AAAA,MAC/B,UAAU,IAAI,aAAa;AAAA,MAC3B,UAAU,IAAI,aAAa;AAAA,MAC3B,gBAAgB,IAAI,mBAAmB;AAAA,MACvC,SAAS,OAAO,IAAI,WAAW,EAAE;AAAA,MACjC,OAAO,IAAI,SAAS;AAAA,MACpB,SAAS,IAAI,WAAW;AAAA,MACxB,YAAY,aAAa,WAAW,YAAY,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC7E;AAAA,EACF,CAAC;AAED,MAAI,YAAY,GACb,WAAW,qBAA4B,EACvC,UAAU;AACb,MAAI,YAAY,MAAM;AACpB,gBAAY,UAAU,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,MAC7C,GAAG,aAAoB,KAAK,QAAQ;AAAA,MACpC,GAAG,aAAoB,MAAM,IAAI;AAAA,IACnC,CAAC,CAAC;AAAA,EACJ,OAAO;AACL,gBAAY,UAAU,MAAM,aAAoB,MAAM,IAAW;AAAA,EACnE;AACA,MAAI,MAAM,QAAQ,oBAAoB,KAAK,qBAAqB,QAAQ;AACtE,gBAAY,UAAU,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,MAC7C,GAAG,mBAA0B,MAAM,IAAI;AAAA,MACvC,GAAG,mBAA0B,MAAM,oBAAoB;AAAA,IACzD,CAAC,CAAC;AAAA,EACJ,OAAO;AACL,gBAAY,UAAU,MAAM,mBAA0B,MAAM,IAAW;AAAA,EACzE;AACA,QAAM,UAAU,MAAM,UACnB,QAAQ,eAAsB,MAAM,EACpC,MAAM,GAAG,EACT,QAAQ;AAEX,QAAM,OAAO,QAAQ,IAAI,CAAC,QAAa;AACrC,UAAM,aAAa,IAAI,uBAAuB,OAAO,IAAI,cAAc,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI;AACrH,UAAM,QAAQ,IAAI,UAAU,SAAS,SAAS;AAC9C,WAAO;AAAA,MACL,IAAI,OAAO,IAAI,EAAE;AAAA,MACjB,QAAQ,OAAO,IAAI,UAAU,EAAE;AAAA,MAC/B,SAAS,OAAO,IAAI,WAAW,EAAE;AAAA,MACjC;AAAA,MACA,YAAY,IAAI,eAAe;AAAA,MAC/B,UAAU,IAAI,aAAa;AAAA,MAC3B,UAAU,IAAI,aAAa;AAAA,MAC3B,gBAAgB,IAAI,mBAAmB;AAAA,MACvC,SAAS,OAAO,IAAI,WAAW,EAAE;AAAA,MACjC,SAAS,IAAI,WAAW;AAAA,MACxB,YAAY,aAAa,WAAW,YAAY,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC7E;AAAA,EACF,CAAC;AAED,QAAM,WAAW,aAAa,KAAK,EAAE,OAAO,QAAQ,KAAK,CAAC;AAC1D,QAAM,UAAU,MAAM,KAAK,CAAC,SAAS;AAGnC,QAAI,KAAK,aAAa,QAAQ,KAAK,cAAc,KAAM,QAAO;AAC9D,QAAI,KAAK,aAAa,QAAQ,KAAK,cAAc,KAAM,QAAO;AAC9D,WAAO,KAAK,cAAc,KAAK;AAAA,EACjC,CAAC;AACD,MAAI,SAAS;AACX,aAAS,QAAQ;AAAA,MACf;AAAA,MACA,KAAK,UAAU;AAAA,QACb,MAAM;AAAA,QACN,QAAQ,QAAQ;AAAA,QAChB,aAAa,QAAQ,SAAS,QAAQ;AAAA,QACtC,WAAW,QAAQ;AAAA,QACnB,cAAc,QAAQ;AAAA,QACtB,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,sBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,aAAa;AAAA,EACpB,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,+BAA+B,QAAQ,+BAA+B;AAAA,EACpG;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,2CAA2C,QAAQ,sBAAsB;AAAA,IACrG,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,sBAAsB;AAAA,EACvF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
|
@@ -97,6 +97,40 @@ async function readCoverageSnapshot(db, scope) {
|
|
|
97
97
|
vectorIndexedCount: toCount(row.vector_indexed_count)
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
+
async function readCoverageSnapshots(db, batch) {
|
|
101
|
+
const entityTypes = Array.from(
|
|
102
|
+
new Set((batch.entityTypes ?? []).map((id) => String(id || "")).filter((id) => id.length > 0))
|
|
103
|
+
);
|
|
104
|
+
const result = /* @__PURE__ */ new Map();
|
|
105
|
+
if (entityTypes.length === 0) return result;
|
|
106
|
+
const withDeleted = batch.withDeleted === true;
|
|
107
|
+
let query = db.selectFrom("entity_index_coverage").select([
|
|
108
|
+
"entity_type",
|
|
109
|
+
"base_count",
|
|
110
|
+
"indexed_count",
|
|
111
|
+
"vector_indexed_count",
|
|
112
|
+
"refreshed_at",
|
|
113
|
+
"organization_id"
|
|
114
|
+
]).where("entity_type", "in", entityTypes).where("with_deleted", "=", withDeleted).orderBy("refreshed_at", "desc");
|
|
115
|
+
query = batch.tenantId == null ? query.where("tenant_id", "is", null) : query.where("tenant_id", "=", batch.tenantId);
|
|
116
|
+
query = applyOrganizationCondition(query, "organization_id", batch.organizationId ?? null);
|
|
117
|
+
const rows = await query.execute();
|
|
118
|
+
for (const row of rows ?? []) {
|
|
119
|
+
const entityType = String(row.entity_type || "");
|
|
120
|
+
if (!entityType || result.has(entityType)) continue;
|
|
121
|
+
const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : row.refreshed_at ? new Date(row.refreshed_at) : null;
|
|
122
|
+
result.set(entityType, {
|
|
123
|
+
base_count: row.base_count,
|
|
124
|
+
indexed_count: row.indexed_count,
|
|
125
|
+
vector_indexed_count: row.vector_indexed_count,
|
|
126
|
+
refreshed_at: refreshedAt ?? null,
|
|
127
|
+
baseCount: toCount(row.base_count),
|
|
128
|
+
indexedCount: toCount(row.indexed_count),
|
|
129
|
+
vectorIndexedCount: toCount(row.vector_indexed_count)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
100
134
|
async function applyCoverageAdjustments(em, adjustments) {
|
|
101
135
|
if (!adjustments.length) return;
|
|
102
136
|
const db = em.getKysely();
|
|
@@ -265,6 +299,7 @@ export {
|
|
|
265
299
|
createCoverageAdjustments,
|
|
266
300
|
deleteCoverageForEntity,
|
|
267
301
|
readCoverageSnapshot,
|
|
302
|
+
readCoverageSnapshots,
|
|
268
303
|
refreshCoverageSnapshot,
|
|
269
304
|
writeCoverageCounts
|
|
270
305
|
};
|