@open-mercato/core 0.6.7-develop.6706.1.b3a4c759bb → 0.6.7-develop.6726.1.983ae8a07e

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 (117) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/admin/nav.js +14 -4
  3. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  4. package/dist/modules/auth/api/users/route.js +8 -19
  5. package/dist/modules/auth/api/users/route.js.map +2 -2
  6. package/dist/modules/auth/lib/backendChrome.js +31 -14
  7. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  8. package/dist/modules/customers/api/utils.js +12 -18
  9. package/dist/modules/customers/api/utils.js.map +2 -2
  10. package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +17 -3
  11. package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +2 -2
  12. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +7 -1
  13. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  14. package/dist/modules/customers/cli.js +11 -4
  15. package/dist/modules/customers/cli.js.map +2 -2
  16. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +4 -2
  17. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  18. package/dist/modules/customers/components/formConfig.js +14 -10
  19. package/dist/modules/customers/components/formConfig.js.map +2 -2
  20. package/dist/modules/dashboards/api/widgets/data/batch/route.js +4 -2
  21. package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +2 -2
  22. package/dist/modules/dashboards/api/widgets/data/route.js +20 -2
  23. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  24. package/dist/modules/dashboards/lib/aggregations.js +76 -40
  25. package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
  26. package/dist/modules/dashboards/lib/exactDecimal.js +69 -0
  27. package/dist/modules/dashboards/lib/exactDecimal.js.map +7 -0
  28. package/dist/modules/dashboards/services/widgetDataService.js +228 -5
  29. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  30. package/dist/modules/feature_toggles/components/FeatureGuard.js +3 -2
  31. package/dist/modules/feature_toggles/components/FeatureGuard.js.map +2 -2
  32. package/dist/modules/feature_toggles/components/hooks/staleTime.js +5 -0
  33. package/dist/modules/feature_toggles/components/hooks/staleTime.js.map +7 -0
  34. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js +5 -2
  35. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js.map +2 -2
  36. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js +5 -2
  37. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js.map +2 -2
  38. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js +5 -2
  39. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js.map +2 -2
  40. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js +5 -2
  41. package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js.map +2 -2
  42. package/dist/modules/inbox_ops/api/proposals/route.js +16 -1
  43. package/dist/modules/inbox_ops/api/proposals/route.js.map +2 -2
  44. package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js +32 -0
  45. package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js.map +7 -0
  46. package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +0 -5
  47. package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
  48. package/dist/modules/integrations/widgets/injection/external-ids/widget.js +14 -0
  49. package/dist/modules/integrations/widgets/injection/external-ids/widget.js.map +7 -0
  50. package/dist/modules/messages/lib/searchLookup.js +8 -16
  51. package/dist/modules/messages/lib/searchLookup.js.map +2 -2
  52. package/dist/modules/query_index/api/openapi.js +13 -1
  53. package/dist/modules/query_index/api/openapi.js.map +2 -2
  54. package/dist/modules/query_index/api/status.js +165 -105
  55. package/dist/modules/query_index/api/status.js.map +2 -2
  56. package/dist/modules/query_index/components/QueryIndexesTable.js +128 -41
  57. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  58. package/dist/modules/query_index/lib/batch.js +11 -4
  59. package/dist/modules/query_index/lib/batch.js.map +2 -2
  60. package/dist/modules/query_index/lib/document.js +12 -5
  61. package/dist/modules/query_index/lib/document.js.map +2 -2
  62. package/dist/modules/query_index/lib/engine.js +36 -4
  63. package/dist/modules/query_index/lib/engine.js.map +2 -2
  64. package/dist/modules/query_index/lib/indexer.js +1 -1
  65. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  66. package/dist/modules/query_index/lib/search-tokens.js +7 -4
  67. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  68. package/dist/modules/sales/api/documents/factory.js +14 -3
  69. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  70. package/dist/modules/sales/commands/documents.js +22 -1
  71. package/dist/modules/sales/commands/documents.js.map +2 -2
  72. package/dist/modules/sales/components/useSalesChannelsEnabled.js +2 -35
  73. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +2 -2
  74. package/dist/modules/sales/data/validators.js +25 -4
  75. package/dist/modules/sales/data/validators.js.map +2 -2
  76. package/package.json +7 -7
  77. package/src/modules/auth/api/admin/nav.ts +24 -4
  78. package/src/modules/auth/api/users/route.ts +8 -28
  79. package/src/modules/auth/lib/backendChrome.tsx +51 -13
  80. package/src/modules/customers/api/utils.ts +12 -31
  81. package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +35 -3
  82. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +10 -1
  83. package/src/modules/customers/cli.ts +11 -4
  84. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +4 -0
  85. package/src/modules/customers/components/formConfig.tsx +12 -8
  86. package/src/modules/dashboards/api/widgets/data/batch/route.ts +4 -0
  87. package/src/modules/dashboards/api/widgets/data/route.ts +21 -0
  88. package/src/modules/dashboards/lib/aggregations.ts +122 -43
  89. package/src/modules/dashboards/lib/exactDecimal.ts +96 -0
  90. package/src/modules/dashboards/services/widgetDataService.ts +342 -4
  91. package/src/modules/feature_toggles/components/FeatureGuard.tsx +9 -2
  92. package/src/modules/feature_toggles/components/hooks/staleTime.ts +5 -0
  93. package/src/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.ts +13 -1
  94. package/src/modules/feature_toggles/components/hooks/useFeatureFlagJson.ts +12 -3
  95. package/src/modules/feature_toggles/components/hooks/useFeatureFlagNumber.ts +10 -1
  96. package/src/modules/feature_toggles/components/hooks/useFeatureFlagString.ts +10 -1
  97. package/src/modules/inbox_ops/api/proposals/route.ts +20 -1
  98. package/src/modules/inbox_ops/subscribers/search-reindex-proposal-created.ts +39 -0
  99. package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +0 -5
  100. package/src/modules/integrations/widgets/injection/external-ids/widget.ts +13 -0
  101. package/src/modules/messages/lib/searchLookup.ts +11 -34
  102. package/src/modules/query_index/api/openapi.ts +12 -0
  103. package/src/modules/query_index/api/status.ts +238 -136
  104. package/src/modules/query_index/components/QueryIndexesTable.tsx +192 -46
  105. package/src/modules/query_index/i18n/de.json +5 -3
  106. package/src/modules/query_index/i18n/en.json +5 -3
  107. package/src/modules/query_index/i18n/es.json +5 -3
  108. package/src/modules/query_index/i18n/pl.json +5 -3
  109. package/src/modules/query_index/lib/batch.ts +11 -4
  110. package/src/modules/query_index/lib/document.ts +35 -4
  111. package/src/modules/query_index/lib/engine.ts +53 -5
  112. package/src/modules/query_index/lib/indexer.ts +1 -1
  113. package/src/modules/query_index/lib/search-tokens.ts +13 -4
  114. package/src/modules/sales/api/documents/factory.ts +28 -3
  115. package/src/modules/sales/commands/documents.ts +26 -2
  116. package/src/modules/sales/components/useSalesChannelsEnabled.ts +2 -48
  117. package/src/modules/sales/data/validators.ts +50 -5
@@ -8,23 +8,30 @@ export type FeatureGuardProps = {
8
8
  children: React.ReactNode
9
9
  fallback?: React.ReactNode
10
10
  loadingFallback?: React.ReactNode
11
+ defaultValue?: boolean
11
12
  }
12
13
 
13
14
  /**
14
15
  * FeatureGuard component that conditionally renders children based on feature toggle state.
15
- *
16
+ *
16
17
  * @param id - The feature toggle identifier
17
18
  * @param children - Content to render when the feature is enabled
18
19
  * @param fallback - Optional content to render when the feature is disabled
19
20
  * @param loadingFallback - Optional content to render while loading the feature state
21
+ * @param defaultValue - Toggle value assumed when the check cannot be resolved
22
+ * (undefined toggle, type mismatch, network or permission failure). Defaults
23
+ * to `false`; pass `true` to keep guarded UI visible when the check fails.
24
+ * Distinct from `fallback`, which is what gets rendered once the feature is
25
+ * known to be disabled.
20
26
  */
21
27
  export function FeatureGuard({
22
28
  id,
23
29
  children,
24
30
  fallback = null,
25
31
  loadingFallback = null,
32
+ defaultValue = false,
26
33
  }: FeatureGuardProps) {
27
- const { enabled, isLoading } = useFeatureFlagBoolean({ id })
34
+ const { enabled, isLoading } = useFeatureFlagBoolean({ id, defaultValue })
28
35
 
29
36
  if (isLoading) {
30
37
  return <>{loadingFallback}</>
@@ -0,0 +1,5 @@
1
+ // Mirrors the server-side toggle resolution cache TTL in
2
+ // `lib/feature-flag-check.ts`, so several components reading the same toggle
3
+ // share one request instead of refetching a value the server would not have
4
+ // re-resolved anyway.
5
+ export const FEATURE_FLAG_STALE_TIME_MS = 60_000
@@ -2,9 +2,19 @@
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
5
+ import { FEATURE_FLAG_STALE_TIME_MS } from './staleTime'
5
6
 
6
7
  export type UseFeatureFlagOptions = {
7
8
  id: string
9
+ /**
10
+ * Value used while the check is in flight and whenever the toggle cannot be
11
+ * resolved (undefined toggle, type mismatch, network or permission failure).
12
+ *
13
+ * Defaults to `false` so an unresolved toggle keeps a feature switched off.
14
+ * Pass `true` for toggles that hide existing UI — there a failed check must
15
+ * not make the feature disappear.
16
+ */
17
+ defaultValue?: boolean
8
18
  }
9
19
 
10
20
  export type UseFeatureFlagResult = {
@@ -21,6 +31,7 @@ type Result<T> = {
21
31
  }
22
32
 
23
33
  export function useFeatureFlagBoolean(options: UseFeatureFlagOptions): UseFeatureFlagResult {
34
+ const defaultValue = options.defaultValue ?? false
24
35
  const query = useQuery({
25
36
  queryKey: ['featureToggles', 'check', options?.id],
26
37
  queryFn: async () => {
@@ -37,9 +48,10 @@ export function useFeatureFlagBoolean(options: UseFeatureFlagOptions): UseFeatur
37
48
  return result
38
49
  },
39
50
  enabled: !!options.id,
51
+ staleTime: FEATURE_FLAG_STALE_TIME_MS,
40
52
  })
41
53
 
42
- const enabled = query.data?.ok ? query.data.value : false
54
+ const enabled = query.data?.ok ? query.data.value : defaultValue
43
55
  const isLoading = query.isLoading
44
56
 
45
57
  return {
@@ -2,9 +2,16 @@
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
5
+ import { FEATURE_FLAG_STALE_TIME_MS } from './staleTime'
5
6
 
6
- export type UseFeatureFlagJsonOptions = {
7
+ export type UseFeatureFlagJsonOptions<T = unknown> = {
7
8
  id: string
9
+ /**
10
+ * Value used while the check is in flight and whenever the toggle cannot be
11
+ * resolved (undefined toggle, type mismatch, network or permission failure).
12
+ * Defaults to `null`.
13
+ */
14
+ defaultValue?: T | null
8
15
  }
9
16
 
10
17
  export type UseFeatureFlagJsonResult<T = unknown> = {
@@ -20,7 +27,8 @@ type Result<T> = {
20
27
  error: unknown
21
28
  }
22
29
 
23
- export function useFeatureFlagJson<T = unknown>(options: UseFeatureFlagJsonOptions): UseFeatureFlagJsonResult<T> {
30
+ export function useFeatureFlagJson<T = unknown>(options: UseFeatureFlagJsonOptions<T>): UseFeatureFlagJsonResult<T> {
31
+ const defaultValue = options.defaultValue ?? null
24
32
  const query = useQuery({
25
33
  queryKey: ['featureToggles', 'check', 'json', options?.id],
26
34
  queryFn: async () => {
@@ -37,9 +45,10 @@ export function useFeatureFlagJson<T = unknown>(options: UseFeatureFlagJsonOptio
37
45
  return result
38
46
  },
39
47
  enabled: !!options.id,
48
+ staleTime: FEATURE_FLAG_STALE_TIME_MS,
40
49
  })
41
50
 
42
- const value = query.data?.ok ? query.data.value : null
51
+ const value = query.data?.ok ? query.data.value : defaultValue
43
52
  const isLoading = query.isLoading
44
53
 
45
54
  return {
@@ -2,9 +2,16 @@
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
5
+ import { FEATURE_FLAG_STALE_TIME_MS } from './staleTime'
5
6
 
6
7
  export type UseFeatureFlagNumberOptions = {
7
8
  id: string
9
+ /**
10
+ * Value used while the check is in flight and whenever the toggle cannot be
11
+ * resolved (undefined toggle, type mismatch, network or permission failure).
12
+ * Defaults to `null`.
13
+ */
14
+ defaultValue?: number | null
8
15
  }
9
16
 
10
17
  export type UseFeatureFlagNumberResult = {
@@ -21,6 +28,7 @@ type Result<T> = {
21
28
  }
22
29
 
23
30
  export function useFeatureFlagNumber(options: UseFeatureFlagNumberOptions): UseFeatureFlagNumberResult {
31
+ const defaultValue = options.defaultValue ?? null
24
32
  const query = useQuery({
25
33
  queryKey: ['featureToggles', 'check', 'number', options?.id],
26
34
  queryFn: async () => {
@@ -37,9 +45,10 @@ export function useFeatureFlagNumber(options: UseFeatureFlagNumberOptions): UseF
37
45
  return result
38
46
  },
39
47
  enabled: !!options.id,
48
+ staleTime: FEATURE_FLAG_STALE_TIME_MS,
40
49
  })
41
50
 
42
- const value = query.data?.ok ? query.data.value : null
51
+ const value = query.data?.ok ? query.data.value : defaultValue
43
52
  const isLoading = query.isLoading
44
53
 
45
54
  return {
@@ -2,9 +2,16 @@
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
5
+ import { FEATURE_FLAG_STALE_TIME_MS } from './staleTime'
5
6
 
6
7
  export type UseFeatureFlagStringOptions = {
7
8
  id: string
9
+ /**
10
+ * Value used while the check is in flight and whenever the toggle cannot be
11
+ * resolved (undefined toggle, type mismatch, network or permission failure).
12
+ * Defaults to `null`.
13
+ */
14
+ defaultValue?: string | null
8
15
  }
9
16
 
10
17
  export type UseFeatureFlagStringResult = {
@@ -21,6 +28,7 @@ type Result<T> = {
21
28
  }
22
29
 
23
30
  export function useFeatureFlagString(options: UseFeatureFlagStringOptions): UseFeatureFlagStringResult {
31
+ const defaultValue = options.defaultValue ?? null
24
32
  const query = useQuery({
25
33
  queryKey: ['featureToggles', 'check', 'string', options?.id],
26
34
  queryFn: async () => {
@@ -37,9 +45,10 @@ export function useFeatureFlagString(options: UseFeatureFlagStringOptions): UseF
37
45
  return result
38
46
  },
39
47
  enabled: !!options.id,
48
+ staleTime: FEATURE_FLAG_STALE_TIME_MS,
40
49
  })
41
50
 
42
- const value = query.data?.ok ? query.data.value : null
51
+ const value = query.data?.ok ? query.data.value : defaultValue
43
52
  const isLoading = query.isLoading
44
53
 
45
54
  return {
@@ -4,6 +4,8 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
4
4
  import type { FilterQuery } from '@mikro-orm/postgresql'
5
5
  import { findAndCountWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
6
6
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
7
+ import { findEntityIdsBySearchTokens, type SearchTokenDatabase } from '@open-mercato/shared/lib/search/tokenLookup'
8
+ import { E } from '#generated/entities.ids.generated'
7
9
  import { InboxProposal, InboxEmail, InboxProposalAction, InboxDiscrepancy, type InboxProposalCategory } from '../../data/entities'
8
10
  import { proposalListQuerySchema } from '../../data/validators'
9
11
  import { resolveRequestContext, UnauthorizedError } from '../routeHelpers'
@@ -47,7 +49,24 @@ export async function GET(req: Request) {
47
49
  }
48
50
  }
49
51
  if (query.search) {
50
- where.summary = { $ilike: `%${escapeLikePattern(query.search)}%` }
52
+ // `summary` is covered by the inbox_ops encryption map, so an ILIKE alone
53
+ // compares the pattern against ciphertext and matches nothing once
54
+ // encryption is on. Union it with the token index, which stores hashes of
55
+ // the plaintext. Issue #2990.
56
+ const searchOr: FilterQuery<InboxProposal>[] = [
57
+ { summary: { $ilike: `%${escapeLikePattern(query.search)}%` } },
58
+ ]
59
+ const tokenMatch = await findEntityIdsBySearchTokens({
60
+ db: ctx.em.getKysely<SearchTokenDatabase>(),
61
+ entityType: E.inbox_ops.inbox_proposal,
62
+ query: query.search,
63
+ fields: ['summary'],
64
+ scope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
65
+ })
66
+ if (tokenMatch.matched && tokenMatch.ids.length) {
67
+ searchOr.push({ id: { $in: tokenMatch.ids } })
68
+ }
69
+ where.$or = searchOr
51
70
  }
52
71
 
53
72
  const offset = (query.page - 1) * query.pageSize
@@ -0,0 +1,39 @@
1
+ export const metadata = {
2
+ event: 'inbox_ops.proposal.created',
3
+ persistent: false,
4
+ id: 'inbox_ops:query-index-reindex-proposal-created',
5
+ }
6
+
7
+ type Payload = {
8
+ proposalId?: string
9
+ tenantId?: string | null
10
+ organizationId?: string | null
11
+ }
12
+
13
+ type EventBus = { emitEvent: (name: string, body: unknown, options?: unknown) => Promise<void> }
14
+ type HandlerContext = { resolve: <T = unknown>(name: string) => T }
15
+
16
+ export default async function handle(payload: Payload, ctx: HandlerContext): Promise<void> {
17
+ const recordId = typeof payload.proposalId === 'string' ? payload.proposalId : null
18
+ const tenantId = typeof payload.tenantId === 'string' ? payload.tenantId : null
19
+ if (!recordId || !tenantId) return
20
+
21
+ let eventBus: EventBus | null = null
22
+ try {
23
+ eventBus = ctx.resolve<EventBus>('eventBus')
24
+ } catch {
25
+ return
26
+ }
27
+
28
+ await eventBus.emitEvent('query_index.upsert_one', {
29
+ entityType: 'inbox_ops:inbox_proposal',
30
+ recordId,
31
+ organizationId: payload.organizationId ?? null,
32
+ tenantId,
33
+ crudAction: 'created',
34
+ coverageBaseDelta: 1,
35
+ }, {
36
+ tenantId,
37
+ organizationId: payload.organizationId ?? null,
38
+ }).catch(() => undefined)
39
+ }
@@ -87,8 +87,3 @@ export default function ExternalIdsWidget({ data }: InjectionWidgetComponentProp
87
87
  )
88
88
  }
89
89
 
90
- ExternalIdsWidget.metadata = {
91
- id: 'integrations.injection.external-ids',
92
- title: 'External IDs',
93
- features: ['integrations.view'],
94
- }
@@ -0,0 +1,13 @@
1
+ import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'
2
+ import ExternalIdsWidget from './widget.client'
3
+
4
+ const widget: InjectionWidgetModule = {
5
+ metadata: {
6
+ id: 'integrations.injection.external-ids',
7
+ title: 'External IDs',
8
+ features: ['integrations.view'],
9
+ },
10
+ Widget: ExternalIdsWidget,
11
+ }
12
+
13
+ export default widget
@@ -1,10 +1,9 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
- import { type Kysely, sql } from 'kysely'
3
- import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
4
- import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
2
+ import type { Kysely } from 'kysely'
3
+ import { findEntityIdsBySearchTokensCompat, type SearchTokenDatabase } from '@open-mercato/shared/lib/search/tokenLookup'
5
4
 
6
- function getDb(em: EntityManager): Kysely<any> {
7
- return em.getKysely<any>()
5
+ function getDb(em: EntityManager): Kysely<SearchTokenDatabase> {
6
+ return em.getKysely<SearchTokenDatabase>()
8
7
  }
9
8
 
10
9
  export async function findMessageIdsBySearchTokens({
@@ -20,33 +19,11 @@ export async function findMessageIdsBySearchTokens({
20
19
  organizationId: string | null
21
20
  fields?: string[]
22
21
  }): Promise<string[] | null> {
23
- const trimmed = query.trim()
24
- if (!trimmed) return null
25
-
26
- const tokens = tokenizeText(trimmed, resolveSearchConfig())
27
- if (!tokens.hashes.length) return []
28
-
29
- const db = getDb(em) as any
30
- let searchQuery = db
31
- .selectFrom('search_tokens')
32
- .select('entity_id')
33
- .where('entity_type', '=', 'messages:message')
34
- .where('field', 'in', fields)
35
- .where('token_hash', 'in', tokens.hashes)
36
- .where(sql<boolean>`tenant_id is not distinct from ${tenantId}`)
37
-
38
- if (organizationId) {
39
- searchQuery = searchQuery.where('organization_id', '=', organizationId)
40
- } else {
41
- searchQuery = searchQuery.where(sql<boolean>`organization_id is not distinct from ${null}`)
42
- }
43
-
44
- const rows = await searchQuery
45
- .groupBy('entity_id')
46
- .having(sql<boolean>`count(distinct token_hash) >= ${tokens.hashes.length}`)
47
- .execute()
48
-
49
- return rows
50
- .map((row: { entity_id?: unknown }) => (typeof row.entity_id === 'string' ? row.entity_id : null))
51
- .filter((id: string | null): id is string => typeof id === 'string' && id.length > 0)
22
+ return findEntityIdsBySearchTokensCompat({
23
+ db: getDb(em),
24
+ entityType: 'messages:message',
25
+ query,
26
+ fields,
27
+ scope: { tenantId, organizationId },
28
+ })
52
29
  }
@@ -37,13 +37,25 @@ export const queryIndexJobSchema = z.object({
37
37
 
38
38
  export const queryIndexStatusItemSchema = z.object({
39
39
  entityId: z.string(),
40
+ /** Human label. Code-defined entities have no label registry, so this mirrors `entityId`. */
40
41
  label: z.string(),
41
42
  baseCount: z.number().int().nonnegative().nullable(),
42
43
  indexCount: z.number().int().nonnegative().nullable(),
43
44
  vectorCount: z.number().int().nonnegative().nullable().optional(),
45
+ /** The entity declares `buildSource`, i.e. it is configured for vector search. */
44
46
  vectorEnabled: z.boolean().optional(),
47
+ /** Additive: vector indexing can actually run (auto-indexing on, provider reachable, tenant opted in). */
48
+ vectorIndexingActive: z.boolean().optional(),
49
+ fulltextCount: z.number().int().nonnegative().nullable().optional(),
50
+ fulltextEnabled: z.boolean().optional(),
51
+ /** Additive: the entity has active custom field definitions in the current scope. */
52
+ hasCustomFields: z.boolean().optional(),
53
+ /** Aggregate health: query index in sync AND, when vector-configured, vector coverage complete. */
45
54
  ok: z.boolean(),
55
+ /** Additive: query index alone is in sync with the base table, independent of vector/fulltext. */
56
+ queryIndexOk: z.boolean().optional(),
46
57
  job: queryIndexJobSchema,
58
+ refreshedAt: z.string().datetime().nullable().optional(),
47
59
  })
48
60
 
49
61
  export const queryIndexErrorLogSchema = z.object({