@open-mercato/core 0.6.7-develop.6842.1.8cd8a8883c → 0.6.7-develop.6851.1.c1680b00d8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/entities/api/definitions.js +2 -0
  3. package/dist/modules/entities/api/definitions.js.map +2 -2
  4. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +18 -3
  5. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  6. package/dist/modules/entities/components/RecordListRelationCell.js +63 -0
  7. package/dist/modules/entities/components/RecordListRelationCell.js.map +7 -0
  8. package/dist/modules/entities/components/recordListRelationDisplays.js +80 -0
  9. package/dist/modules/entities/components/recordListRelationDisplays.js.map +7 -0
  10. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +14 -1
  11. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
  12. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +22 -17
  13. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  14. package/dist/modules/query_index/api/reindex.js +10 -0
  15. package/dist/modules/query_index/api/reindex.js.map +2 -2
  16. package/dist/modules/query_index/cli.js +125 -1
  17. package/dist/modules/query_index/cli.js.map +2 -2
  18. package/dist/modules/query_index/lib/coverage.js +19 -7
  19. package/dist/modules/query_index/lib/coverage.js.map +2 -2
  20. package/dist/modules/query_index/lib/reindexer.js +43 -37
  21. package/dist/modules/query_index/lib/reindexer.js.map +2 -2
  22. package/dist/modules/sales/commands/documents.js +27 -0
  23. package/dist/modules/sales/commands/documents.js.map +2 -2
  24. package/dist/modules/sales/data/validators.js +2 -0
  25. package/dist/modules/sales/data/validators.js.map +2 -2
  26. package/package.json +7 -7
  27. package/src/modules/entities/api/definitions.ts +4 -0
  28. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +94 -77
  29. package/src/modules/entities/components/RecordListRelationCell.tsx +90 -0
  30. package/src/modules/entities/components/recordListRelationDisplays.ts +109 -0
  31. package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +17 -1
  32. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +21 -16
  33. package/src/modules/inbox_ops/components/proposals/types.ts +1 -0
  34. package/src/modules/query_index/api/reindex.ts +15 -0
  35. package/src/modules/query_index/cli.ts +180 -1
  36. package/src/modules/query_index/lib/coverage.ts +38 -8
  37. package/src/modules/query_index/lib/reindexer.ts +62 -40
  38. package/src/modules/sales/commands/documents.ts +49 -0
  39. package/src/modules/sales/data/validators.ts +2 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/lib/coverage.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, type Transaction, sql } from 'kysely'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'coverage' })\n\ntype CoverageExecutor = Kysely<any> | Transaction<any>\n\nexport type CoverageScope = {\n entityType: string\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\ntype CoverageRow = {\n base_count: unknown\n indexed_count: unknown\n vector_indexed_count: unknown\n refreshed_at: Date | string | null\n}\n\nexport type CoverageSnapshot = CoverageRow & {\n baseCount: number\n indexedCount: number\n vectorIndexedCount: number\n}\n\nexport type CoverageBatchScope = {\n entityTypes: readonly string[]\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\nexport type CoverageAdjustment = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n deltaBase: number\n deltaIndex: number\n deltaVector?: number\n}\n\nexport type CoverageDeltaInput = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n baseDelta: number\n indexDelta: number\n vectorDelta?: number\n}\n\nconst COLUMN_CACHE = new Map<string, boolean>()\n// In-flight de-dup: without this, N concurrent `tableHasColumn` callers for the same\n// (table, column) \u2014 e.g. every entity type's `refreshCoverageSnapshot` asking about\n// `vector_search.entity_id` \u2014 would each see a cold cache and fire their own identical\n// `information_schema.columns` query, since the cache is only populated after a query\n// resolves. Tracking the in-flight promise lets late arrivals await the first one instead.\nconst COLUMN_CACHE_PENDING = new Map<string, Promise<boolean>>()\nconst GLOBAL_ORGANIZATION_PLACEHOLDER = '00000000-0000-0000-0000-000000000000'\nexport const COVERAGE_ORG_PLACEHOLDER = GLOBAL_ORGANIZATION_PLACEHOLDER\n\nfunction toCount(value: unknown): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n }\n if (value != null && typeof (value as { valueOf: () => number }).valueOf === 'function') {\n const parsed = Number((value as { valueOf: () => number }).valueOf())\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n}\n\nfunction normalizeOrganizationForStore(orgId: string | null | undefined): string {\n return orgId ?? GLOBAL_ORGANIZATION_PLACEHOLDER\n}\n\nfunction applyOrganizationCondition<QB extends { where: (...args: any[]) => QB }>(\n qb: QB,\n column: string,\n organizationId: string | null | undefined,\n): QB {\n const stored = normalizeOrganizationForStore(organizationId ?? null)\n if (stored === GLOBAL_ORGANIZATION_PLACEHOLDER) {\n return qb.where((eb: any) => eb.or([\n eb(column as any, 'is', null),\n eb(column as any, '=', GLOBAL_ORGANIZATION_PLACEHOLDER),\n ]))\n }\n return qb.where(column as any, '=', stored)\n}\n\nasync function fetchCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope\n): Promise<(CoverageRow & { organization_id: string | null }) | null> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n .orderBy('refreshed_at' as any, 'desc')\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null)\n const row = await query.executeTakeFirst() as (CoverageRow & { organization_id: string | null }) | undefined\n return row ?? null\n}\n\nasync function pruneDuplicateCoverageRows(\n db: CoverageExecutor,\n scope: CoverageScope,\n keepId: string | null\n): Promise<void> {\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n query = scope.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', scope.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', scope.organizationId ?? null)\n if (keepId) {\n query = query.where('id' as any, '!=', keepId)\n }\n await query.execute()\n}\n\nasync function upsertCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope,\n counts: { baseCount: number; indexedCount: number; vectorIndexedCount: number }\n): Promise<void> {\n const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null)\n if (scope.organizationId == null) {\n let purge = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n .where('organization_id' as any, 'is', null as any)\n purge = scope.tenantId == null\n ? purge.where('tenant_id' as any, 'is', null as any)\n : purge.where('tenant_id' as any, '=', scope.tenantId)\n await purge.execute()\n }\n\n const rows = await db\n .insertInto('entity_index_coverage' as any)\n .values({\n entity_type: scope.entityType,\n tenant_id: scope.tenantId ?? null,\n organization_id: storedOrgId,\n with_deleted: scope.withDeleted === true,\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'tenant_id', 'organization_id', 'with_deleted'])\n .doUpdateSet({\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any))\n .returning(['id' as any])\n .execute() as Array<{ id: string }>\n\n const keepId = rows?.[0]?.id ?? null\n await pruneDuplicateCoverageRows(db, scope, keepId)\n}\n\nexport async function readCoverageSnapshot(\n db: Kysely<any>,\n scope: CoverageScope\n): Promise<(CoverageRow & { baseCount: number; indexedCount: number; vectorIndexedCount: number }) | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const row = await fetchCoverageRow(db, {\n entityType,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n withDeleted: scope.withDeleted === true,\n })\n if (!row) return null\n const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n return {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n }\n}\n\nexport async function readCoverageSnapshots(\n db: Kysely<any>,\n batch: CoverageBatchScope\n): Promise<Map<string, CoverageSnapshot>> {\n const entityTypes = Array.from(\n new Set((batch.entityTypes ?? []).map((id) => String(id || '')).filter((id) => id.length > 0))\n )\n const result = new Map<string, CoverageSnapshot>()\n if (entityTypes.length === 0) return result\n\n const withDeleted = batch.withDeleted === true\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'entity_type' as any,\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, 'in', entityTypes)\n .where('with_deleted' as any, '=', withDeleted)\n .orderBy('refreshed_at' as any, 'desc')\n query = batch.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', batch.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', batch.organizationId ?? null)\n\n const rows = await query.execute() as Array<CoverageRow & { entity_type: string }>\n for (const row of rows ?? []) {\n const entityType = String(row.entity_type || '')\n // Rows are ordered by refreshed_at desc, so the first row seen per entity is the latest.\n if (!entityType || result.has(entityType)) continue\n const refreshedAt = row.refreshed_at instanceof Date\n ? row.refreshed_at\n : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n result.set(entityType, {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n })\n }\n return result\n}\n\nexport async function applyCoverageAdjustments(\n em: EntityManager,\n adjustments: CoverageAdjustment[],\n options?: { trx?: CoverageExecutor },\n): Promise<void> {\n if (!adjustments.length) return\n const db = options?.trx ?? ((em as any).getKysely() as Kysely<any>)\n const aggregated = aggregateAdjustments(adjustments)\n for (const entry of aggregated) {\n const scope = entry.scope\n const existing = await fetchCoverageRow(db, scope)\n const currentBase = existing ? toCount(existing.base_count) : 0\n const currentIndex = existing ? toCount(existing.indexed_count) : 0\n const currentVector = existing ? toCount(existing.vector_indexed_count) : 0\n const nextBase = Math.max(currentBase + entry.deltaBase, 0)\n const nextIndex = Math.max(currentIndex + entry.deltaIndex, 0)\n const nextVector = Math.max(currentVector + entry.deltaVector, 0)\n\n await upsertCoverageRow(db, scope, {\n baseCount: nextBase,\n indexedCount: nextIndex,\n vectorIndexedCount: nextVector,\n })\n }\n}\n\nexport async function deleteCoverageForEntity(db: Kysely<any>, entityType: string): Promise<void> {\n if (!entityType) return\n await db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .execute()\n}\n\nasync function tableHasColumn(db: Kysely<any>, table: string, column: string): Promise<boolean> {\n const key = `${table}.${column}`\n if (COLUMN_CACHE.has(key)) return COLUMN_CACHE.get(key)!\n const pending = COLUMN_CACHE_PENDING.get(key)\n if (pending) return pending\n const promise = (async () => {\n const exists = await db\n .selectFrom('information_schema.columns' as any)\n .select(sql<number>`1`.as('present'))\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, '=', table)\n .where('column_name' as any, '=', column)\n .executeTakeFirst()\n const present = !!exists\n COLUMN_CACHE.set(key, present)\n return present\n })()\n COLUMN_CACHE_PENDING.set(key, promise)\n try {\n return await promise\n } finally {\n COLUMN_CACHE_PENDING.delete(key)\n }\n}\n\nexport type ColumnCheck = { table: string; column: string }\n\n// Batches the `information_schema.columns` introspection used by `refreshCoverageSnapshot`\n// into a single query for a whole set of (table, column) pairs, and pre-populates\n// `COLUMN_CACHE_PENDING` for every pair before that query even runs. Callers of\n// `coverage_warmup.ts` use this so its many concurrently-dispatched `coverage.refresh`\n// subscribers hit an already-primed (or in-flight) cache instead of each doing their own\n// per-table introspection round trip.\nexport async function primeColumnCache(db: Kysely<any>, checks: ColumnCheck[]): Promise<void> {\n const missing: Array<{ table: string; column: string; key: string }> = []\n const seen = new Set<string>()\n for (const check of checks) {\n const table = String(check?.table || '')\n const column = String(check?.column || '')\n if (!table || !column) continue\n const key = `${table}.${column}`\n if (seen.has(key)) continue\n seen.add(key)\n if (COLUMN_CACHE.has(key) || COLUMN_CACHE_PENDING.has(key)) continue\n missing.push({ table, column, key })\n }\n if (!missing.length) return\n\n const tables = Array.from(new Set(missing.map((entry) => entry.table)))\n const columns = Array.from(new Set(missing.map((entry) => entry.column)))\n\n const batchPromise = (async (): Promise<Set<string>> => {\n const rows = await db\n .selectFrom('information_schema.columns' as any)\n .select(['table_name' as any, 'column_name' as any])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, 'in', tables)\n .where('column_name' as any, 'in', columns)\n .execute() as Array<{ table_name: string; column_name: string }>\n return new Set(rows.map((row) => `${row.table_name}.${row.column_name}`))\n })()\n\n for (const entry of missing) {\n const entryPromise = batchPromise.then((present) => {\n const value = present.has(entry.key)\n COLUMN_CACHE.set(entry.key, value)\n return value\n })\n // Mark the stored promise as handled: when the batch query fails and no\n // `tableHasColumn` caller has adopted this entry yet (the common case \u2014 the warmup\n // awaits priming before dispatching any refresh), an orphaned rejection would\n // otherwise crash a plain-Node event worker via unhandledRejection. Awaiting\n // callers still observe the rejection through the stored reference.\n entryPromise.catch(() => undefined)\n COLUMN_CACHE_PENDING.set(entry.key, entryPromise)\n }\n\n try {\n await batchPromise\n } finally {\n for (const entry of missing) COLUMN_CACHE_PENDING.delete(entry.key)\n }\n}\n\nexport async function refreshCoverageSnapshot(\n em: EntityManager,\n scope: CoverageScope,\n): Promise<void> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n\n const db = (em as any).getKysely() as Kysely<any>\n const baseTable = resolveEntityTableName(em, entityType)\n\n const hasOrg = await tableHasColumn(db, baseTable, 'organization_id')\n const hasTenant = await tableHasColumn(db, baseTable, 'tenant_id')\n const hasDeleted = await tableHasColumn(db, baseTable, 'deleted_at')\n\n if (organizationId !== null && !hasOrg) return\n if (tenantId !== null && !hasTenant) return\n\n let baseQuery = db\n .selectFrom(`${baseTable} as b` as any)\n .select(sql`count(*)`.as('count'))\n if (organizationId !== null && hasOrg) baseQuery = baseQuery.where('b.organization_id' as any, '=', organizationId)\n if (tenantId !== null && hasTenant) baseQuery = baseQuery.where('b.tenant_id' as any, '=', tenantId)\n if (!withDeleted && hasDeleted) baseQuery = baseQuery.where('b.deleted_at' as any, 'is', null as any)\n\n let indexQuery = db\n .selectFrom('entity_indexes as ei' as any)\n .select(sql`count(*)`.as('count'))\n .where('ei.entity_type' as any, '=', entityType)\n if (organizationId !== null) indexQuery = indexQuery.where('ei.organization_id' as any, '=', organizationId)\n if (tenantId !== null) indexQuery = indexQuery.where('ei.tenant_id' as any, '=', tenantId)\n if (!withDeleted) indexQuery = indexQuery.where('ei.deleted_at' as any, 'is', null as any)\n\n const vectorCountPromise = (async (): Promise<number | undefined> => {\n const hasVectorTable = await tableHasColumn(db, 'vector_search', 'entity_id')\n if (!hasVectorTable || typeof tenantId !== 'string' || tenantId.length === 0) return undefined\n\n try {\n let vectorQuery = db\n .selectFrom('vector_search' as any)\n .select(sql`count(*)`.as('count'))\n .where('entity_id' as any, '=', entityType)\n .where('tenant_id' as any, '=', tenantId)\n if (organizationId !== null) {\n vectorQuery = vectorQuery.where('organization_id' as any, '=', organizationId)\n }\n const vectorRow = await vectorQuery.executeTakeFirst() as { count: unknown } | undefined\n return toCount(vectorRow?.count)\n } catch (err) {\n logger.warn('Failed to resolve vector count for coverage snapshot', {\n entityType,\n tenantId,\n organizationId,\n error: err instanceof Error ? err.message : err,\n })\n return undefined\n }\n })()\n\n const [baseRow, indexRow, vectorCount] = await Promise.all([\n baseQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n indexQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n vectorCountPromise,\n ])\n\n const baseCount = toCount(baseRow?.count)\n const indexCount = toCount(indexRow?.count)\n\n await writeCoverageCounts(em, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorCount,\n })\n}\n\nexport async function writeCoverageCounts(\n em: EntityManager,\n scope: CoverageScope,\n counts: { baseCount?: number; indexedCount?: number; vectorCount?: number }\n): Promise<void> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return\n const db = (em as any).getKysely() as Kysely<any>\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n const existing = await fetchCoverageRow(db, {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n })\n const baseCount = counts.baseCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.baseCount)))\n : Math.max(0, Math.trunc(toCount(existing?.base_count)))\n const indexCount = counts.indexedCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.indexedCount)))\n : Math.max(0, Math.trunc(toCount(existing?.indexed_count)))\n const vectorCount = counts.vectorCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.vectorCount)))\n : Math.max(0, Math.trunc(toCount(existing?.vector_indexed_count)))\n await upsertCoverageRow(db, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorIndexedCount: vectorCount,\n })\n}\n\ntype AggregatedAdjustment = {\n scope: CoverageScope\n deltaBase: number\n deltaIndex: number\n deltaVector: number\n}\n\nfunction aggregateAdjustments(adjustments: CoverageAdjustment[]): AggregatedAdjustment[] {\n const map = new Map<string, AggregatedAdjustment>()\n for (const adj of adjustments) {\n if (!adj?.entityType) continue\n const deltaBase = Number.isFinite(adj.deltaBase) ? adj.deltaBase : 0\n const deltaIndex = Number.isFinite(adj.deltaIndex) ? adj.deltaIndex : 0\n const deltaVector = Number.isFinite(adj.deltaVector) ? adj.deltaVector! : 0\n if (deltaBase === 0 && deltaIndex === 0 && deltaVector === 0) continue\n const scope: CoverageScope = {\n entityType: adj.entityType,\n tenantId: adj.tenantId ?? null,\n organizationId: adj.organizationId ?? null,\n withDeleted: adj.withDeleted === true,\n }\n const key = scopeKey(scope)\n const existing = map.get(key)\n if (existing) {\n existing.deltaBase += deltaBase\n existing.deltaIndex += deltaIndex\n existing.deltaVector += deltaVector\n } else {\n map.set(key, { scope, deltaBase, deltaIndex, deltaVector })\n }\n }\n return Array.from(map.values())\n}\n\nfunction scopeKey(scope: CoverageScope): string {\n const tenant = scope.tenantId ?? '__tenant_null__'\n const org = normalizeOrganizationForStore(scope.organizationId ?? null)\n const deleted = scope.withDeleted === true ? '1' : '0'\n return `${scope.entityType}|${tenant}|${org}|${deleted}`\n}\n\nexport function createCoverageAdjustments(input: CoverageDeltaInput): CoverageAdjustment[] {\n const entityType = String(input.entityType || '')\n if (!entityType) return []\n const baseDelta = Number.isFinite(input.baseDelta) ? input.baseDelta : 0\n const indexDelta = Number.isFinite(input.indexDelta) ? input.indexDelta : 0\n const vectorDelta = Number.isFinite(input.vectorDelta) ? input.vectorDelta! : 0\n if (baseDelta === 0 && indexDelta === 0 && vectorDelta === 0) return []\n const withDeleted = input.withDeleted === true\n const tenantId = input.tenantId ?? null\n const organizationId = input.organizationId ?? null\n return [\n {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n deltaBase: baseDelta,\n deltaIndex: indexDelta,\n deltaVector: vectorDelta,\n },\n ]\n}\n"],
5
- "mappings": "AACA,SAAwC,WAAW;AACnD,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,aAAa,EAAE,MAAM,EAAE,WAAW,WAAW,CAAC;AAmD1E,MAAM,eAAe,oBAAI,IAAqB;AAM9C,MAAM,uBAAuB,oBAAI,IAA8B;AAC/D,MAAM,kCAAkC;AACjC,MAAM,2BAA2B;AAExC,SAAS,QAAQ,OAAwB;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,KAAK;AAC3B,WAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EAC5C;AACA,MAAI,SAAS,QAAQ,OAAQ,MAAoC,YAAY,YAAY;AACvF,UAAM,SAAS,OAAQ,MAAoC,QAAQ,CAAC;AACpE,WAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,SAAS,8BAA8B,OAA0C;AAC/E,SAAO,SAAS;AAClB;AAEA,SAAS,2BACP,IACA,QACA,gBACI;AACJ,QAAM,SAAS,8BAA8B,kBAAkB,IAAI;AACnE,MAAI,WAAW,iCAAiC;AAC9C,WAAO,GAAG,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,MACjC,GAAG,QAAe,MAAM,IAAI;AAAA,MAC5B,GAAG,QAAe,KAAK,+BAA+B;AAAA,IACxD,CAAC,CAAC;AAAA,EACJ;AACA,SAAO,GAAG,MAAM,QAAe,KAAK,MAAM;AAC5C;AAEA,eAAe,iBACb,IACA,OACoE;AACpE,QAAM,EAAE,YAAY,UAAU,gBAAgB,YAAY,IAAI;AAC9D,MAAI,QAAQ,GACT,WAAW,uBAA8B,EACzC,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,MAAM,eAAsB,KAAK,UAAU,EAC3C,MAAM,gBAAuB,KAAK,gBAAgB,IAAI,EACtD,QAAQ,gBAAuB,MAAM;AACxC,UAAQ,YAAY,OAChB,MAAM,MAAM,aAAoB,MAAM,IAAW,IACjD,MAAM,MAAM,aAAoB,KAAK,QAAQ;AACjD,UAAQ,2BAA2B,OAAc,mBAAmB,kBAAkB,IAAI;AAC1F,QAAM,MAAM,MAAM,MAAM,iBAAiB;AACzC,SAAO,OAAO;AAChB;AAEA,eAAe,2BACb,IACA,OACA,QACe;AACf,MAAI,QAAQ,GACT,WAAW,uBAA8B,EACzC,MAAM,eAAsB,KAAK,MAAM,UAAU,EACjD,MAAM,gBAAuB,KAAK,MAAM,gBAAgB,IAAI;AAC/D,UAAQ,MAAM,YAAY,OACtB,MAAM,MAAM,aAAoB,MAAM,IAAW,IACjD,MAAM,MAAM,aAAoB,KAAK,MAAM,QAAQ;AACvD,UAAQ,2BAA2B,OAAc,mBAAmB,MAAM,kBAAkB,IAAI;AAChG,MAAI,QAAQ;AACV,YAAQ,MAAM,MAAM,MAAa,MAAM,MAAM;AAAA,EAC/C;AACA,QAAM,MAAM,QAAQ;AACtB;AAEA,eAAe,kBACb,IACA,OACA,QACe;AACf,QAAM,cAAc,8BAA8B,MAAM,kBAAkB,IAAI;AAC9E,MAAI,MAAM,kBAAkB,MAAM;AAChC,QAAI,QAAQ,GACT,WAAW,uBAA8B,EACzC,MAAM,eAAsB,KAAK,MAAM,UAAU,EACjD,MAAM,gBAAuB,KAAK,MAAM,gBAAgB,IAAI,EAC5D,MAAM,mBAA0B,MAAM,IAAW;AACpD,YAAQ,MAAM,YAAY,OACtB,MAAM,MAAM,aAAoB,MAAM,IAAW,IACjD,MAAM,MAAM,aAAoB,KAAK,MAAM,QAAQ;AACvD,UAAM,MAAM,QAAQ;AAAA,EACtB;AAEA,QAAM,OAAO,MAAM,GAChB,WAAW,uBAA8B,EACzC,OAAO;AAAA,IACN,aAAa,MAAM;AAAA,IACnB,WAAW,MAAM,YAAY;AAAA,IAC7B,iBAAiB;AAAA,IACjB,cAAc,MAAM,gBAAgB;AAAA,IACpC,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,sBAAsB,OAAO;AAAA,IAC7B,cAAc;AAAA,EAChB,CAAQ,EACP,WAAW,CAAC,OAAY,GACtB,QAAQ,CAAC,eAAe,aAAa,mBAAmB,cAAc,CAAC,EACvE,YAAY;AAAA,IACX,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,sBAAsB,OAAO;AAAA,IAC7B,cAAc;AAAA,EAChB,CAAQ,CAAC,EACV,UAAU,CAAC,IAAW,CAAC,EACvB,QAAQ;AAEX,QAAM,SAAS,OAAO,CAAC,GAAG,MAAM;AAChC,QAAM,2BAA2B,IAAI,OAAO,MAAM;AACpD;AAEA,eAAsB,qBACpB,IACA,OACyG;AACzG,QAAM,aAAa,OAAO,MAAM,cAAc,EAAE;AAChD,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,MAAM,MAAM,iBAAiB,IAAI;AAAA,IACrC;AAAA,IACA,UAAU,MAAM,YAAY;AAAA,IAC5B,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,aAAa,MAAM,gBAAgB;AAAA,EACrC,CAAC;AACD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,cAAc,IAAI,wBAAwB,OAAO,IAAI,eAAgB,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,IAAI;AAC3H,SAAO;AAAA,IACL,YAAY,IAAI;AAAA,IAChB,eAAe,IAAI;AAAA,IACnB,sBAAsB,IAAI;AAAA,IAC1B,cAAc,eAAe;AAAA,IAC7B,WAAW,QAAQ,IAAI,UAAU;AAAA,IACjC,cAAc,QAAQ,IAAI,aAAa;AAAA,IACvC,oBAAoB,QAAQ,IAAI,oBAAoB;AAAA,EACtD;AACF;AAEA,eAAsB,sBACpB,IACA,OACwC;AACxC,QAAM,cAAc,MAAM;AAAA,IACxB,IAAI,KAAK,MAAM,eAAe,CAAC,GAAG,IAAI,CAAC,OAAO,OAAO,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;AAAA,EAC/F;AACA,QAAM,SAAS,oBAAI,IAA8B;AACjD,MAAI,YAAY,WAAW,EAAG,QAAO;AAErC,QAAM,cAAc,MAAM,gBAAgB;AAC1C,MAAI,QAAQ,GACT,WAAW,uBAA8B,EACzC,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,MAAM,eAAsB,MAAM,WAAW,EAC7C,MAAM,gBAAuB,KAAK,WAAW,EAC7C,QAAQ,gBAAuB,MAAM;AACxC,UAAQ,MAAM,YAAY,OACtB,MAAM,MAAM,aAAoB,MAAM,IAAW,IACjD,MAAM,MAAM,aAAoB,KAAK,MAAM,QAAQ;AACvD,UAAQ,2BAA2B,OAAc,mBAAmB,MAAM,kBAAkB,IAAI;AAEhG,QAAM,OAAO,MAAM,MAAM,QAAQ;AACjC,aAAW,OAAO,QAAQ,CAAC,GAAG;AAC5B,UAAM,aAAa,OAAO,IAAI,eAAe,EAAE;AAE/C,QAAI,CAAC,cAAc,OAAO,IAAI,UAAU,EAAG;AAC3C,UAAM,cAAc,IAAI,wBAAwB,OAC5C,IAAI,eACH,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,IAAI;AACrD,WAAO,IAAI,YAAY;AAAA,MACrB,YAAY,IAAI;AAAA,MAChB,eAAe,IAAI;AAAA,MACnB,sBAAsB,IAAI;AAAA,MAC1B,cAAc,eAAe;AAAA,MAC7B,WAAW,QAAQ,IAAI,UAAU;AAAA,MACjC,cAAc,QAAQ,IAAI,aAAa;AAAA,MACvC,oBAAoB,QAAQ,IAAI,oBAAoB;AAAA,IACtD,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAsB,yBACpB,IACA,aACA,SACe;AACf,MAAI,CAAC,YAAY,OAAQ;AACzB,QAAM,KAAK,SAAS,OAAS,GAAW,UAAU;AAClD,QAAM,aAAa,qBAAqB,WAAW;AACnD,aAAW,SAAS,YAAY;AAC9B,UAAM,QAAQ,MAAM;AACpB,UAAM,WAAW,MAAM,iBAAiB,IAAI,KAAK;AACjD,UAAM,cAAc,WAAW,QAAQ,SAAS,UAAU,IAAI;AAC9D,UAAM,eAAe,WAAW,QAAQ,SAAS,aAAa,IAAI;AAClE,UAAM,gBAAgB,WAAW,QAAQ,SAAS,oBAAoB,IAAI;AAC1E,UAAM,WAAW,KAAK,IAAI,cAAc,MAAM,WAAW,CAAC;AAC1D,UAAM,YAAY,KAAK,IAAI,eAAe,MAAM,YAAY,CAAC;AAC7D,UAAM,aAAa,KAAK,IAAI,gBAAgB,MAAM,aAAa,CAAC;AAEhE,UAAM,kBAAkB,IAAI,OAAO;AAAA,MACjC,WAAW;AAAA,MACX,cAAc;AAAA,MACd,oBAAoB;AAAA,IACtB,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,wBAAwB,IAAiB,YAAmC;AAChG,MAAI,CAAC,WAAY;AACjB,QAAM,GACH,WAAW,uBAA8B,EACzC,MAAM,eAAsB,KAAK,UAAU,EAC3C,QAAQ;AACb;AAEA,eAAe,eAAe,IAAiB,OAAe,QAAkC;AAC9F,QAAM,MAAM,GAAG,KAAK,IAAI,MAAM;AAC9B,MAAI,aAAa,IAAI,GAAG,EAAG,QAAO,aAAa,IAAI,GAAG;AACtD,QAAM,UAAU,qBAAqB,IAAI,GAAG;AAC5C,MAAI,QAAS,QAAO;AACpB,QAAM,WAAW,YAAY;AAC3B,UAAM,SAAS,MAAM,GAClB,WAAW,4BAAmC,EAC9C,OAAO,OAAe,GAAG,SAAS,CAAC,EACnC,MAAM,oCAA6C,EACnD,MAAM,cAAqB,KAAK,KAAK,EACrC,MAAM,eAAsB,KAAK,MAAM,EACvC,iBAAiB;AACpB,UAAM,UAAU,CAAC,CAAC;AAClB,iBAAa,IAAI,KAAK,OAAO;AAC7B,WAAO;AAAA,EACT,GAAG;AACH,uBAAqB,IAAI,KAAK,OAAO;AACrC,MAAI;AACF,WAAO,MAAM;AAAA,EACf,UAAE;AACA,yBAAqB,OAAO,GAAG;AAAA,EACjC;AACF;AAUA,eAAsB,iBAAiB,IAAiB,QAAsC;AAC5F,QAAM,UAAiE,CAAC;AACxE,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,OAAO,OAAO,SAAS,EAAE;AACvC,UAAM,SAAS,OAAO,OAAO,UAAU,EAAE;AACzC,QAAI,CAAC,SAAS,CAAC,OAAQ;AACvB,UAAM,MAAM,GAAG,KAAK,IAAI,MAAM;AAC9B,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,aAAa,IAAI,GAAG,KAAK,qBAAqB,IAAI,GAAG,EAAG;AAC5D,YAAQ,KAAK,EAAE,OAAO,QAAQ,IAAI,CAAC;AAAA,EACrC;AACA,MAAI,CAAC,QAAQ,OAAQ;AAErB,QAAM,SAAS,MAAM,KAAK,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,CAAC;AACtE,QAAM,UAAU,MAAM,KAAK,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,MAAM,CAAC,CAAC;AAExE,QAAM,gBAAgB,YAAkC;AACtD,UAAM,OAAO,MAAM,GAChB,WAAW,4BAAmC,EAC9C,OAAO,CAAC,cAAqB,aAAoB,CAAC,EAClD,MAAM,oCAA6C,EACnD,MAAM,cAAqB,MAAM,MAAM,EACvC,MAAM,eAAsB,MAAM,OAAO,EACzC,QAAQ;AACX,WAAO,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,GAAG,IAAI,UAAU,IAAI,IAAI,WAAW,EAAE,CAAC;AAAA,EAC1E,GAAG;AAEH,aAAW,SAAS,SAAS;AAC3B,UAAM,eAAe,aAAa,KAAK,CAAC,YAAY;AAClD,YAAM,QAAQ,QAAQ,IAAI,MAAM,GAAG;AACnC,mBAAa,IAAI,MAAM,KAAK,KAAK;AACjC,aAAO;AAAA,IACT,CAAC;AAMD,iBAAa,MAAM,MAAM,MAAS;AAClC,yBAAqB,IAAI,MAAM,KAAK,YAAY;AAAA,EAClD;AAEA,MAAI;AACF,UAAM;AAAA,EACR,UAAE;AACA,eAAW,SAAS,QAAS,sBAAqB,OAAO,MAAM,GAAG;AAAA,EACpE;AACF;AAEA,eAAsB,wBACpB,IACA,OACe;AACf,QAAM,aAAa,OAAO,MAAM,cAAc,EAAE;AAChD,MAAI,CAAC,WAAY;AACjB,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,QAAM,cAAc,MAAM,gBAAgB;AAE1C,QAAM,KAAM,GAAW,UAAU;AACjC,QAAM,YAAY,uBAAuB,IAAI,UAAU;AAEvD,QAAM,SAAS,MAAM,eAAe,IAAI,WAAW,iBAAiB;AACpE,QAAM,YAAY,MAAM,eAAe,IAAI,WAAW,WAAW;AACjE,QAAM,aAAa,MAAM,eAAe,IAAI,WAAW,YAAY;AAEnE,MAAI,mBAAmB,QAAQ,CAAC,OAAQ;AACxC,MAAI,aAAa,QAAQ,CAAC,UAAW;AAErC,MAAI,YAAY,GACb,WAAW,GAAG,SAAS,OAAc,EACrC,OAAO,cAAc,GAAG,OAAO,CAAC;AACnC,MAAI,mBAAmB,QAAQ,OAAQ,aAAY,UAAU,MAAM,qBAA4B,KAAK,cAAc;AAClH,MAAI,aAAa,QAAQ,UAAW,aAAY,UAAU,MAAM,eAAsB,KAAK,QAAQ;AACnG,MAAI,CAAC,eAAe,WAAY,aAAY,UAAU,MAAM,gBAAuB,MAAM,IAAW;AAEpG,MAAI,aAAa,GACd,WAAW,sBAA6B,EACxC,OAAO,cAAc,GAAG,OAAO,CAAC,EAChC,MAAM,kBAAyB,KAAK,UAAU;AACjD,MAAI,mBAAmB,KAAM,cAAa,WAAW,MAAM,sBAA6B,KAAK,cAAc;AAC3G,MAAI,aAAa,KAAM,cAAa,WAAW,MAAM,gBAAuB,KAAK,QAAQ;AACzF,MAAI,CAAC,YAAa,cAAa,WAAW,MAAM,iBAAwB,MAAM,IAAW;AAEzF,QAAM,sBAAsB,YAAyC;AACnE,UAAM,iBAAiB,MAAM,eAAe,IAAI,iBAAiB,WAAW;AAC5E,QAAI,CAAC,kBAAkB,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAErF,QAAI;AACF,UAAI,cAAc,GACf,WAAW,eAAsB,EACjC,OAAO,cAAc,GAAG,OAAO,CAAC,EAChC,MAAM,aAAoB,KAAK,UAAU,EACzC,MAAM,aAAoB,KAAK,QAAQ;AAC1C,UAAI,mBAAmB,MAAM;AAC3B,sBAAc,YAAY,MAAM,mBAA0B,KAAK,cAAc;AAAA,MAC/E;AACA,YAAM,YAAY,MAAM,YAAY,iBAAiB;AACrD,aAAO,QAAQ,WAAW,KAAK;AAAA,IACjC,SAAS,KAAK;AACZ,aAAO,KAAK,wDAAwD;AAAA,QAClE;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,MAC9C,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,CAAC,SAAS,UAAU,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzD,UAAU,iBAAiB;AAAA,IAC3B,WAAW,iBAAiB;AAAA,IAC5B;AAAA,EACF,CAAC;AAED,QAAM,YAAY,QAAQ,SAAS,KAAK;AACxC,QAAM,aAAa,QAAQ,UAAU,KAAK;AAE1C,QAAM,oBAAoB,IAAI,EAAE,YAAY,UAAU,gBAAgB,YAAY,GAAG;AAAA,IACnF;AAAA,IACA,cAAc;AAAA,IACd;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,oBACpB,IACA,OACA,QACe;AACf,QAAM,aAAa,OAAO,MAAM,cAAc,EAAE;AAChD,MAAI,CAAC,WAAY;AACjB,QAAM,KAAM,GAAW,UAAU;AACjC,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,QAAM,cAAc,MAAM,gBAAgB;AAC1C,QAAM,WAAW,MAAM,iBAAiB,IAAI;AAAA,IAC1C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,YAAY,OAAO,cAAc,SACnC,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,SAAS,CAAC,CAAC,IACjD,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,UAAU,UAAU,CAAC,CAAC;AACzD,QAAM,aAAa,OAAO,iBAAiB,SACvC,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,YAAY,CAAC,CAAC,IACpD,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,UAAU,aAAa,CAAC,CAAC;AAC5D,QAAM,cAAc,OAAO,gBAAgB,SACvC,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,WAAW,CAAC,CAAC,IACnD,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,UAAU,oBAAoB,CAAC,CAAC;AACnE,QAAM,kBAAkB,IAAI,EAAE,YAAY,UAAU,gBAAgB,YAAY,GAAG;AAAA,IACjF;AAAA,IACA,cAAc;AAAA,IACd,oBAAoB;AAAA,EACtB,CAAC;AACH;AASA,SAAS,qBAAqB,aAA2D;AACvF,QAAM,MAAM,oBAAI,IAAkC;AAClD,aAAW,OAAO,aAAa;AAC7B,QAAI,CAAC,KAAK,WAAY;AACtB,UAAM,YAAY,OAAO,SAAS,IAAI,SAAS,IAAI,IAAI,YAAY;AACnE,UAAM,aAAa,OAAO,SAAS,IAAI,UAAU,IAAI,IAAI,aAAa;AACtE,UAAM,cAAc,OAAO,SAAS,IAAI,WAAW,IAAI,IAAI,cAAe;AAC1E,QAAI,cAAc,KAAK,eAAe,KAAK,gBAAgB,EAAG;AAC9D,UAAM,QAAuB;AAAA,MAC3B,YAAY,IAAI;AAAA,MAChB,UAAU,IAAI,YAAY;AAAA,MAC1B,gBAAgB,IAAI,kBAAkB;AAAA,MACtC,aAAa,IAAI,gBAAgB;AAAA,IACnC;AACA,UAAM,MAAM,SAAS,KAAK;AAC1B,UAAM,WAAW,IAAI,IAAI,GAAG;AAC5B,QAAI,UAAU;AACZ,eAAS,aAAa;AACtB,eAAS,cAAc;AACvB,eAAS,eAAe;AAAA,IAC1B,OAAO;AACL,UAAI,IAAI,KAAK,EAAE,OAAO,WAAW,YAAY,YAAY,CAAC;AAAA,IAC5D;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI,OAAO,CAAC;AAChC;AAEA,SAAS,SAAS,OAA8B;AAC9C,QAAM,SAAS,MAAM,YAAY;AACjC,QAAM,MAAM,8BAA8B,MAAM,kBAAkB,IAAI;AACtE,QAAM,UAAU,MAAM,gBAAgB,OAAO,MAAM;AACnD,SAAO,GAAG,MAAM,UAAU,IAAI,MAAM,IAAI,GAAG,IAAI,OAAO;AACxD;AAEO,SAAS,0BAA0B,OAAiD;AACzF,QAAM,aAAa,OAAO,MAAM,cAAc,EAAE;AAChD,MAAI,CAAC,WAAY,QAAO,CAAC;AACzB,QAAM,YAAY,OAAO,SAAS,MAAM,SAAS,IAAI,MAAM,YAAY;AACvE,QAAM,aAAa,OAAO,SAAS,MAAM,UAAU,IAAI,MAAM,aAAa;AAC1E,QAAM,cAAc,OAAO,SAAS,MAAM,WAAW,IAAI,MAAM,cAAe;AAC9E,MAAI,cAAc,KAAK,eAAe,KAAK,gBAAgB,EAAG,QAAO,CAAC;AACtE,QAAM,cAAc,MAAM,gBAAgB;AAC1C,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,SAAO;AAAA,IACL;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,aAAa;AAAA,IACf;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, type Transaction, sql } from 'kysely'\nimport { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'coverage' })\n\ntype CoverageExecutor = Kysely<any> | Transaction<any>\n\nexport type CoverageScope = {\n entityType: string\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\ntype CoverageRow = {\n base_count: unknown\n indexed_count: unknown\n vector_indexed_count: unknown\n refreshed_at: Date | string | null\n}\n\nexport type CoverageSnapshot = CoverageRow & {\n baseCount: number\n indexedCount: number\n vectorIndexedCount: number\n}\n\nexport type CoverageBatchScope = {\n entityTypes: readonly string[]\n tenantId?: string | null\n organizationId?: string | null\n withDeleted?: boolean\n}\n\nexport type CoverageAdjustment = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n deltaBase: number\n deltaIndex: number\n deltaVector?: number\n}\n\nexport type CoverageDeltaInput = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n withDeleted?: boolean\n baseDelta: number\n indexDelta: number\n vectorDelta?: number\n}\n\nconst COLUMN_CACHE = new Map<string, boolean>()\n// In-flight de-dup: without this, N concurrent `tableHasColumn` callers for the same\n// (table, column) \u2014 e.g. every entity type's `refreshCoverageSnapshot` asking about\n// `vector_search.entity_id` \u2014 would each see a cold cache and fire their own identical\n// `information_schema.columns` query, since the cache is only populated after a query\n// resolves. Tracking the in-flight promise lets late arrivals await the first one instead.\nconst COLUMN_CACHE_PENDING = new Map<string, Promise<boolean>>()\nconst GLOBAL_ORGANIZATION_PLACEHOLDER = '00000000-0000-0000-0000-000000000000'\nexport const COVERAGE_ORG_PLACEHOLDER = GLOBAL_ORGANIZATION_PLACEHOLDER\n\nfunction toCount(value: unknown): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n }\n if (value != null && typeof (value as { valueOf: () => number }).valueOf === 'function') {\n const parsed = Number((value as { valueOf: () => number }).valueOf())\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n}\n\nfunction normalizeOrganizationForStore(orgId: string | null | undefined): string {\n return orgId ?? GLOBAL_ORGANIZATION_PLACEHOLDER\n}\n\nfunction applyOrganizationCondition<QB extends { where: (...args: any[]) => QB }>(\n qb: QB,\n column: string,\n organizationId: string | null | undefined,\n): QB {\n const stored = normalizeOrganizationForStore(organizationId ?? null)\n if (stored === GLOBAL_ORGANIZATION_PLACEHOLDER) {\n return qb.where((eb: any) => eb.or([\n eb(column as any, 'is', null),\n eb(column as any, '=', GLOBAL_ORGANIZATION_PLACEHOLDER),\n ]))\n }\n return qb.where(column as any, '=', stored)\n}\n\nasync function fetchCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope\n): Promise<(CoverageRow & { organization_id: string | null }) | null> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n .orderBy('refreshed_at' as any, 'desc')\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null)\n const row = await query.executeTakeFirst() as (CoverageRow & { organization_id: string | null }) | undefined\n return row ?? null\n}\n\nasync function pruneDuplicateCoverageRows(\n db: CoverageExecutor,\n scope: CoverageScope,\n keepId: string | null\n): Promise<void> {\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n query = scope.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', scope.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', scope.organizationId ?? null)\n if (keepId) {\n query = query.where('id' as any, '!=', keepId)\n }\n await query.execute()\n}\n\nasync function upsertCoverageRow(\n db: CoverageExecutor,\n scope: CoverageScope,\n counts: { baseCount: number; indexedCount: number; vectorIndexedCount: number }\n): Promise<void> {\n const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null)\n if (scope.organizationId == null) {\n let purge = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', scope.entityType)\n .where('with_deleted' as any, '=', scope.withDeleted === true)\n .where('organization_id' as any, 'is', null as any)\n purge = scope.tenantId == null\n ? purge.where('tenant_id' as any, 'is', null as any)\n : purge.where('tenant_id' as any, '=', scope.tenantId)\n await purge.execute()\n }\n\n const rows = await db\n .insertInto('entity_index_coverage' as any)\n .values({\n entity_type: scope.entityType,\n tenant_id: scope.tenantId ?? null,\n organization_id: storedOrgId,\n with_deleted: scope.withDeleted === true,\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'tenant_id', 'organization_id', 'with_deleted'])\n .doUpdateSet({\n base_count: counts.baseCount,\n indexed_count: counts.indexedCount,\n vector_indexed_count: counts.vectorIndexedCount,\n refreshed_at: sql`now()`,\n } as any))\n .returning(['id' as any])\n .execute() as Array<{ id: string }>\n\n const keepId = rows?.[0]?.id ?? null\n await pruneDuplicateCoverageRows(db, scope, keepId)\n}\n\nexport async function readCoverageSnapshot(\n db: Kysely<any>,\n scope: CoverageScope\n): Promise<(CoverageRow & { baseCount: number; indexedCount: number; vectorIndexedCount: number }) | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const row = await fetchCoverageRow(db, {\n entityType,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n withDeleted: scope.withDeleted === true,\n })\n if (!row) return null\n const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n return {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n }\n}\n\nexport async function readCoverageSnapshots(\n db: Kysely<any>,\n batch: CoverageBatchScope\n): Promise<Map<string, CoverageSnapshot>> {\n const entityTypes = Array.from(\n new Set((batch.entityTypes ?? []).map((id) => String(id || '')).filter((id) => id.length > 0))\n )\n const result = new Map<string, CoverageSnapshot>()\n if (entityTypes.length === 0) return result\n\n const withDeleted = batch.withDeleted === true\n let query = db\n .selectFrom('entity_index_coverage' as any)\n .select([\n 'entity_type' as any,\n 'base_count' as any,\n 'indexed_count' as any,\n 'vector_indexed_count' as any,\n 'refreshed_at' as any,\n 'organization_id' as any,\n ])\n .where('entity_type' as any, 'in', entityTypes)\n .where('with_deleted' as any, '=', withDeleted)\n .orderBy('refreshed_at' as any, 'desc')\n query = batch.tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', batch.tenantId)\n query = applyOrganizationCondition(query as any, 'organization_id', batch.organizationId ?? null)\n\n const rows = await query.execute() as Array<CoverageRow & { entity_type: string }>\n for (const row of rows ?? []) {\n const entityType = String(row.entity_type || '')\n // Rows are ordered by refreshed_at desc, so the first row seen per entity is the latest.\n if (!entityType || result.has(entityType)) continue\n const refreshedAt = row.refreshed_at instanceof Date\n ? row.refreshed_at\n : (row.refreshed_at ? new Date(row.refreshed_at) : null)\n result.set(entityType, {\n base_count: row.base_count,\n indexed_count: row.indexed_count,\n vector_indexed_count: row.vector_indexed_count,\n refreshed_at: refreshedAt ?? null,\n baseCount: toCount(row.base_count),\n indexedCount: toCount(row.indexed_count),\n vectorIndexedCount: toCount(row.vector_indexed_count),\n })\n }\n return result\n}\n\nexport async function applyCoverageAdjustments(\n em: EntityManager,\n adjustments: CoverageAdjustment[],\n options?: { trx?: CoverageExecutor },\n): Promise<void> {\n if (!adjustments.length) return\n const db = options?.trx ?? ((em as any).getKysely() as Kysely<any>)\n const aggregated = aggregateAdjustments(adjustments)\n for (const entry of aggregated) {\n const scope = entry.scope\n const existing = await fetchCoverageRow(db, scope)\n const currentBase = existing ? toCount(existing.base_count) : 0\n const currentIndex = existing ? toCount(existing.indexed_count) : 0\n const currentVector = existing ? toCount(existing.vector_indexed_count) : 0\n const nextBase = Math.max(currentBase + entry.deltaBase, 0)\n const nextIndex = Math.max(currentIndex + entry.deltaIndex, 0)\n const nextVector = Math.max(currentVector + entry.deltaVector, 0)\n\n await upsertCoverageRow(db, scope, {\n baseCount: nextBase,\n indexedCount: nextIndex,\n vectorIndexedCount: nextVector,\n })\n }\n}\n\nexport async function deleteCoverageForEntity(db: Kysely<any>, entityType: string): Promise<void> {\n if (!entityType) return\n await db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .execute()\n}\n\nasync function deleteCoverageScope(db: Kysely<any>, scope: CoverageScope): Promise<void> {\n const { entityType, tenantId, organizationId, withDeleted } = scope\n if (!entityType) return\n let query = db\n .deleteFrom('entity_index_coverage' as any)\n .where('entity_type' as any, '=', entityType)\n .where('with_deleted' as any, '=', withDeleted === true)\n query = tenantId == null\n ? query.where('tenant_id' as any, 'is', null as any)\n : query.where('tenant_id' as any, '=', tenantId)\n await applyOrganizationCondition(query as any, 'organization_id', organizationId ?? null).execute()\n}\n\nasync function tableHasColumn(db: Kysely<any>, table: string, column: string): Promise<boolean> {\n const key = `${table}.${column}`\n if (COLUMN_CACHE.has(key)) return COLUMN_CACHE.get(key)!\n const pending = COLUMN_CACHE_PENDING.get(key)\n if (pending) return pending\n const promise = (async () => {\n const exists = await db\n .selectFrom('information_schema.columns' as any)\n .select(sql<number>`1`.as('present'))\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, '=', table)\n .where('column_name' as any, '=', column)\n .executeTakeFirst()\n const present = !!exists\n COLUMN_CACHE.set(key, present)\n return present\n })()\n COLUMN_CACHE_PENDING.set(key, promise)\n try {\n return await promise\n } finally {\n COLUMN_CACHE_PENDING.delete(key)\n }\n}\n\nexport type ColumnCheck = { table: string; column: string }\n\n// Batches the `information_schema.columns` introspection used by `refreshCoverageSnapshot`\n// into a single query for a whole set of (table, column) pairs, and pre-populates\n// `COLUMN_CACHE_PENDING` for every pair before that query even runs. Callers of\n// `coverage_warmup.ts` use this so its many concurrently-dispatched `coverage.refresh`\n// subscribers hit an already-primed (or in-flight) cache instead of each doing their own\n// per-table introspection round trip.\nexport async function primeColumnCache(db: Kysely<any>, checks: ColumnCheck[]): Promise<void> {\n const missing: Array<{ table: string; column: string; key: string }> = []\n const seen = new Set<string>()\n for (const check of checks) {\n const table = String(check?.table || '')\n const column = String(check?.column || '')\n if (!table || !column) continue\n const key = `${table}.${column}`\n if (seen.has(key)) continue\n seen.add(key)\n if (COLUMN_CACHE.has(key) || COLUMN_CACHE_PENDING.has(key)) continue\n missing.push({ table, column, key })\n }\n if (!missing.length) return\n\n const tables = Array.from(new Set(missing.map((entry) => entry.table)))\n const columns = Array.from(new Set(missing.map((entry) => entry.column)))\n\n const batchPromise = (async (): Promise<Set<string>> => {\n const rows = await db\n .selectFrom('information_schema.columns' as any)\n .select(['table_name' as any, 'column_name' as any])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, 'in', tables)\n .where('column_name' as any, 'in', columns)\n .execute() as Array<{ table_name: string; column_name: string }>\n return new Set(rows.map((row) => `${row.table_name}.${row.column_name}`))\n })()\n\n for (const entry of missing) {\n const entryPromise = batchPromise.then((present) => {\n const value = present.has(entry.key)\n COLUMN_CACHE.set(entry.key, value)\n return value\n })\n // Mark the stored promise as handled: when the batch query fails and no\n // `tableHasColumn` caller has adopted this entry yet (the common case \u2014 the warmup\n // awaits priming before dispatching any refresh), an orphaned rejection would\n // otherwise crash a plain-Node event worker via unhandledRejection. Awaiting\n // callers still observe the rejection through the stored reference.\n entryPromise.catch(() => undefined)\n COLUMN_CACHE_PENDING.set(entry.key, entryPromise)\n }\n\n try {\n await batchPromise\n } finally {\n for (const entry of missing) COLUMN_CACHE_PENDING.delete(entry.key)\n }\n}\n\nexport async function refreshCoverageSnapshot(\n em: EntityManager,\n scope: CoverageScope,\n): Promise<{ baseCount: number; indexedCount: number } | null> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return null\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n\n const db = (em as any).getKysely() as Kysely<any>\n const baseTable = resolveEntityTableName(em, entityType)\n\n const hasOrg = await tableHasColumn(db, baseTable, 'organization_id')\n const hasTenant = await tableHasColumn(db, baseTable, 'tenant_id')\n const hasDeleted = await tableHasColumn(db, baseTable, 'deleted_at')\n\n // A scope the base table cannot express must not narrow the index side either. Index rows\n // can carry an organization the base table has no column for \u2014 `organizations` has no\n // `organization_id` yet its index rows derive one from the record id. Filtering only the\n // index side compares two different populations and reports a gap no reindex can close;\n // returning early instead of recounting left the previous snapshot frozen, which is what\n // surfaced as a permanent \"out of sync\" row for `directory:organization`.\n //\n // Tenant is different: a base table without `tenant_id` (`user_roles`) cannot be counted\n // per tenant at all, and writing the cross-tenant total into one tenant's row would leak\n // another tenant's volume into it. Drop the unusable scoped row instead, leaving only the\n // global row \u2014 which is the one that can be true \u2014 rather than freezing a stale count.\n const scopeOrg = organizationId !== null && hasOrg\n if (tenantId !== null && !hasTenant) {\n await deleteCoverageScope(db, { entityType, tenantId, organizationId, withDeleted })\n return null\n }\n const scopeTenant = tenantId !== null && hasTenant\n\n let baseQuery = db\n .selectFrom(`${baseTable} as b` as any)\n .select(sql`count(*)`.as('count'))\n if (scopeOrg) baseQuery = baseQuery.where('b.organization_id' as any, '=', organizationId)\n if (scopeTenant) baseQuery = baseQuery.where('b.tenant_id' as any, '=', tenantId)\n if (!withDeleted && hasDeleted) baseQuery = baseQuery.where('b.deleted_at' as any, 'is', null as any)\n\n let indexQuery = db\n .selectFrom('entity_indexes as ei' as any)\n .select(sql`count(*)`.as('count'))\n .where('ei.entity_type' as any, '=', entityType)\n if (scopeOrg) indexQuery = indexQuery.where('ei.organization_id' as any, '=', organizationId)\n if (scopeTenant) indexQuery = indexQuery.where('ei.tenant_id' as any, '=', tenantId)\n if (!withDeleted) indexQuery = indexQuery.where('ei.deleted_at' as any, 'is', null as any)\n\n const vectorCountPromise = (async (): Promise<number | undefined> => {\n const hasVectorTable = await tableHasColumn(db, 'vector_search', 'entity_id')\n if (!hasVectorTable || typeof tenantId !== 'string' || tenantId.length === 0) return undefined\n\n try {\n let vectorQuery = db\n .selectFrom('vector_search' as any)\n .select(sql`count(*)`.as('count'))\n .where('entity_id' as any, '=', entityType)\n .where('tenant_id' as any, '=', tenantId)\n if (organizationId !== null) {\n vectorQuery = vectorQuery.where('organization_id' as any, '=', organizationId)\n }\n const vectorRow = await vectorQuery.executeTakeFirst() as { count: unknown } | undefined\n return toCount(vectorRow?.count)\n } catch (err) {\n logger.warn('Failed to resolve vector count for coverage snapshot', {\n entityType,\n tenantId,\n organizationId,\n error: err instanceof Error ? err.message : err,\n })\n return undefined\n }\n })()\n\n const [baseRow, indexRow, vectorCount] = await Promise.all([\n baseQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n indexQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,\n vectorCountPromise,\n ])\n\n const baseCount = toCount(baseRow?.count)\n const indexCount = toCount(indexRow?.count)\n\n await writeCoverageCounts(em, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorCount,\n })\n\n return { baseCount, indexedCount: indexCount }\n}\n\nexport async function writeCoverageCounts(\n em: EntityManager,\n scope: CoverageScope,\n counts: { baseCount?: number; indexedCount?: number; vectorCount?: number }\n): Promise<void> {\n const entityType = String(scope.entityType || '')\n if (!entityType) return\n const db = (em as any).getKysely() as Kysely<any>\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n const withDeleted = scope.withDeleted === true\n const existing = await fetchCoverageRow(db, {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n })\n const baseCount = counts.baseCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.baseCount)))\n : Math.max(0, Math.trunc(toCount(existing?.base_count)))\n const indexCount = counts.indexedCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.indexedCount)))\n : Math.max(0, Math.trunc(toCount(existing?.indexed_count)))\n const vectorCount = counts.vectorCount !== undefined\n ? Math.max(0, Math.trunc(toCount(counts.vectorCount)))\n : Math.max(0, Math.trunc(toCount(existing?.vector_indexed_count)))\n await upsertCoverageRow(db, { entityType, tenantId, organizationId, withDeleted }, {\n baseCount,\n indexedCount: indexCount,\n vectorIndexedCount: vectorCount,\n })\n}\n\ntype AggregatedAdjustment = {\n scope: CoverageScope\n deltaBase: number\n deltaIndex: number\n deltaVector: number\n}\n\nfunction aggregateAdjustments(adjustments: CoverageAdjustment[]): AggregatedAdjustment[] {\n const map = new Map<string, AggregatedAdjustment>()\n for (const adj of adjustments) {\n if (!adj?.entityType) continue\n const deltaBase = Number.isFinite(adj.deltaBase) ? adj.deltaBase : 0\n const deltaIndex = Number.isFinite(adj.deltaIndex) ? adj.deltaIndex : 0\n const deltaVector = Number.isFinite(adj.deltaVector) ? adj.deltaVector! : 0\n if (deltaBase === 0 && deltaIndex === 0 && deltaVector === 0) continue\n const scope: CoverageScope = {\n entityType: adj.entityType,\n tenantId: adj.tenantId ?? null,\n organizationId: adj.organizationId ?? null,\n withDeleted: adj.withDeleted === true,\n }\n const key = scopeKey(scope)\n const existing = map.get(key)\n if (existing) {\n existing.deltaBase += deltaBase\n existing.deltaIndex += deltaIndex\n existing.deltaVector += deltaVector\n } else {\n map.set(key, { scope, deltaBase, deltaIndex, deltaVector })\n }\n }\n return Array.from(map.values())\n}\n\nfunction scopeKey(scope: CoverageScope): string {\n const tenant = scope.tenantId ?? '__tenant_null__'\n const org = normalizeOrganizationForStore(scope.organizationId ?? null)\n const deleted = scope.withDeleted === true ? '1' : '0'\n return `${scope.entityType}|${tenant}|${org}|${deleted}`\n}\n\nexport function createCoverageAdjustments(input: CoverageDeltaInput): CoverageAdjustment[] {\n const entityType = String(input.entityType || '')\n if (!entityType) return []\n const baseDelta = Number.isFinite(input.baseDelta) ? input.baseDelta : 0\n const indexDelta = Number.isFinite(input.indexDelta) ? input.indexDelta : 0\n const vectorDelta = Number.isFinite(input.vectorDelta) ? input.vectorDelta! : 0\n if (baseDelta === 0 && indexDelta === 0 && vectorDelta === 0) return []\n const withDeleted = input.withDeleted === true\n const tenantId = input.tenantId ?? null\n const organizationId = input.organizationId ?? null\n return [\n {\n entityType,\n tenantId,\n organizationId,\n withDeleted,\n deltaBase: baseDelta,\n deltaIndex: indexDelta,\n deltaVector: vectorDelta,\n },\n ]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -193,51 +193,57 @@ async function reindexEntity(em, options) {
193
193
  let jobFailed = false;
194
194
  const writeTotals = createEmptyUpsertIndexBatchResult();
195
195
  options?.onProgress?.({ processed, total, chunkSize: 0 });
196
- if (resetCoverage) {
197
- if (force) {
196
+ if (force && (resetCoverage || usingPartitions)) {
197
+ try {
198
+ let purgeQuery = db.deleteFrom("entity_indexes").where("entity_type", "=", entityType);
199
+ if (tenantId !== void 0) {
200
+ purgeQuery = purgeQuery.where(sql`tenant_id is not distinct from ${tenantId ?? null}`);
201
+ }
202
+ if (organizationId !== void 0) {
203
+ purgeQuery = purgeQuery.where(sql`organization_id is not distinct from ${organizationId ?? null}`);
204
+ }
205
+ if (usingPartitions && partitionIndex !== null) {
206
+ purgeQuery = purgeQuery.where(
207
+ sql`mod(abs(hashtext(entity_id::text)), ${partitionCountRaw}) = ${partitionIndex}`
208
+ );
209
+ }
210
+ await purgeQuery.execute();
211
+ } catch (error) {
212
+ logger.warn("Failed to purge index rows before force reindex", {
213
+ entityType,
214
+ tenantId: tenantId ?? null,
215
+ organizationId: organizationId ?? null,
216
+ partitionIndex: usingPartitions ? partitionIndex : null,
217
+ error: error instanceof Error ? error.message : error
218
+ });
219
+ }
220
+ }
221
+ if (force && resetCoverage && !usingPartitions && emitVectorize && eventBus) {
222
+ if (tenantId !== void 0) {
223
+ const payload = {
224
+ entityType,
225
+ tenantId: tenantId ?? null
226
+ };
227
+ if (organizationId !== void 0) payload.organizationId = organizationId ?? null;
198
228
  try {
199
- let purgeQuery = db.deleteFrom("entity_indexes").where("entity_type", "=", entityType);
200
- if (tenantId !== void 0) {
201
- purgeQuery = purgeQuery.where(sql`tenant_id is not distinct from ${tenantId ?? null}`);
202
- }
203
- if (organizationId !== void 0) {
204
- purgeQuery = purgeQuery.where(sql`organization_id is not distinct from ${organizationId ?? null}`);
205
- }
206
- await purgeQuery.execute();
207
- } catch (error) {
208
- logger.warn("Failed to purge index rows before force reindex", {
229
+ await eventBus.emitEvent("query_index.vectorize_purge", payload);
230
+ } catch (err) {
231
+ logger.warn("Failed to queue vector purge before force reindex", {
209
232
  entityType,
210
233
  tenantId: tenantId ?? null,
211
234
  organizationId: organizationId ?? null,
212
- error: error instanceof Error ? error.message : error
235
+ error: err instanceof Error ? err.message : err
213
236
  });
214
237
  }
215
- if (emitVectorize && eventBus) {
216
- if (tenantId !== void 0) {
217
- const payload = {
218
- entityType,
219
- tenantId: tenantId ?? null
220
- };
221
- if (organizationId !== void 0) payload.organizationId = organizationId ?? null;
222
- try {
223
- await eventBus.emitEvent("query_index.vectorize_purge", payload);
224
- } catch (err) {
225
- logger.warn("Failed to queue vector purge before force reindex", {
226
- entityType,
227
- tenantId: tenantId ?? null,
228
- organizationId: organizationId ?? null,
229
- error: err instanceof Error ? err.message : err
230
- });
231
- }
232
- } else {
233
- logger.warn("Skipping vector purge for force reindex without tenant scope", {
234
- entityType
235
- });
236
- }
237
- }
238
+ } else {
239
+ logger.warn("Skipping vector purge for force reindex without tenant scope", {
240
+ entityType
241
+ });
238
242
  }
243
+ }
244
+ if (resetCoverage) {
239
245
  const nowTs = Date.now();
240
- for (const scope of baseCounts.values()) {
246
+ for (const scope of usingPartitions ? [] : baseCounts.values()) {
241
247
  const key = `${entityType}|${scopeKey(scope.tenantId, scope.organizationId)}`;
242
248
  const last = lastCoverageReset.get(key) ?? 0;
243
249
  if (force || nowTs - last >= COVERAGE_REFRESH_THROTTLE_MS) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/query_index/lib/reindexer.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { resolveRegisteredEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport { decryptIndexDocForSearch, encryptIndexDocForStorage } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport {\n upsertIndexBatch,\n assertIndexBatchWritesLanded,\n createEmptyUpsertIndexBatchResult,\n mergeUpsertIndexBatchResults,\n QueryIndexBatchWriteError,\n type AnyRow,\n} from './batch'\nimport { refreshCoverageSnapshot, writeCoverageCounts, applyCoverageAdjustments } from './coverage'\nimport { prepareJob, updateJobProgress, finalizeJob, type JobScope } from './jobs'\nimport { purgeOrphans } from './stale'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport { isSearchDebugEnabled } from './search-tokens'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'reindexer' })\n\nexport type ReindexJobOptions = {\n entityType: string\n tenantId?: string | null\n organizationId?: string | null\n force?: boolean\n batchSize?: number\n emitVectorizeEvents?: boolean\n eventBus?: {\n emitEvent(event: string, payload: any, options?: any): Promise<void>\n }\n partitionCount?: number\n partitionIndex?: number\n resetCoverage?: boolean\n onProgress?: (info: { processed: number; total: number; chunkSize: number }) => void\n vectorService?: VectorIndexService | null\n}\n\nexport type ReindexJobResult = {\n processed: number\n total: number\n tenantScopes: Array<string | null>\n scopes: Array<{ tenantId: string | null; organizationId: string | null }>\n}\n\nexport const DEFAULT_REINDEX_PARTITIONS = 5\nconst DEFAULT_BATCH_SIZE = 500\n/**\n * Above this many failed records the purge exclusion list stops being a sane query, so\n * the purge is skipped entirely instead. Failing closed keeps stale rows; the alternative\n * deletes index entries the run failed to rebuild.\n */\nconst MAX_PURGE_EXCLUSIONS = 1000\nconst deriveOrgFromId = new Set<string>(['directory:organization'])\nconst COVERAGE_REFRESH_THROTTLE_MS = 5 * 60 * 1000\nconst lastCoverageReset = new Map<string, number>()\n\nconst REINDEX_DECRYPT_DEBUG_KEYS = ['display_name', 'first_name', 'last_name', 'brand_name', 'legal_name', 'primary_email', 'primary_phone'] as const\n\nexport type ReindexDecryptDebugPayload = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n keys: string[]\n}\n\nexport function buildReindexDecryptDebugPayload(\n entityType: string,\n doc: Record<string, unknown>,\n scope: { organizationId: string | null; tenantId: string | null },\n): ReindexDecryptDebugPayload {\n const presentKeys: string[] = []\n for (const key of REINDEX_DECRYPT_DEBUG_KEYS) {\n const value = doc[key]\n if (key in doc && value != null && value !== '') presentKeys.push(key)\n }\n return {\n entityType,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n keys: presentKeys,\n }\n}\n\nasync function cleanupLegacyJobScopes(\n db: Kysely<any>,\n options: {\n entityType: string\n organizationId: string | null\n tenantId: string | null\n activePartitionCount: number | null\n },\n): Promise<void> {\n await db\n .deleteFrom('entity_index_jobs' as any)\n .where('entity_type' as any, '=', options.entityType)\n .where(sql<boolean>`organization_id is not distinct from ${options.organizationId}`)\n .where(sql<boolean>`tenant_id is not distinct from ${options.tenantId}`)\n .where(sql<boolean>`partition_count is distinct from ${options.activePartitionCount}`)\n .execute()\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n}\n\nasync function getColumnSet(db: Kysely<any>, tableName: string): Promise<Set<string>> {\n try {\n const rows = await db\n .selectFrom('information_schema.columns' as any)\n .select(['column_name' as any])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, '=', tableName)\n .execute() as Array<{ column_name: string }>\n return new Set(rows.map((row) => String(row.column_name).toLowerCase()))\n } catch {\n return new Set<string>()\n }\n}\n\nexport async function reindexEntity(\n em: EntityManager,\n options: ReindexJobOptions,\n): Promise<ReindexJobResult> {\n const entityType = String(options?.entityType || '')\n if (!entityType) {\n return {\n processed: 0,\n total: 0,\n tenantScopes: [],\n scopes: [],\n }\n }\n const tenantIdInput = options?.tenantId\n const tenantId = tenantIdInput === 'undefined' ? undefined : tenantIdInput\n const organizationIdInput = options?.organizationId\n const organizationId = organizationIdInput === 'undefined' ? undefined : organizationIdInput\n const force = options?.force === true\n const batchSize = Number.isFinite(options?.batchSize) && options!.batchSize! > 0\n ? Math.max(1, Math.trunc(options!.batchSize!))\n : DEFAULT_BATCH_SIZE\n const emitVectorize = options?.emitVectorizeEvents === true\n const eventBus = options?.eventBus\n const vectorService = options?.vectorService ?? null\n const partitionCountRaw = Number.isFinite(options?.partitionCount)\n ? Math.max(1, Math.trunc(options!.partitionCount!))\n : 1\n const usingPartitions = partitionCountRaw > 1\n const partitionIndexRaw = Number.isFinite(options?.partitionIndex)\n ? Math.max(0, Math.trunc(options!.partitionIndex!))\n : 0\n const partitionIndex = usingPartitions\n ? Math.min(partitionIndexRaw, partitionCountRaw - 1)\n : null\n const resetCoverage = options?.resetCoverage ?? (!usingPartitions || partitionIndex === 0)\n\n const db = (em as any).getKysely() as Kysely<any>\n // Resolve the source table strictly via registered MikroORM metadata. We must\n // never fall back to a pluralized guess derived from the caller-supplied id\n // here: doing so would let a principal with `query_index.reindex` point the\n // reindexer at arbitrary tables (e.g. `auth_users`, `users`) and read their\n // rows into the index, bypassing tenant scoping and entity-level encryption.\n const table = resolveRegisteredEntityTableName(em, entityType)\n if (!table || entityType === 'query_index:search_token' || table === 'search_tokens') {\n if (!table) {\n logger.warn('Refusing to reindex unregistered entity type', {\n entityType,\n })\n }\n return {\n processed: 0,\n total: 0,\n tenantScopes: [],\n scopes: [],\n }\n }\n const columns = await getColumnSet(db, table)\n const hasOrgCol = columns.has('organization_id')\n const hasTenantCol = columns.has('tenant_id')\n const hasDeletedCol = columns.has('deleted_at')\n\n const jobScope: JobScope = {\n entityType,\n organizationId: organizationId ?? null,\n tenantId: tenantId ?? null,\n partitionIndex,\n partitionCount: usingPartitions ? partitionCountRaw : null,\n }\n\n if (!force) {\n const activeJob = await db\n .selectFrom('entity_index_jobs' as any)\n .select(['id' as any])\n .where('entity_type' as any, '=', entityType)\n .where('finished_at' as any, 'is', null as any)\n .where(sql<boolean>`organization_id is not distinct from ${null}`)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId ?? null}`)\n .where(sql<boolean>`partition_index is not distinct from ${partitionIndex}`)\n .where(sql<boolean>`partition_count is not distinct from ${usingPartitions ? partitionCountRaw : null}`)\n .executeTakeFirst()\n if (activeJob) {\n return {\n processed: 0,\n total: 0,\n tenantScopes: [],\n scopes: [],\n }\n }\n }\n\n if (resetCoverage) {\n await cleanupLegacyJobScopes(db, {\n entityType,\n organizationId: jobScope.organizationId ?? null,\n tenantId: jobScope.tenantId ?? null,\n activePartitionCount: jobScope.partitionCount ?? null,\n })\n }\n\n const scopeKey = (tenantValue: string | null, orgValue: string | null) => `${tenantValue ?? '__null__'}|${orgValue ?? '__null__'}`\n\n const applyBaseWhere = <QB extends { where: (...args: any[]) => QB }>(q: QB): QB => {\n let chain = q\n if (hasDeletedCol) chain = chain.where('b.deleted_at' as any, 'is', null as any)\n if (tenantId !== undefined && hasTenantCol) {\n chain = tenantId === null\n ? chain.where('b.tenant_id' as any, 'is', null as any)\n : chain.where('b.tenant_id' as any, '=', tenantId)\n }\n if (organizationId !== undefined && hasOrgCol) {\n chain = organizationId === null\n ? chain.where('b.organization_id' as any, 'is', null as any)\n : chain.where('b.organization_id' as any, '=', organizationId)\n }\n if (usingPartitions && partitionIndex !== null) {\n chain = chain.where(sql<boolean>`mod(abs(hashtext(b.id::text)), ${partitionCountRaw}) = ${partitionIndex}`)\n }\n return chain\n }\n\n type ScopeStats = { tenantId: string | null; organizationId: string | null; count: number }\n const baseCounts = new Map<string, ScopeStats>()\n const registerBaseCount = (tenantValue: string | null, orgValue: string | null, count: number) => {\n const key = scopeKey(tenantValue, orgValue)\n baseCounts.set(key, { tenantId: tenantValue, organizationId: orgValue, count })\n }\n\n const groupByTenant = hasTenantCol && tenantId === undefined\n const groupByOrg = hasOrgCol && organizationId === undefined\n\n if (groupByTenant || groupByOrg) {\n let groupQuery = applyBaseWhere(\n db.selectFrom(`${table} as b` as any).select(sql<number>`count(*)`.as('count')),\n )\n if (groupByTenant) {\n groupQuery = groupQuery.select('b.tenant_id as tenant_id' as any).groupBy('b.tenant_id' as any)\n }\n if (groupByOrg) {\n groupQuery = groupQuery.select('b.organization_id as organization_id' as any).groupBy('b.organization_id' as any)\n }\n const rows = await groupQuery.execute() as Array<Record<string, unknown>>\n for (const row of rows) {\n const bucketTenant = groupByTenant\n ? ((row as any)?.tenant_id ?? null)\n : (tenantId === undefined ? null : tenantId ?? null)\n const bucketOrg = groupByOrg\n ? ((row as any)?.organization_id ?? null)\n : (organizationId === undefined ? null : organizationId ?? null)\n registerBaseCount(bucketTenant, bucketOrg, toNumber((row as any)?.count))\n }\n } else {\n const row = await applyBaseWhere(\n db.selectFrom(`${table} as b` as any).select(sql<number>`count(*)`.as('count')),\n ).executeTakeFirst() as { count: unknown } | undefined\n const bucketTenant = tenantId === undefined ? null : tenantId ?? null\n const bucketOrg = organizationId === undefined ? null : organizationId ?? null\n registerBaseCount(bucketTenant, bucketOrg, toNumber(row?.count))\n }\n\n const total = Array.from(baseCounts.values()).reduce((acc, value) => acc + (Number.isFinite(value.count) ? value.count : 0), 0)\n await prepareJob(db, jobScope, 'reindexing', { totalCount: total })\n const jobRow = await db\n .selectFrom('entity_index_jobs' as any)\n .select(['started_at' as any])\n .where('entity_type' as any, '=', entityType)\n .where('organization_id' as any, 'is', null as any)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId ?? null}`)\n .where(sql<boolean>`partition_index is not distinct from ${partitionIndex}`)\n .where(sql<boolean>`partition_count is not distinct from ${usingPartitions ? partitionCountRaw : null}`)\n .orderBy('started_at' as any, 'desc')\n .executeTakeFirst() as { started_at: Date | string } | undefined\n const jobStartedAt = jobRow?.started_at ? new Date(jobRow.started_at) : new Date()\n const deriveOrg = deriveOrgFromId.has(entityType)\n ? (row: AnyRow) => String(row.id)\n : undefined\n\n const scopeOverrides: { tenantId?: string; orgId?: string } = {}\n if (tenantId !== undefined && tenantId !== null) {\n scopeOverrides.tenantId = String(tenantId)\n }\n if (organizationId !== undefined && organizationId !== null) {\n scopeOverrides.orgId = String(organizationId)\n }\n\n const scopeEntries = Array.from(baseCounts.values()).map((entry) => ({\n tenantId: entry.tenantId,\n organizationId: entry.organizationId,\n }))\n const tenantScopes = Array.from(\n new Set(scopeEntries.map((entry) => entry.tenantId ?? null)),\n )\n\n let processed = 0\n let lastId: string | null = null\n let jobFailed = false\n const writeTotals = createEmptyUpsertIndexBatchResult()\n\n options?.onProgress?.({ processed, total, chunkSize: 0 })\n\n if (resetCoverage) {\n if (force) {\n try {\n let purgeQuery = db\n .deleteFrom('entity_indexes' as any)\n .where('entity_type' as any, '=', entityType)\n if (tenantId !== undefined) {\n purgeQuery = purgeQuery.where(sql<boolean>`tenant_id is not distinct from ${tenantId ?? null}`)\n }\n if (organizationId !== undefined) {\n purgeQuery = purgeQuery.where(sql<boolean>`organization_id is not distinct from ${organizationId ?? null}`)\n }\n await purgeQuery.execute()\n } catch (error) {\n logger.warn('Failed to purge index rows before force reindex', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n error: error instanceof Error ? error.message : error,\n })\n }\n\n if (emitVectorize && eventBus) {\n if (tenantId !== undefined) {\n const payload: Record<string, unknown> = {\n entityType,\n tenantId: tenantId ?? null,\n }\n if (organizationId !== undefined) payload.organizationId = organizationId ?? null\n try {\n await eventBus.emitEvent('query_index.vectorize_purge', payload)\n } catch (err) {\n logger.warn('Failed to queue vector purge before force reindex', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n error: err instanceof Error ? err.message : err,\n })\n }\n } else {\n logger.warn('Skipping vector purge for force reindex without tenant scope', {\n entityType,\n })\n }\n }\n }\n\n const nowTs = Date.now()\n for (const scope of baseCounts.values()) {\n const key = `${entityType}|${scopeKey(scope.tenantId, scope.organizationId)}`\n const last = lastCoverageReset.get(key) ?? 0\n if (force || nowTs - last >= COVERAGE_REFRESH_THROTTLE_MS) {\n await writeCoverageCounts(em, {\n entityType,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n withDeleted: false,\n }, {\n baseCount: scope.count,\n indexedCount: 0,\n vectorCount: emitVectorize ? 0 : undefined,\n })\n lastCoverageReset.set(key, nowTs)\n }\n }\n }\n\n try {\n while (true) {\n let query = applyBaseWhere(\n db\n .selectFrom(`${table} as b` as any)\n .selectAll('b' as any)\n .orderBy('b.id' as any, 'asc')\n .limit(batchSize),\n )\n if (lastId !== null) {\n query = query.where('b.id' as any, '>', lastId)\n }\n const rows = await query.execute() as AnyRow[]\n if (!rows.length) break\n\n const encryption = resolveTenantEncryptionService(em as any)\n const dekKeyCache = new Map<string | null, string | null>()\n const encryptDoc = async (\n targetEntity: string,\n doc: Record<string, unknown>,\n scope: { organizationId: string | null; tenantId: string | null },\n ) => {\n return await encryptIndexDocForStorage(\n targetEntity,\n doc,\n { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },\n encryption,\n )\n }\n const decryptDoc = async (\n targetEntity: string,\n doc: Record<string, unknown>,\n scope: { organizationId: string | null; tenantId: string | null },\n ) => {\n const result = await decryptIndexDocForSearch(\n targetEntity,\n doc,\n { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },\n encryption,\n dekKeyCache,\n )\n if (isSearchDebugEnabled()) {\n logger.debug('Reindex decrypt', buildReindexDecryptDebugPayload(targetEntity, result as Record<string, unknown>, scope))\n }\n return result\n }\n\n const batchResult = await upsertIndexBatch(db, entityType, rows, scopeOverrides, { deriveOrganizationId: deriveOrg, encryptDoc, decryptDoc })\n mergeUpsertIndexBatchResults(writeTotals, batchResult)\n\n // A whole batch failing is infrastructural (pool exhausted, disk full, KMS down),\n // not a poison record. Abort now instead of grinding through the rest of the table.\n if (batchResult.written === 0 && batchResult.attempted > 0) {\n throw new QueryIndexBatchWriteError(entityType, writeTotals)\n }\n\n const failedInBatch = new Set(batchResult.failedRecordIds)\n const writtenRows = failedInBatch.size ? rows.filter((row) => !failedInBatch.has(String(row.id))) : rows\n\n const coverageDeltas = new Map<string, { tenantId: string | null; organizationId: string | null; delta: number }>()\n for (const row of writtenRows) {\n const scopeTenant = tenantId !== undefined\n ? tenantId ?? null\n : (hasTenantCol ? ((row as AnyRow).tenant_id ?? null) : null)\n const scopeOrg = organizationId !== undefined\n ? organizationId ?? null\n : (hasOrgCol ? ((row as AnyRow).organization_id ?? null) : (deriveOrg ? deriveOrg(row) ?? null : null))\n const key = scopeKey(scopeTenant ?? null, scopeOrg ?? null)\n const existingDelta = coverageDeltas.get(key)\n if (existingDelta) existingDelta.delta += 1\n else coverageDeltas.set(key, {\n tenantId: scopeTenant ?? null,\n organizationId: scopeOrg ?? null,\n delta: 1,\n })\n }\n if (coverageDeltas.size > 0) {\n await applyCoverageAdjustments(\n em,\n Array.from(coverageDeltas.values()).map((entry) => ({\n entityType,\n tenantId: entry.tenantId,\n organizationId: entry.organizationId,\n withDeleted: false,\n deltaBase: 0,\n deltaIndex: entry.delta,\n })),\n )\n }\n\n if (emitVectorize && eventBus) {\n await Promise.all(\n writtenRows.map((row) => {\n const scopeOrg = organizationId !== undefined\n ? organizationId ?? null\n : hasOrgCol\n ? ((row as AnyRow).organization_id ?? null)\n : (deriveOrg ? deriveOrg(row) ?? null : null)\n const scopeTenant = tenantId !== undefined\n ? tenantId ?? null\n : (hasTenantCol ? ((row as AnyRow).tenant_id ?? null) : null)\n return eventBus\n .emitEvent('query_index.vectorize_one', {\n entityType,\n recordId: String(row.id),\n organizationId: scopeOrg,\n tenantId: scopeTenant,\n })\n .catch(() => undefined)\n }),\n )\n }\n\n processed += batchResult.written\n lastId = String(rows[rows.length - 1]!.id)\n options?.onProgress?.({ processed, total, chunkSize: batchResult.written })\n await updateJobProgress(db, jobScope, batchResult.written)\n }\n\n // Records this run failed to write still look untouched to the purge predicate, so\n // without the exclusion the purge would delete the index rows it just failed to\n // rebuild \u2014 turning a stale entry into a missing one.\n const purgeExclusions = writeTotals.failedRecordIds\n if (purgeExclusions.length > MAX_PURGE_EXCLUSIONS) {\n logger.warn('Skipping orphan purge after widespread write failures', {\n entityType,\n failedRecords: purgeExclusions.length,\n })\n } else {\n await purgeOrphans(db, {\n entityType,\n tenantId,\n organizationId,\n partitionIndex: usingPartitions ? partitionIndex : null,\n partitionCount: usingPartitions ? partitionCountRaw : null,\n startedAt: jobStartedAt,\n excludeRecordIds: purgeExclusions,\n })\n }\n\n if (force && vectorService && (!usingPartitions || partitionIndex === null)) {\n try {\n await vectorService.removeOrphans({\n entityId: entityType,\n tenantId,\n organizationId,\n olderThan: jobStartedAt,\n })\n } catch (error) {\n logger.warn('Failed to prune vector orphans after reindex', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n error: error instanceof Error ? error.message : error,\n })\n }\n }\n\n for (const scope of scopeEntries) {\n await refreshCoverageSnapshot(\n em,\n {\n entityType,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n withDeleted: false,\n },\n )\n }\n\n // Deliberately after the coverage refresh: the authoritative recount is what keeps\n // indexed_count truthful, and it is most worth having when a run has just failed.\n // Throwing here fails the queue job so the loss is visible instead of silent.\n if (writeTotals.searchTokenFailures > 0) {\n logger.warn('Search token writes failed during reindex', {\n entityType,\n batches: writeTotals.searchTokenFailures,\n })\n }\n assertIndexBatchWritesLanded(entityType, writeTotals)\n } catch (error) {\n jobFailed = true\n throw error\n } finally {\n // Still finalized on failure: the scope stays wedged behind the active-job guard\n // while finished_at is null. The status carries the outcome instead.\n await finalizeJob(db, jobScope, jobFailed ? { status: 'failed' } : {})\n }\n\n return {\n processed,\n total,\n scopes: scopeEntries,\n tenantScopes,\n }\n}\n"],
5
- "mappings": "AACA,SAAsB,WAAW;AACjC,SAAS,wCAAwC;AACjD,SAAS,sCAAsC;AAC/C,SAAS,0BAA0B,iCAAiC;AACpE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,yBAAyB,qBAAqB,gCAAgC;AACvF,SAAS,YAAY,mBAAmB,mBAAkC;AAC1E,SAAS,oBAAoB;AAE7B,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,aAAa,EAAE,MAAM,EAAE,WAAW,YAAY,CAAC;AA0BpE,MAAM,6BAA6B;AAC1C,MAAM,qBAAqB;AAM3B,MAAM,uBAAuB;AAC7B,MAAM,kBAAkB,oBAAI,IAAY,CAAC,wBAAwB,CAAC;AAClE,MAAM,+BAA+B,IAAI,KAAK;AAC9C,MAAM,oBAAoB,oBAAI,IAAoB;AAElD,MAAM,6BAA6B,CAAC,gBAAgB,cAAc,aAAa,cAAc,cAAc,iBAAiB,eAAe;AASpI,SAAS,gCACd,YACA,KACA,OAC4B;AAC5B,QAAM,cAAwB,CAAC;AAC/B,aAAW,OAAO,4BAA4B;AAC5C,UAAM,QAAQ,IAAI,GAAG;AACrB,QAAI,OAAO,OAAO,SAAS,QAAQ,UAAU,GAAI,aAAY,KAAK,GAAG;AAAA,EACvE;AACA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,MAAM,YAAY;AAAA,IAC5B,gBAAgB,MAAM,kBAAkB;AAAA,IACxC,MAAM;AAAA,EACR;AACF;AAEA,eAAe,uBACb,IACA,SAMe;AACf,QAAM,GACH,WAAW,mBAA0B,EACrC,MAAM,eAAsB,KAAK,QAAQ,UAAU,EACnD,MAAM,2CAAoD,QAAQ,cAAc,EAAE,EAClF,MAAM,qCAA8C,QAAQ,QAAQ,EAAE,EACtE,MAAM,uCAAgD,QAAQ,oBAAoB,EAAE,EACpF,QAAQ;AACb;AAEA,SAAS,SAAS,OAAwB;AACxC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,KAAK;AAC3B,WAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,eAAe,aAAa,IAAiB,WAAyC;AACpF,MAAI;AACF,UAAM,OAAO,MAAM,GAChB,WAAW,4BAAmC,EAC9C,OAAO,CAAC,aAAoB,CAAC,EAC7B,MAAM,oCAA6C,EACnD,MAAM,cAAqB,KAAK,SAAS,EACzC,QAAQ;AACX,WAAO,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,OAAO,IAAI,WAAW,EAAE,YAAY,CAAC,CAAC;AAAA,EACzE,QAAQ;AACN,WAAO,oBAAI,IAAY;AAAA,EACzB;AACF;AAEA,eAAsB,cACpB,IACA,SAC2B;AAC3B,QAAM,aAAa,OAAO,SAAS,cAAc,EAAE;AACnD,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,MACL,WAAW;AAAA,MACX,OAAO;AAAA,MACP,cAAc,CAAC;AAAA,MACf,QAAQ,CAAC;AAAA,IACX;AAAA,EACF;AACA,QAAM,gBAAgB,SAAS;AAC/B,QAAM,WAAW,kBAAkB,cAAc,SAAY;AAC7D,QAAM,sBAAsB,SAAS;AACrC,QAAM,iBAAiB,wBAAwB,cAAc,SAAY;AACzE,QAAM,QAAQ,SAAS,UAAU;AACjC,QAAM,YAAY,OAAO,SAAS,SAAS,SAAS,KAAK,QAAS,YAAa,IAC3E,KAAK,IAAI,GAAG,KAAK,MAAM,QAAS,SAAU,CAAC,IAC3C;AACJ,QAAM,gBAAgB,SAAS,wBAAwB;AACvD,QAAM,WAAW,SAAS;AAC1B,QAAM,gBAAgB,SAAS,iBAAiB;AAChD,QAAM,oBAAoB,OAAO,SAAS,SAAS,cAAc,IAC7D,KAAK,IAAI,GAAG,KAAK,MAAM,QAAS,cAAe,CAAC,IAChD;AACJ,QAAM,kBAAkB,oBAAoB;AAC5C,QAAM,oBAAoB,OAAO,SAAS,SAAS,cAAc,IAC7D,KAAK,IAAI,GAAG,KAAK,MAAM,QAAS,cAAe,CAAC,IAChD;AACJ,QAAM,iBAAiB,kBACnB,KAAK,IAAI,mBAAmB,oBAAoB,CAAC,IACjD;AACJ,QAAM,gBAAgB,SAAS,kBAAkB,CAAC,mBAAmB,mBAAmB;AAExF,QAAM,KAAM,GAAW,UAAU;AAMjC,QAAM,QAAQ,iCAAiC,IAAI,UAAU;AAC7D,MAAI,CAAC,SAAS,eAAe,8BAA8B,UAAU,iBAAiB;AACpF,QAAI,CAAC,OAAO;AACV,aAAO,KAAK,gDAAgD;AAAA,QAC1D;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,MACL,WAAW;AAAA,MACX,OAAO;AAAA,MACP,cAAc,CAAC;AAAA,MACf,QAAQ,CAAC;AAAA,IACX;AAAA,EACF;AACA,QAAM,UAAU,MAAM,aAAa,IAAI,KAAK;AAC5C,QAAM,YAAY,QAAQ,IAAI,iBAAiB;AAC/C,QAAM,eAAe,QAAQ,IAAI,WAAW;AAC5C,QAAM,gBAAgB,QAAQ,IAAI,YAAY;AAE9C,QAAM,WAAqB;AAAA,IACzB;AAAA,IACA,gBAAgB,kBAAkB;AAAA,IAClC,UAAU,YAAY;AAAA,IACtB;AAAA,IACA,gBAAgB,kBAAkB,oBAAoB;AAAA,EACxD;AAEA,MAAI,CAAC,OAAO;AACV,UAAM,YAAY,MAAM,GACrB,WAAW,mBAA0B,EACrC,OAAO,CAAC,IAAW,CAAC,EACpB,MAAM,eAAsB,KAAK,UAAU,EAC3C,MAAM,eAAsB,MAAM,IAAW,EAC7C,MAAM,2CAAoD,IAAI,EAAE,EAChE,MAAM,qCAA8C,YAAY,IAAI,EAAE,EACtE,MAAM,2CAAoD,cAAc,EAAE,EAC1E,MAAM,2CAAoD,kBAAkB,oBAAoB,IAAI,EAAE,EACtG,iBAAiB;AACpB,QAAI,WAAW;AACb,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO;AAAA,QACP,cAAc,CAAC;AAAA,QACf,QAAQ,CAAC;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe;AACjB,UAAM,uBAAuB,IAAI;AAAA,MAC/B;AAAA,MACA,gBAAgB,SAAS,kBAAkB;AAAA,MAC3C,UAAU,SAAS,YAAY;AAAA,MAC/B,sBAAsB,SAAS,kBAAkB;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,QAAM,WAAW,CAAC,aAA4B,aAA4B,GAAG,eAAe,UAAU,IAAI,YAAY,UAAU;AAEhI,QAAM,iBAAiB,CAA+C,MAAc;AAClF,QAAI,QAAQ;AACZ,QAAI,cAAe,SAAQ,MAAM,MAAM,gBAAuB,MAAM,IAAW;AAC/E,QAAI,aAAa,UAAa,cAAc;AAC1C,cAAQ,aAAa,OACjB,MAAM,MAAM,eAAsB,MAAM,IAAW,IACnD,MAAM,MAAM,eAAsB,KAAK,QAAQ;AAAA,IACrD;AACA,QAAI,mBAAmB,UAAa,WAAW;AAC7C,cAAQ,mBAAmB,OACvB,MAAM,MAAM,qBAA4B,MAAM,IAAW,IACzD,MAAM,MAAM,qBAA4B,KAAK,cAAc;AAAA,IACjE;AACA,QAAI,mBAAmB,mBAAmB,MAAM;AAC9C,cAAQ,MAAM,MAAM,qCAA8C,iBAAiB,OAAO,cAAc,EAAE;AAAA,IAC5G;AACA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,oBAAI,IAAwB;AAC/C,QAAM,oBAAoB,CAAC,aAA4B,UAAyB,UAAkB;AAChG,UAAM,MAAM,SAAS,aAAa,QAAQ;AAC1C,eAAW,IAAI,KAAK,EAAE,UAAU,aAAa,gBAAgB,UAAU,MAAM,CAAC;AAAA,EAChF;AAEA,QAAM,gBAAgB,gBAAgB,aAAa;AACnD,QAAM,aAAa,aAAa,mBAAmB;AAEnD,MAAI,iBAAiB,YAAY;AAC/B,QAAI,aAAa;AAAA,MACf,GAAG,WAAW,GAAG,KAAK,OAAc,EAAE,OAAO,cAAsB,GAAG,OAAO,CAAC;AAAA,IAChF;AACA,QAAI,eAAe;AACjB,mBAAa,WAAW,OAAO,0BAAiC,EAAE,QAAQ,aAAoB;AAAA,IAChG;AACA,QAAI,YAAY;AACd,mBAAa,WAAW,OAAO,sCAA6C,EAAE,QAAQ,mBAA0B;AAAA,IAClH;AACA,UAAM,OAAO,MAAM,WAAW,QAAQ;AACtC,eAAW,OAAO,MAAM;AACtB,YAAM,eAAe,gBACf,KAAa,aAAa,OAC3B,aAAa,SAAY,OAAO,YAAY;AACjD,YAAM,YAAY,aACZ,KAAa,mBAAmB,OACjC,mBAAmB,SAAY,OAAO,kBAAkB;AAC7D,wBAAkB,cAAc,WAAW,SAAU,KAAa,KAAK,CAAC;AAAA,IAC1E;AAAA,EACF,OAAO;AACL,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,WAAW,GAAG,KAAK,OAAc,EAAE,OAAO,cAAsB,GAAG,OAAO,CAAC;AAAA,IAChF,EAAE,iBAAiB;AACnB,UAAM,eAAe,aAAa,SAAY,OAAO,YAAY;AACjE,UAAM,YAAY,mBAAmB,SAAY,OAAO,kBAAkB;AAC1E,sBAAkB,cAAc,WAAW,SAAS,KAAK,KAAK,CAAC;AAAA,EACjE;AAEA,QAAM,QAAQ,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,OAAO,CAAC,KAAK,UAAU,OAAO,OAAO,SAAS,MAAM,KAAK,IAAI,MAAM,QAAQ,IAAI,CAAC;AAC9H,QAAM,WAAW,IAAI,UAAU,cAAc,EAAE,YAAY,MAAM,CAAC;AAClE,QAAM,SAAS,MAAM,GAClB,WAAW,mBAA0B,EACrC,OAAO,CAAC,YAAmB,CAAC,EAC5B,MAAM,eAAsB,KAAK,UAAU,EAC3C,MAAM,mBAA0B,MAAM,IAAW,EACjD,MAAM,qCAA8C,YAAY,IAAI,EAAE,EACtE,MAAM,2CAAoD,cAAc,EAAE,EAC1E,MAAM,2CAAoD,kBAAkB,oBAAoB,IAAI,EAAE,EACtG,QAAQ,cAAqB,MAAM,EACnC,iBAAiB;AACpB,QAAM,eAAe,QAAQ,aAAa,IAAI,KAAK,OAAO,UAAU,IAAI,oBAAI,KAAK;AACjF,QAAM,YAAY,gBAAgB,IAAI,UAAU,IAC5C,CAAC,QAAgB,OAAO,IAAI,EAAE,IAC9B;AAEJ,QAAM,iBAAwD,CAAC;AAC/D,MAAI,aAAa,UAAa,aAAa,MAAM;AAC/C,mBAAe,WAAW,OAAO,QAAQ;AAAA,EAC3C;AACA,MAAI,mBAAmB,UAAa,mBAAmB,MAAM;AAC3D,mBAAe,QAAQ,OAAO,cAAc;AAAA,EAC9C;AAEA,QAAM,eAAe,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW;AAAA,IACnE,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,EACxB,EAAE;AACF,QAAM,eAAe,MAAM;AAAA,IACzB,IAAI,IAAI,aAAa,IAAI,CAAC,UAAU,MAAM,YAAY,IAAI,CAAC;AAAA,EAC7D;AAEA,MAAI,YAAY;AAChB,MAAI,SAAwB;AAC5B,MAAI,YAAY;AAChB,QAAM,cAAc,kCAAkC;AAEtD,WAAS,aAAa,EAAE,WAAW,OAAO,WAAW,EAAE,CAAC;AAExD,MAAI,eAAe;AACjB,QAAI,OAAO;AACT,UAAI;AACF,YAAI,aAAa,GACd,WAAW,gBAAuB,EAClC,MAAM,eAAsB,KAAK,UAAU;AAC9C,YAAI,aAAa,QAAW;AAC1B,uBAAa,WAAW,MAAM,qCAA8C,YAAY,IAAI,EAAE;AAAA,QAChG;AACA,YAAI,mBAAmB,QAAW;AAChC,uBAAa,WAAW,MAAM,2CAAoD,kBAAkB,IAAI,EAAE;AAAA,QAC5G;AACA,cAAM,WAAW,QAAQ;AAAA,MAC3B,SAAS,OAAO;AACd,eAAO,KAAK,mDAAmD;AAAA,UAC7D;AAAA,UACA,UAAU,YAAY;AAAA,UACtB,gBAAgB,kBAAkB;AAAA,UAClC,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAClD,CAAC;AAAA,MACH;AAEA,UAAI,iBAAiB,UAAU;AAC7B,YAAI,aAAa,QAAW;AAC1B,gBAAM,UAAmC;AAAA,YACvC;AAAA,YACA,UAAU,YAAY;AAAA,UACxB;AACA,cAAI,mBAAmB,OAAW,SAAQ,iBAAiB,kBAAkB;AAC7E,cAAI;AACF,kBAAM,SAAS,UAAU,+BAA+B,OAAO;AAAA,UACjE,SAAS,KAAK;AACZ,mBAAO,KAAK,qDAAqD;AAAA,cAC/D;AAAA,cACA,UAAU,YAAY;AAAA,cACtB,gBAAgB,kBAAkB;AAAA,cAClC,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,YAC9C,CAAC;AAAA,UACH;AAAA,QACF,OAAO;AACL,iBAAO,KAAK,gEAAgE;AAAA,YAC1E;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,IAAI;AACvB,eAAW,SAAS,WAAW,OAAO,GAAG;AACvC,YAAM,MAAM,GAAG,UAAU,IAAI,SAAS,MAAM,UAAU,MAAM,cAAc,CAAC;AAC3E,YAAM,OAAO,kBAAkB,IAAI,GAAG,KAAK;AAC3C,UAAI,SAAS,QAAQ,QAAQ,8BAA8B;AACzD,cAAM,oBAAoB,IAAI;AAAA,UAC5B;AAAA,UACA,UAAU,MAAM;AAAA,UAChB,gBAAgB,MAAM;AAAA,UACtB,aAAa;AAAA,QACf,GAAG;AAAA,UACD,WAAW,MAAM;AAAA,UACjB,cAAc;AAAA,UACd,aAAa,gBAAgB,IAAI;AAAA,QACnC,CAAC;AACD,0BAAkB,IAAI,KAAK,KAAK;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,WAAO,MAAM;AACX,UAAI,QAAQ;AAAA,QACV,GACG,WAAW,GAAG,KAAK,OAAc,EACjC,UAAU,GAAU,EACpB,QAAQ,QAAe,KAAK,EAC5B,MAAM,SAAS;AAAA,MACpB;AACA,UAAI,WAAW,MAAM;AACnB,gBAAQ,MAAM,MAAM,QAAe,KAAK,MAAM;AAAA,MAChD;AACA,YAAM,OAAO,MAAM,MAAM,QAAQ;AACjC,UAAI,CAAC,KAAK,OAAQ;AAElB,YAAM,aAAa,+BAA+B,EAAS;AAC3D,YAAM,cAAc,oBAAI,IAAkC;AAC1D,YAAM,aAAa,OACjB,cACA,KACA,UACG;AACH,eAAO,MAAM;AAAA,UACX;AAAA,UACA;AAAA,UACA,EAAE,UAAU,MAAM,YAAY,MAAM,gBAAgB,MAAM,kBAAkB,KAAK;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AACA,YAAM,aAAa,OACjB,cACA,KACA,UACG;AACH,cAAM,SAAS,MAAM;AAAA,UACnB;AAAA,UACA;AAAA,UACA,EAAE,UAAU,MAAM,YAAY,MAAM,gBAAgB,MAAM,kBAAkB,KAAK;AAAA,UACjF;AAAA,UACA;AAAA,QACF;AACA,YAAI,qBAAqB,GAAG;AAC1B,iBAAO,MAAM,mBAAmB,gCAAgC,cAAc,QAAmC,KAAK,CAAC;AAAA,QACzH;AACA,eAAO;AAAA,MACT;AAEA,YAAM,cAAc,MAAM,iBAAiB,IAAI,YAAY,MAAM,gBAAgB,EAAE,sBAAsB,WAAW,YAAY,WAAW,CAAC;AAC5I,mCAA6B,aAAa,WAAW;AAIrD,UAAI,YAAY,YAAY,KAAK,YAAY,YAAY,GAAG;AAC1D,cAAM,IAAI,0BAA0B,YAAY,WAAW;AAAA,MAC7D;AAEA,YAAM,gBAAgB,IAAI,IAAI,YAAY,eAAe;AACzD,YAAM,cAAc,cAAc,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI;AAEpG,YAAM,iBAAiB,oBAAI,IAAuF;AAClH,iBAAW,OAAO,aAAa;AAC7B,cAAM,cAAc,aAAa,SAC7B,YAAY,OACX,eAAiB,IAAe,aAAa,OAAQ;AAC1D,cAAM,WAAW,mBAAmB,SAChC,kBAAkB,OACjB,YAAc,IAAe,mBAAmB,OAAS,YAAY,UAAU,GAAG,KAAK,OAAO;AACnG,cAAM,MAAM,SAAS,eAAe,MAAM,YAAY,IAAI;AAC1D,cAAM,gBAAgB,eAAe,IAAI,GAAG;AAC5C,YAAI,cAAe,eAAc,SAAS;AAAA,YACrC,gBAAe,IAAI,KAAK;AAAA,UAC3B,UAAU,eAAe;AAAA,UACzB,gBAAgB,YAAY;AAAA,UAC5B,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AACA,UAAI,eAAe,OAAO,GAAG;AAC3B,cAAM;AAAA,UACJ;AAAA,UACA,MAAM,KAAK,eAAe,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW;AAAA,YAClD;AAAA,YACA,UAAU,MAAM;AAAA,YAChB,gBAAgB,MAAM;AAAA,YACtB,aAAa;AAAA,YACb,WAAW;AAAA,YACX,YAAY,MAAM;AAAA,UACpB,EAAE;AAAA,QACJ;AAAA,MACF;AAEA,UAAI,iBAAiB,UAAU;AAC7B,cAAM,QAAQ;AAAA,UACZ,YAAY,IAAI,CAAC,QAAQ;AACvB,kBAAM,WAAW,mBAAmB,SAChC,kBAAkB,OAClB,YACI,IAAe,mBAAmB,OACnC,YAAY,UAAU,GAAG,KAAK,OAAO;AAC5C,kBAAM,cAAc,aAAa,SAC7B,YAAY,OACX,eAAiB,IAAe,aAAa,OAAQ;AAC1D,mBAAO,SACJ,UAAU,6BAA6B;AAAA,cACtC;AAAA,cACA,UAAU,OAAO,IAAI,EAAE;AAAA,cACvB,gBAAgB;AAAA,cAChB,UAAU;AAAA,YACZ,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,UAC1B,CAAC;AAAA,QACH;AAAA,MACF;AAEA,mBAAa,YAAY;AACzB,eAAS,OAAO,KAAK,KAAK,SAAS,CAAC,EAAG,EAAE;AACzC,eAAS,aAAa,EAAE,WAAW,OAAO,WAAW,YAAY,QAAQ,CAAC;AAC1E,YAAM,kBAAkB,IAAI,UAAU,YAAY,OAAO;AAAA,IAC3D;AAKA,UAAM,kBAAkB,YAAY;AACpC,QAAI,gBAAgB,SAAS,sBAAsB;AACjD,aAAO,KAAK,yDAAyD;AAAA,QACnE;AAAA,QACA,eAAe,gBAAgB;AAAA,MACjC,CAAC;AAAA,IACH,OAAO;AACL,YAAM,aAAa,IAAI;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB,kBAAkB,iBAAiB;AAAA,QACnD,gBAAgB,kBAAkB,oBAAoB;AAAA,QACtD,WAAW;AAAA,QACX,kBAAkB;AAAA,MACpB,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,kBAAkB,CAAC,mBAAmB,mBAAmB,OAAO;AAC3E,UAAI;AACF,cAAM,cAAc,cAAc;AAAA,UAChC,UAAU;AAAA,UACV;AAAA,UACA;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AAAA,MACH,SAAS,OAAO;AACd,eAAO,KAAK,gDAAgD;AAAA,UAC1D;AAAA,UACA,UAAU,YAAY;AAAA,UACtB,gBAAgB,kBAAkB;AAAA,UAClC,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAClD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,eAAW,SAAS,cAAc;AAChC,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE;AAAA,UACA,UAAU,MAAM;AAAA,UAChB,gBAAgB,MAAM;AAAA,UACtB,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAKA,QAAI,YAAY,sBAAsB,GAAG;AACvC,aAAO,KAAK,6CAA6C;AAAA,QACvD;AAAA,QACA,SAAS,YAAY;AAAA,MACvB,CAAC;AAAA,IACH;AACA,iCAA6B,YAAY,WAAW;AAAA,EACtD,SAAS,OAAO;AACd,gBAAY;AACZ,UAAM;AAAA,EACR,UAAE;AAGA,UAAM,YAAY,IAAI,UAAU,YAAY,EAAE,QAAQ,SAAS,IAAI,CAAC,CAAC;AAAA,EACvE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { resolveRegisteredEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport { decryptIndexDocForSearch, encryptIndexDocForStorage } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport {\n upsertIndexBatch,\n assertIndexBatchWritesLanded,\n createEmptyUpsertIndexBatchResult,\n mergeUpsertIndexBatchResults,\n QueryIndexBatchWriteError,\n type AnyRow,\n} from './batch'\nimport { refreshCoverageSnapshot, writeCoverageCounts, applyCoverageAdjustments } from './coverage'\nimport { prepareJob, updateJobProgress, finalizeJob, type JobScope } from './jobs'\nimport { purgeOrphans } from './stale'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport { isSearchDebugEnabled } from './search-tokens'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'reindexer' })\n\nexport type ReindexJobOptions = {\n entityType: string\n tenantId?: string | null\n organizationId?: string | null\n force?: boolean\n batchSize?: number\n emitVectorizeEvents?: boolean\n eventBus?: {\n emitEvent(event: string, payload: any, options?: any): Promise<void>\n }\n partitionCount?: number\n partitionIndex?: number\n resetCoverage?: boolean\n onProgress?: (info: { processed: number; total: number; chunkSize: number }) => void\n vectorService?: VectorIndexService | null\n}\n\nexport type ReindexJobResult = {\n processed: number\n total: number\n tenantScopes: Array<string | null>\n scopes: Array<{ tenantId: string | null; organizationId: string | null }>\n}\n\nexport const DEFAULT_REINDEX_PARTITIONS = 5\nconst DEFAULT_BATCH_SIZE = 500\n/**\n * Above this many failed records the purge exclusion list stops being a sane query, so\n * the purge is skipped entirely instead. Failing closed keeps stale rows; the alternative\n * deletes index entries the run failed to rebuild.\n */\nconst MAX_PURGE_EXCLUSIONS = 1000\nconst deriveOrgFromId = new Set<string>(['directory:organization'])\nconst COVERAGE_REFRESH_THROTTLE_MS = 5 * 60 * 1000\nconst lastCoverageReset = new Map<string, number>()\n\nconst REINDEX_DECRYPT_DEBUG_KEYS = ['display_name', 'first_name', 'last_name', 'brand_name', 'legal_name', 'primary_email', 'primary_phone'] as const\n\nexport type ReindexDecryptDebugPayload = {\n entityType: string\n tenantId: string | null\n organizationId: string | null\n keys: string[]\n}\n\nexport function buildReindexDecryptDebugPayload(\n entityType: string,\n doc: Record<string, unknown>,\n scope: { organizationId: string | null; tenantId: string | null },\n): ReindexDecryptDebugPayload {\n const presentKeys: string[] = []\n for (const key of REINDEX_DECRYPT_DEBUG_KEYS) {\n const value = doc[key]\n if (key in doc && value != null && value !== '') presentKeys.push(key)\n }\n return {\n entityType,\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n keys: presentKeys,\n }\n}\n\nasync function cleanupLegacyJobScopes(\n db: Kysely<any>,\n options: {\n entityType: string\n organizationId: string | null\n tenantId: string | null\n activePartitionCount: number | null\n },\n): Promise<void> {\n await db\n .deleteFrom('entity_index_jobs' as any)\n .where('entity_type' as any, '=', options.entityType)\n .where(sql<boolean>`organization_id is not distinct from ${options.organizationId}`)\n .where(sql<boolean>`tenant_id is not distinct from ${options.tenantId}`)\n .where(sql<boolean>`partition_count is distinct from ${options.activePartitionCount}`)\n .execute()\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === 'number') return Number.isFinite(value) ? value : 0\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n}\n\nasync function getColumnSet(db: Kysely<any>, tableName: string): Promise<Set<string>> {\n try {\n const rows = await db\n .selectFrom('information_schema.columns' as any)\n .select(['column_name' as any])\n .where(sql<boolean>`table_schema = current_schema()`)\n .where('table_name' as any, '=', tableName)\n .execute() as Array<{ column_name: string }>\n return new Set(rows.map((row) => String(row.column_name).toLowerCase()))\n } catch {\n return new Set<string>()\n }\n}\n\nexport async function reindexEntity(\n em: EntityManager,\n options: ReindexJobOptions,\n): Promise<ReindexJobResult> {\n const entityType = String(options?.entityType || '')\n if (!entityType) {\n return {\n processed: 0,\n total: 0,\n tenantScopes: [],\n scopes: [],\n }\n }\n const tenantIdInput = options?.tenantId\n const tenantId = tenantIdInput === 'undefined' ? undefined : tenantIdInput\n const organizationIdInput = options?.organizationId\n const organizationId = organizationIdInput === 'undefined' ? undefined : organizationIdInput\n const force = options?.force === true\n const batchSize = Number.isFinite(options?.batchSize) && options!.batchSize! > 0\n ? Math.max(1, Math.trunc(options!.batchSize!))\n : DEFAULT_BATCH_SIZE\n const emitVectorize = options?.emitVectorizeEvents === true\n const eventBus = options?.eventBus\n const vectorService = options?.vectorService ?? null\n const partitionCountRaw = Number.isFinite(options?.partitionCount)\n ? Math.max(1, Math.trunc(options!.partitionCount!))\n : 1\n const usingPartitions = partitionCountRaw > 1\n const partitionIndexRaw = Number.isFinite(options?.partitionIndex)\n ? Math.max(0, Math.trunc(options!.partitionIndex!))\n : 0\n const partitionIndex = usingPartitions\n ? Math.min(partitionIndexRaw, partitionCountRaw - 1)\n : null\n const resetCoverage = options?.resetCoverage ?? (!usingPartitions || partitionIndex === 0)\n\n const db = (em as any).getKysely() as Kysely<any>\n // Resolve the source table strictly via registered MikroORM metadata. We must\n // never fall back to a pluralized guess derived from the caller-supplied id\n // here: doing so would let a principal with `query_index.reindex` point the\n // reindexer at arbitrary tables (e.g. `auth_users`, `users`) and read their\n // rows into the index, bypassing tenant scoping and entity-level encryption.\n const table = resolveRegisteredEntityTableName(em, entityType)\n if (!table || entityType === 'query_index:search_token' || table === 'search_tokens') {\n if (!table) {\n logger.warn('Refusing to reindex unregistered entity type', {\n entityType,\n })\n }\n return {\n processed: 0,\n total: 0,\n tenantScopes: [],\n scopes: [],\n }\n }\n const columns = await getColumnSet(db, table)\n const hasOrgCol = columns.has('organization_id')\n const hasTenantCol = columns.has('tenant_id')\n const hasDeletedCol = columns.has('deleted_at')\n\n const jobScope: JobScope = {\n entityType,\n organizationId: organizationId ?? null,\n tenantId: tenantId ?? null,\n partitionIndex,\n partitionCount: usingPartitions ? partitionCountRaw : null,\n }\n\n if (!force) {\n const activeJob = await db\n .selectFrom('entity_index_jobs' as any)\n .select(['id' as any])\n .where('entity_type' as any, '=', entityType)\n .where('finished_at' as any, 'is', null as any)\n .where(sql<boolean>`organization_id is not distinct from ${null}`)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId ?? null}`)\n .where(sql<boolean>`partition_index is not distinct from ${partitionIndex}`)\n .where(sql<boolean>`partition_count is not distinct from ${usingPartitions ? partitionCountRaw : null}`)\n .executeTakeFirst()\n if (activeJob) {\n return {\n processed: 0,\n total: 0,\n tenantScopes: [],\n scopes: [],\n }\n }\n }\n\n if (resetCoverage) {\n await cleanupLegacyJobScopes(db, {\n entityType,\n organizationId: jobScope.organizationId ?? null,\n tenantId: jobScope.tenantId ?? null,\n activePartitionCount: jobScope.partitionCount ?? null,\n })\n }\n\n const scopeKey = (tenantValue: string | null, orgValue: string | null) => `${tenantValue ?? '__null__'}|${orgValue ?? '__null__'}`\n\n const applyBaseWhere = <QB extends { where: (...args: any[]) => QB }>(q: QB): QB => {\n let chain = q\n if (hasDeletedCol) chain = chain.where('b.deleted_at' as any, 'is', null as any)\n if (tenantId !== undefined && hasTenantCol) {\n chain = tenantId === null\n ? chain.where('b.tenant_id' as any, 'is', null as any)\n : chain.where('b.tenant_id' as any, '=', tenantId)\n }\n if (organizationId !== undefined && hasOrgCol) {\n chain = organizationId === null\n ? chain.where('b.organization_id' as any, 'is', null as any)\n : chain.where('b.organization_id' as any, '=', organizationId)\n }\n if (usingPartitions && partitionIndex !== null) {\n chain = chain.where(sql<boolean>`mod(abs(hashtext(b.id::text)), ${partitionCountRaw}) = ${partitionIndex}`)\n }\n return chain\n }\n\n type ScopeStats = { tenantId: string | null; organizationId: string | null; count: number }\n const baseCounts = new Map<string, ScopeStats>()\n const registerBaseCount = (tenantValue: string | null, orgValue: string | null, count: number) => {\n const key = scopeKey(tenantValue, orgValue)\n baseCounts.set(key, { tenantId: tenantValue, organizationId: orgValue, count })\n }\n\n const groupByTenant = hasTenantCol && tenantId === undefined\n const groupByOrg = hasOrgCol && organizationId === undefined\n\n if (groupByTenant || groupByOrg) {\n let groupQuery = applyBaseWhere(\n db.selectFrom(`${table} as b` as any).select(sql<number>`count(*)`.as('count')),\n )\n if (groupByTenant) {\n groupQuery = groupQuery.select('b.tenant_id as tenant_id' as any).groupBy('b.tenant_id' as any)\n }\n if (groupByOrg) {\n groupQuery = groupQuery.select('b.organization_id as organization_id' as any).groupBy('b.organization_id' as any)\n }\n const rows = await groupQuery.execute() as Array<Record<string, unknown>>\n for (const row of rows) {\n const bucketTenant = groupByTenant\n ? ((row as any)?.tenant_id ?? null)\n : (tenantId === undefined ? null : tenantId ?? null)\n const bucketOrg = groupByOrg\n ? ((row as any)?.organization_id ?? null)\n : (organizationId === undefined ? null : organizationId ?? null)\n registerBaseCount(bucketTenant, bucketOrg, toNumber((row as any)?.count))\n }\n } else {\n const row = await applyBaseWhere(\n db.selectFrom(`${table} as b` as any).select(sql<number>`count(*)`.as('count')),\n ).executeTakeFirst() as { count: unknown } | undefined\n const bucketTenant = tenantId === undefined ? null : tenantId ?? null\n const bucketOrg = organizationId === undefined ? null : organizationId ?? null\n registerBaseCount(bucketTenant, bucketOrg, toNumber(row?.count))\n }\n\n const total = Array.from(baseCounts.values()).reduce((acc, value) => acc + (Number.isFinite(value.count) ? value.count : 0), 0)\n await prepareJob(db, jobScope, 'reindexing', { totalCount: total })\n const jobRow = await db\n .selectFrom('entity_index_jobs' as any)\n .select(['started_at' as any])\n .where('entity_type' as any, '=', entityType)\n .where('organization_id' as any, 'is', null as any)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId ?? null}`)\n .where(sql<boolean>`partition_index is not distinct from ${partitionIndex}`)\n .where(sql<boolean>`partition_count is not distinct from ${usingPartitions ? partitionCountRaw : null}`)\n .orderBy('started_at' as any, 'desc')\n .executeTakeFirst() as { started_at: Date | string } | undefined\n const jobStartedAt = jobRow?.started_at ? new Date(jobRow.started_at) : new Date()\n const deriveOrg = deriveOrgFromId.has(entityType)\n ? (row: AnyRow) => String(row.id)\n : undefined\n\n const scopeOverrides: { tenantId?: string; orgId?: string } = {}\n if (tenantId !== undefined && tenantId !== null) {\n scopeOverrides.tenantId = String(tenantId)\n }\n if (organizationId !== undefined && organizationId !== null) {\n scopeOverrides.orgId = String(organizationId)\n }\n\n const scopeEntries = Array.from(baseCounts.values()).map((entry) => ({\n tenantId: entry.tenantId,\n organizationId: entry.organizationId,\n }))\n const tenantScopes = Array.from(\n new Set(scopeEntries.map((entry) => entry.tenantId ?? null)),\n )\n\n let processed = 0\n let lastId: string | null = null\n let jobFailed = false\n const writeTotals = createEmptyUpsertIndexBatchResult()\n\n options?.onProgress?.({ processed, total, chunkSize: 0 })\n\n // Partitions run concurrently, so a scope-wide purge here would delete rows a sibling\n // partition has already written and never rebuild them (the run still reports success\n // because progress counts attempted writes). Each partition therefore purges only the\n // slice it is about to rebuild, and every partition purges \u2014 restricting the purge to\n // the coverage-resetting partition would otherwise leave the other slices' stale rows\n // behind on a forced rebuild.\n if (force && (resetCoverage || usingPartitions)) {\n try {\n let purgeQuery = db\n .deleteFrom('entity_indexes' as any)\n .where('entity_type' as any, '=', entityType)\n if (tenantId !== undefined) {\n purgeQuery = purgeQuery.where(sql<boolean>`tenant_id is not distinct from ${tenantId ?? null}`)\n }\n if (organizationId !== undefined) {\n purgeQuery = purgeQuery.where(sql<boolean>`organization_id is not distinct from ${organizationId ?? null}`)\n }\n if (usingPartitions && partitionIndex !== null) {\n purgeQuery = purgeQuery.where(\n sql<boolean>`mod(abs(hashtext(entity_id::text)), ${partitionCountRaw}) = ${partitionIndex}`,\n )\n }\n await purgeQuery.execute()\n } catch (error) {\n logger.warn('Failed to purge index rows before force reindex', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n partitionIndex: usingPartitions ? partitionIndex : null,\n error: error instanceof Error ? error.message : error,\n })\n }\n }\n\n // The vector purge is scope-wide and cannot be partitioned \u2014 the queued job deletes every\n // vector for the scope. Emitting it from one partition while siblings are already queueing\n // per-record vectorize jobs is the same race the row purge just had, so a partitioned run\n // leaves it to the caller that owns the fan-out (`api/reindex.ts` queues it once, before\n // dispatching any partition).\n if (force && resetCoverage && !usingPartitions && emitVectorize && eventBus) {\n if (tenantId !== undefined) {\n const payload: Record<string, unknown> = {\n entityType,\n tenantId: tenantId ?? null,\n }\n if (organizationId !== undefined) payload.organizationId = organizationId ?? null\n try {\n await eventBus.emitEvent('query_index.vectorize_purge', payload)\n } catch (err) {\n logger.warn('Failed to queue vector purge before force reindex', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n error: err instanceof Error ? err.message : err,\n })\n }\n } else {\n logger.warn('Skipping vector purge for force reindex without tenant scope', {\n entityType,\n })\n }\n }\n\n if (resetCoverage) {\n // Only meaningful for an unpartitioned run: `baseCounts` holds this partition's slice,\n // so writing it as the scope's base count under-reports by the partition factor, and\n // zeroing indexed_count scope-wide discards deltas siblings have already applied. The\n // authoritative `refreshCoverageSnapshot` at the end of every partition owns the counts\n // for partitioned runs.\n const nowTs = Date.now()\n for (const scope of usingPartitions ? [] : baseCounts.values()) {\n const key = `${entityType}|${scopeKey(scope.tenantId, scope.organizationId)}`\n const last = lastCoverageReset.get(key) ?? 0\n if (force || nowTs - last >= COVERAGE_REFRESH_THROTTLE_MS) {\n await writeCoverageCounts(em, {\n entityType,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n withDeleted: false,\n }, {\n baseCount: scope.count,\n indexedCount: 0,\n vectorCount: emitVectorize ? 0 : undefined,\n })\n lastCoverageReset.set(key, nowTs)\n }\n }\n }\n\n try {\n while (true) {\n let query = applyBaseWhere(\n db\n .selectFrom(`${table} as b` as any)\n .selectAll('b' as any)\n .orderBy('b.id' as any, 'asc')\n .limit(batchSize),\n )\n if (lastId !== null) {\n query = query.where('b.id' as any, '>', lastId)\n }\n const rows = await query.execute() as AnyRow[]\n if (!rows.length) break\n\n const encryption = resolveTenantEncryptionService(em as any)\n const dekKeyCache = new Map<string | null, string | null>()\n const encryptDoc = async (\n targetEntity: string,\n doc: Record<string, unknown>,\n scope: { organizationId: string | null; tenantId: string | null },\n ) => {\n return await encryptIndexDocForStorage(\n targetEntity,\n doc,\n { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },\n encryption,\n )\n }\n const decryptDoc = async (\n targetEntity: string,\n doc: Record<string, unknown>,\n scope: { organizationId: string | null; tenantId: string | null },\n ) => {\n const result = await decryptIndexDocForSearch(\n targetEntity,\n doc,\n { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },\n encryption,\n dekKeyCache,\n )\n if (isSearchDebugEnabled()) {\n logger.debug('Reindex decrypt', buildReindexDecryptDebugPayload(targetEntity, result as Record<string, unknown>, scope))\n }\n return result\n }\n\n const batchResult = await upsertIndexBatch(db, entityType, rows, scopeOverrides, { deriveOrganizationId: deriveOrg, encryptDoc, decryptDoc })\n mergeUpsertIndexBatchResults(writeTotals, batchResult)\n\n // A whole batch failing is infrastructural (pool exhausted, disk full, KMS down),\n // not a poison record. Abort now instead of grinding through the rest of the table.\n if (batchResult.written === 0 && batchResult.attempted > 0) {\n throw new QueryIndexBatchWriteError(entityType, writeTotals)\n }\n\n const failedInBatch = new Set(batchResult.failedRecordIds)\n const writtenRows = failedInBatch.size ? rows.filter((row) => !failedInBatch.has(String(row.id))) : rows\n\n const coverageDeltas = new Map<string, { tenantId: string | null; organizationId: string | null; delta: number }>()\n for (const row of writtenRows) {\n const scopeTenant = tenantId !== undefined\n ? tenantId ?? null\n : (hasTenantCol ? ((row as AnyRow).tenant_id ?? null) : null)\n const scopeOrg = organizationId !== undefined\n ? organizationId ?? null\n : (hasOrgCol ? ((row as AnyRow).organization_id ?? null) : (deriveOrg ? deriveOrg(row) ?? null : null))\n const key = scopeKey(scopeTenant ?? null, scopeOrg ?? null)\n const existingDelta = coverageDeltas.get(key)\n if (existingDelta) existingDelta.delta += 1\n else coverageDeltas.set(key, {\n tenantId: scopeTenant ?? null,\n organizationId: scopeOrg ?? null,\n delta: 1,\n })\n }\n if (coverageDeltas.size > 0) {\n await applyCoverageAdjustments(\n em,\n Array.from(coverageDeltas.values()).map((entry) => ({\n entityType,\n tenantId: entry.tenantId,\n organizationId: entry.organizationId,\n withDeleted: false,\n deltaBase: 0,\n deltaIndex: entry.delta,\n })),\n )\n }\n\n if (emitVectorize && eventBus) {\n await Promise.all(\n writtenRows.map((row) => {\n const scopeOrg = organizationId !== undefined\n ? organizationId ?? null\n : hasOrgCol\n ? ((row as AnyRow).organization_id ?? null)\n : (deriveOrg ? deriveOrg(row) ?? null : null)\n const scopeTenant = tenantId !== undefined\n ? tenantId ?? null\n : (hasTenantCol ? ((row as AnyRow).tenant_id ?? null) : null)\n return eventBus\n .emitEvent('query_index.vectorize_one', {\n entityType,\n recordId: String(row.id),\n organizationId: scopeOrg,\n tenantId: scopeTenant,\n })\n .catch(() => undefined)\n }),\n )\n }\n\n processed += batchResult.written\n lastId = String(rows[rows.length - 1]!.id)\n options?.onProgress?.({ processed, total, chunkSize: batchResult.written })\n await updateJobProgress(db, jobScope, batchResult.written)\n }\n\n // Records this run failed to write still look untouched to the purge predicate, so\n // without the exclusion the purge would delete the index rows it just failed to\n // rebuild \u2014 turning a stale entry into a missing one.\n const purgeExclusions = writeTotals.failedRecordIds\n if (purgeExclusions.length > MAX_PURGE_EXCLUSIONS) {\n logger.warn('Skipping orphan purge after widespread write failures', {\n entityType,\n failedRecords: purgeExclusions.length,\n })\n } else {\n await purgeOrphans(db, {\n entityType,\n tenantId,\n organizationId,\n partitionIndex: usingPartitions ? partitionIndex : null,\n partitionCount: usingPartitions ? partitionCountRaw : null,\n startedAt: jobStartedAt,\n excludeRecordIds: purgeExclusions,\n })\n }\n\n if (force && vectorService && (!usingPartitions || partitionIndex === null)) {\n try {\n await vectorService.removeOrphans({\n entityId: entityType,\n tenantId,\n organizationId,\n olderThan: jobStartedAt,\n })\n } catch (error) {\n logger.warn('Failed to prune vector orphans after reindex', {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n error: error instanceof Error ? error.message : error,\n })\n }\n }\n\n for (const scope of scopeEntries) {\n await refreshCoverageSnapshot(\n em,\n {\n entityType,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n withDeleted: false,\n },\n )\n }\n\n // Deliberately after the coverage refresh: the authoritative recount is what keeps\n // indexed_count truthful, and it is most worth having when a run has just failed.\n // Throwing here fails the queue job so the loss is visible instead of silent.\n if (writeTotals.searchTokenFailures > 0) {\n logger.warn('Search token writes failed during reindex', {\n entityType,\n batches: writeTotals.searchTokenFailures,\n })\n }\n assertIndexBatchWritesLanded(entityType, writeTotals)\n } catch (error) {\n jobFailed = true\n throw error\n } finally {\n // Still finalized on failure: the scope stays wedged behind the active-job guard\n // while finished_at is null. The status carries the outcome instead.\n await finalizeJob(db, jobScope, jobFailed ? { status: 'failed' } : {})\n }\n\n return {\n processed,\n total,\n scopes: scopeEntries,\n tenantScopes,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -172,6 +172,19 @@ function cloneJson(value) {
172
172
  if (value === null || value === void 0) return value;
173
173
  return JSON.parse(JSON.stringify(value));
174
174
  }
175
+ async function setDocumentCustomFieldsIfSupplied(em, params) {
176
+ const { customFields } = params;
177
+ if (customFields === void 0 || customFields === null) return;
178
+ const values = typeof customFields === "object" && !Array.isArray(customFields) ? customFields : {};
179
+ if (!Object.keys(values).length) return;
180
+ await setRecordCustomFields(em, {
181
+ entityId: params.entityId,
182
+ recordId: params.recordId,
183
+ organizationId: params.organizationId,
184
+ tenantId: params.tenantId,
185
+ values: normalizeCustomFieldValues(values)
186
+ });
187
+ }
175
188
  async function resolveCustomerSnapshot(em, organizationId, tenantId, customerEntityId, customerContactId) {
176
189
  if (!customerEntityId) return null;
177
190
  const customer = await em.findOne(
@@ -3349,6 +3362,13 @@ const createQuoteCommand = {
3349
3362
  tenantId: quote.tenantId,
3350
3363
  tagIds: parsed.tags
3351
3364
  });
3365
+ await setDocumentCustomFieldsIfSupplied(em, {
3366
+ entityId: E.sales.sales_quote,
3367
+ recordId: quote.id,
3368
+ organizationId: quote.organizationId,
3369
+ tenantId: quote.tenantId,
3370
+ customFields: parsed.customFields
3371
+ });
3352
3372
  }
3353
3373
  ],
3354
3374
  { transaction: true }
@@ -4245,6 +4265,13 @@ const createOrderCommand = {
4245
4265
  tenantId: order.tenantId,
4246
4266
  tagIds: parsed.tags
4247
4267
  });
4268
+ await setDocumentCustomFieldsIfSupplied(em, {
4269
+ entityId: E.sales.sales_order,
4270
+ recordId: order.id,
4271
+ organizationId: order.organizationId,
4272
+ tenantId: order.tenantId,
4273
+ customFields: parsed.customFields
4274
+ });
4248
4275
  }
4249
4276
  ],
4250
4277
  { transaction: true }