@open-mercato/core 0.4.6-develop-6953d75a91 → 0.4.6-develop-90c3eb0e8a

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 (34) hide show
  1. package/dist/modules/notifications/events.js +30 -0
  2. package/dist/modules/notifications/events.js.map +7 -0
  3. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +2 -3
  4. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +2 -2
  5. package/dist/modules/notifications/lib/events.js +6 -1
  6. package/dist/modules/notifications/lib/events.js.map +2 -2
  7. package/dist/modules/notifications/lib/notificationMapper.js +10 -1
  8. package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
  9. package/dist/modules/notifications/lib/notificationService.js +26 -1
  10. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  11. package/dist/modules/progress/events.js +6 -6
  12. package/dist/modules/progress/events.js.map +2 -2
  13. package/dist/modules/progress/lib/events.js.map +1 -1
  14. package/dist/modules/progress/lib/progressServiceImpl.js +38 -29
  15. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  16. package/dist/modules/query_index/api/reindex.js +3 -0
  17. package/dist/modules/query_index/api/reindex.js.map +2 -2
  18. package/dist/modules/query_index/components/QueryIndexesTable.js +8 -10
  19. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  20. package/dist/modules/query_index/subscribers/reindex.js +89 -1
  21. package/dist/modules/query_index/subscribers/reindex.js.map +2 -2
  22. package/package.json +2 -2
  23. package/src/modules/notifications/README.md +21 -0
  24. package/src/modules/notifications/events.ts +28 -0
  25. package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +2 -3
  26. package/src/modules/notifications/lib/events.ts +5 -0
  27. package/src/modules/notifications/lib/notificationMapper.ts +12 -1
  28. package/src/modules/notifications/lib/notificationService.ts +33 -1
  29. package/src/modules/progress/events.ts +6 -6
  30. package/src/modules/progress/lib/events.ts +60 -0
  31. package/src/modules/progress/lib/progressServiceImpl.ts +32 -22
  32. package/src/modules/query_index/api/reindex.ts +3 -0
  33. package/src/modules/query_index/components/QueryIndexesTable.tsx +8 -10
  34. package/src/modules/query_index/subscribers/reindex.ts +99 -0
@@ -18,6 +18,15 @@ export type ProgressJobCreatedPayload = {
18
18
  jobId: string
19
19
  jobType: string
20
20
  name: string
21
+ description?: string | null
22
+ status?: string
23
+ progressPercent?: number
24
+ processedCount?: number
25
+ totalCount?: number | null
26
+ etaSeconds?: number | null
27
+ cancellable?: boolean
28
+ startedAt?: string | null
29
+ finishedAt?: string | null
21
30
  tenantId: string
22
31
  organizationId?: string | null
23
32
  }
@@ -25,36 +34,87 @@ export type ProgressJobCreatedPayload = {
25
34
  export type ProgressJobStartedPayload = {
26
35
  jobId: string
27
36
  jobType: string
37
+ name?: string
38
+ description?: string | null
39
+ status?: string
40
+ progressPercent?: number
41
+ processedCount?: number
42
+ totalCount?: number | null
43
+ etaSeconds?: number | null
44
+ cancellable?: boolean
45
+ startedAt?: string | null
46
+ finishedAt?: string | null
28
47
  tenantId: string
48
+ organizationId?: string | null
29
49
  }
30
50
 
31
51
  export type ProgressJobUpdatedPayload = {
32
52
  jobId: string
33
53
  jobType?: string
54
+ name?: string
55
+ description?: string | null
56
+ status?: string
34
57
  progressPercent: number
35
58
  processedCount: number
36
59
  totalCount?: number | null
37
60
  etaSeconds?: number | null
38
61
  tenantId: string
62
+ organizationId?: string | null
63
+ cancellable?: boolean
64
+ startedAt?: string | null
65
+ finishedAt?: string | null
39
66
  }
40
67
 
41
68
  export type ProgressJobCompletedPayload = {
42
69
  jobId: string
43
70
  jobType: string
71
+ name?: string
72
+ description?: string | null
73
+ status?: string
74
+ progressPercent?: number
75
+ processedCount?: number
76
+ totalCount?: number | null
77
+ etaSeconds?: number | null
78
+ cancellable?: boolean
79
+ startedAt?: string | null
80
+ finishedAt?: string | null
44
81
  resultSummary?: Record<string, unknown> | null
45
82
  tenantId: string
83
+ organizationId?: string | null
46
84
  }
47
85
 
48
86
  export type ProgressJobFailedPayload = {
49
87
  jobId: string
50
88
  jobType: string
89
+ name?: string
90
+ description?: string | null
91
+ status?: string
92
+ progressPercent?: number
93
+ processedCount?: number
94
+ totalCount?: number | null
95
+ etaSeconds?: number | null
96
+ cancellable?: boolean
97
+ startedAt?: string | null
98
+ finishedAt?: string | null
51
99
  errorMessage: string
52
100
  tenantId: string
101
+ organizationId?: string | null
53
102
  stale?: boolean
54
103
  }
55
104
 
56
105
  export type ProgressJobCancelledPayload = {
57
106
  jobId: string
58
107
  jobType: string
108
+ name?: string
109
+ description?: string | null
110
+ status?: string
111
+ progressPercent?: number
112
+ processedCount?: number
113
+ totalCount?: number | null
114
+ etaSeconds?: number | null
115
+ cancellable?: boolean
116
+ startedAt?: string | null
117
+ finishedAt?: string | null
59
118
  tenantId: string
119
+ organizationId?: string | null
60
120
  }
@@ -4,6 +4,23 @@ import type { ProgressService } from './progressService'
4
4
  import { calculateEta, calculateProgressPercent, STALE_JOB_TIMEOUT_SECONDS } from './progressService'
5
5
  import { PROGRESS_EVENTS } from './events'
6
6
 
7
+ function buildJobPayload(job: ProgressJob): Record<string, unknown> {
8
+ return {
9
+ jobId: job.id,
10
+ jobType: job.jobType,
11
+ name: job.name,
12
+ description: job.description ?? null,
13
+ status: job.status,
14
+ progressPercent: job.progressPercent,
15
+ processedCount: job.processedCount,
16
+ totalCount: job.totalCount ?? null,
17
+ etaSeconds: job.etaSeconds ?? null,
18
+ cancellable: job.cancellable,
19
+ startedAt: job.startedAt?.toISOString() ?? null,
20
+ finishedAt: job.finishedAt?.toISOString() ?? null,
21
+ }
22
+ }
23
+
7
24
  export function createProgressService(em: EntityManager, eventBus: { emit: (event: string, payload: Record<string, unknown>) => Promise<void> }): ProgressService {
8
25
  return {
9
26
  async createJob(input, ctx) {
@@ -26,9 +43,7 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
26
43
  await em.persistAndFlush(job)
27
44
 
28
45
  await eventBus.emit(PROGRESS_EVENTS.JOB_CREATED, {
29
- jobId: job.id,
30
- jobType: job.jobType,
31
- name: job.name,
46
+ ...buildJobPayload(job),
32
47
  tenantId: ctx.tenantId,
33
48
  organizationId: ctx.organizationId,
34
49
  })
@@ -46,9 +61,9 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
46
61
  await em.flush()
47
62
 
48
63
  await eventBus.emit(PROGRESS_EVENTS.JOB_STARTED, {
49
- jobId: job.id,
50
- jobType: job.jobType,
64
+ ...buildJobPayload(job),
51
65
  tenantId: ctx.tenantId,
66
+ organizationId: job.organizationId ?? null,
52
67
  })
53
68
 
54
69
  return job
@@ -84,13 +99,9 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
84
99
  await em.flush()
85
100
 
86
101
  await eventBus.emit(PROGRESS_EVENTS.JOB_UPDATED, {
87
- jobId: job.id,
88
- jobType: job.jobType,
89
- progressPercent: job.progressPercent,
90
- processedCount: job.processedCount,
91
- totalCount: job.totalCount,
92
- etaSeconds: job.etaSeconds,
102
+ ...buildJobPayload(job),
93
103
  tenantId: ctx.tenantId,
104
+ organizationId: job.organizationId ?? null,
94
105
  })
95
106
 
96
107
  return job
@@ -112,10 +123,9 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
112
123
  await em.flush()
113
124
 
114
125
  await eventBus.emit(PROGRESS_EVENTS.JOB_UPDATED, {
115
- jobId: job.id,
116
- progressPercent: job.progressPercent,
117
- processedCount: job.processedCount,
126
+ ...buildJobPayload(job),
118
127
  tenantId: ctx.tenantId,
128
+ organizationId: job.organizationId ?? null,
119
129
  })
120
130
 
121
131
  return job
@@ -136,10 +146,10 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
136
146
  await em.flush()
137
147
 
138
148
  await eventBus.emit(PROGRESS_EVENTS.JOB_COMPLETED, {
139
- jobId: job.id,
140
- jobType: job.jobType,
149
+ ...buildJobPayload(job),
141
150
  resultSummary: job.resultSummary,
142
151
  tenantId: ctx.tenantId,
152
+ organizationId: job.organizationId ?? null,
143
153
  })
144
154
 
145
155
  return job
@@ -157,10 +167,10 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
157
167
  await em.flush()
158
168
 
159
169
  await eventBus.emit(PROGRESS_EVENTS.JOB_FAILED, {
160
- jobId: job.id,
161
- jobType: job.jobType,
170
+ ...buildJobPayload(job),
162
171
  errorMessage: job.errorMessage,
163
172
  tenantId: ctx.tenantId,
173
+ organizationId: job.organizationId ?? null,
164
174
  })
165
175
 
166
176
  return job
@@ -185,9 +195,9 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
185
195
  await em.flush()
186
196
 
187
197
  await eventBus.emit(PROGRESS_EVENTS.JOB_CANCELLED, {
188
- jobId: job.id,
189
- jobType: job.jobType,
198
+ ...buildJobPayload(job),
190
199
  tenantId: ctx.tenantId,
200
+ organizationId: job.organizationId ?? null,
191
201
  })
192
202
 
193
203
  return job
@@ -246,11 +256,11 @@ export function createProgressService(em: EntityManager, eventBus: { emit: (even
246
256
  job.errorMessage = `Job stale: no heartbeat for ${timeoutSeconds} seconds`
247
257
 
248
258
  await eventBus.emit(PROGRESS_EVENTS.JOB_FAILED, {
249
- jobId: job.id,
250
- jobType: job.jobType,
259
+ ...buildJobPayload(job),
251
260
  errorMessage: job.errorMessage,
252
261
  tenantId: job.tenantId,
253
262
  stale: true,
263
+ organizationId: job.organizationId ?? null,
254
264
  })
255
265
  }
256
266
 
@@ -71,6 +71,9 @@ export async function POST(req: Request) {
71
71
  if (auth.orgId !== undefined) {
72
72
  payload.organizationId = auth.orgId ?? null
73
73
  }
74
+ if (typeof auth.sub === 'string' && auth.sub.length > 0) {
75
+ payload.requestedByUserId = auth.sub
76
+ }
74
77
  return bus.emitEvent(
75
78
  'query_index.reindex',
76
79
  payload,
@@ -288,16 +288,14 @@ export default function QueryIndexesTable() {
288
288
  : t('query_index.table.actions.vectorReindex')
289
289
  const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })
290
290
  try {
291
- if (action === 'reindex') {
292
- await apiCallOrThrow('/api/vector/reindex', {
293
- method: 'POST',
294
- headers: { 'content-type': 'application/json' },
295
- body: JSON.stringify({ entityId }),
296
- }, { errorMessage })
297
- } else {
298
- const url = `/api/vector/index?entityId=${encodeURIComponent(entityId)}`
299
- await apiCallOrThrow(url, { method: 'DELETE' }, { errorMessage })
300
- }
291
+ await apiCallOrThrow('/api/search/embeddings/reindex', {
292
+ method: 'POST',
293
+ headers: { 'content-type': 'application/json' },
294
+ body: JSON.stringify({
295
+ entityId,
296
+ purgeFirst: action === 'purge',
297
+ }),
298
+ }, { errorMessage })
301
299
  } catch (err) {
302
300
  console.error('query_index.table.vectorAction', err)
303
301
  if (typeof window !== 'undefined') {
@@ -3,6 +3,7 @@ import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'
3
3
  import { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'
4
4
  import { reindexEntity } from '../lib/reindexer'
5
5
  import type { VectorIndexService } from '@open-mercato/search/vector'
6
+ import type { ProgressService } from '@open-mercato/core/modules/progress/lib/progressService'
6
7
 
7
8
  export const metadata = { event: 'query_index.reindex', persistent: true }
8
9
 
@@ -25,6 +26,93 @@ export default async function handle(payload: any, ctx: { resolve: <T=any>(name:
25
26
  const partitionCount = Number.isFinite(payload?.partitionCount) ? Math.max(1, Math.trunc(payload.partitionCount)) : undefined
26
27
  const partitionIndex = Number.isFinite(payload?.partitionIndex) ? Math.max(0, Math.trunc(payload.partitionIndex)) : undefined
27
28
  const resetCoverage = typeof payload?.resetCoverage === 'boolean' ? payload.resetCoverage : undefined
29
+ const requestedByUserId = typeof payload?.requestedByUserId === 'string' ? payload.requestedByUserId : null
30
+
31
+ const progressTenantId = typeof tenantId === 'string' && tenantId.length > 0 ? tenantId : null
32
+ const progressOrganizationId = typeof organizationId === 'string' && organizationId.length > 0 ? organizationId : null
33
+ const progressPartitionIndex = Number.isFinite(partitionIndex) ? partitionIndex : null
34
+ const progressPartitionCount = Number.isFinite(partitionCount) ? partitionCount : null
35
+ let progressService: ProgressService | null = null
36
+ let progressJobId: string | null = null
37
+ let progressEnabled = false
38
+ try {
39
+ progressService = ctx.resolve<ProgressService>('progressService')
40
+ progressEnabled = progressService != null && progressTenantId != null
41
+ } catch {
42
+ progressService = null
43
+ progressEnabled = false
44
+ }
45
+
46
+ const updateProgress = async (
47
+ info: { processed: number; total: number },
48
+ options?: { complete?: boolean; failed?: boolean; errorMessage?: string },
49
+ ): Promise<void> => {
50
+ if (!progressEnabled || !progressService || !progressTenantId) return
51
+ const progressCtx = {
52
+ tenantId: progressTenantId,
53
+ organizationId: progressOrganizationId,
54
+ userId: requestedByUserId,
55
+ }
56
+ const totalCount = Number.isFinite(info.total) ? Math.max(0, info.total) : 0
57
+ const processedCount = Number.isFinite(info.processed) ? Math.max(0, info.processed) : 0
58
+ const progressPercent = totalCount > 0 ? Math.min(100, Math.round((processedCount / totalCount) * 100)) : 0
59
+ try {
60
+ if (!progressJobId) {
61
+ const created = await progressService.createJob({
62
+ jobType: 'query_index.reindex',
63
+ name: `Query index reindex: ${entityType}`,
64
+ description: progressPartitionCount && progressPartitionCount > 1
65
+ ? `Partition ${((progressPartitionIndex ?? 0) + 1).toString()} of ${progressPartitionCount.toString()}`
66
+ : undefined,
67
+ totalCount: totalCount > 0 ? totalCount : undefined,
68
+ cancellable: false,
69
+ meta: {
70
+ entityType,
71
+ partitionIndex: progressPartitionIndex,
72
+ partitionCount: progressPartitionCount,
73
+ },
74
+ partitionIndex: progressPartitionIndex ?? undefined,
75
+ partitionCount: progressPartitionCount ?? undefined,
76
+ }, progressCtx)
77
+ progressJobId = created.id
78
+ await progressService.startJob(progressJobId, progressCtx)
79
+ }
80
+
81
+ await progressService.updateProgress(
82
+ progressJobId,
83
+ {
84
+ processedCount,
85
+ totalCount: totalCount > 0 ? totalCount : undefined,
86
+ progressPercent,
87
+ },
88
+ progressCtx,
89
+ )
90
+
91
+ if (options?.complete) {
92
+ await progressService.completeJob(
93
+ progressJobId,
94
+ {
95
+ resultSummary: {
96
+ entityType,
97
+ processed: processedCount,
98
+ total: totalCount,
99
+ },
100
+ },
101
+ progressCtx,
102
+ )
103
+ } else if (options?.failed) {
104
+ await progressService.failJob(
105
+ progressJobId,
106
+ {
107
+ errorMessage: options.errorMessage ?? `Reindex failed for ${entityType}`,
108
+ },
109
+ progressCtx,
110
+ )
111
+ }
112
+ } catch {
113
+ // Never block query_index subscriber execution because of progress tracking.
114
+ }
115
+ }
28
116
 
29
117
  try {
30
118
  await recordIndexerLog(
@@ -57,7 +145,14 @@ export default async function handle(payload: any, ctx: { resolve: <T=any>(name:
57
145
  partitionIndex,
58
146
  resetCoverage,
59
147
  vectorService,
148
+ onProgress: (info) => {
149
+ void updateProgress(info)
150
+ },
60
151
  })
152
+ await updateProgress(
153
+ { processed: result.processed, total: result.total },
154
+ { complete: true },
155
+ )
61
156
  await recordIndexerLog(
62
157
  { em },
63
158
  {
@@ -76,6 +171,10 @@ export default async function handle(payload: any, ctx: { resolve: <T=any>(name:
76
171
  },
77
172
  )
78
173
  } catch (error) {
174
+ await updateProgress(
175
+ { processed: 0, total: 0 },
176
+ { failed: true, errorMessage: error instanceof Error ? error.message : String(error) },
177
+ )
79
178
  await recordIndexerLog(
80
179
  { em },
81
180
  {