@open-mercato/core 0.6.8-develop.6992.1.00c90fecff → 0.6.8-develop.6998.1.d2bc46c56c

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 (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/sync_run/index.js +2 -0
  3. package/dist/generated/entities/sync_run/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +1 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/auth/api/sidebar/preferences/route.js +2 -2
  7. package/dist/modules/auth/api/sidebar/preferences/route.js.map +1 -1
  8. package/dist/modules/auth/lib/backendChrome.js +3 -3
  9. package/dist/modules/auth/lib/backendChrome.js.map +1 -1
  10. package/dist/modules/auth/services/sidebarPreferencesService.js +7 -2
  11. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  12. package/dist/modules/data_sync/api/options.js +1 -0
  13. package/dist/modules/data_sync/api/options.js.map +2 -2
  14. package/dist/modules/data_sync/api/run.js +15 -1
  15. package/dist/modules/data_sync/api/run.js.map +2 -2
  16. package/dist/modules/data_sync/api/runs/[id]/retry.js +23 -1
  17. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  18. package/dist/modules/data_sync/api/runs/[id]/route.js +1 -0
  19. package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
  20. package/dist/modules/data_sync/backend/data-sync/page.js +60 -17
  21. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  22. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +34 -2
  23. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  24. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +21 -15
  25. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  26. package/dist/modules/data_sync/components/RunParameterFields.js +135 -0
  27. package/dist/modules/data_sync/components/RunParameterFields.js.map +7 -0
  28. package/dist/modules/data_sync/data/entities.js +3 -0
  29. package/dist/modules/data_sync/data/entities.js.map +2 -2
  30. package/dist/modules/data_sync/data/validators.js +4 -1
  31. package/dist/modules/data_sync/data/validators.js.map +2 -2
  32. package/dist/modules/data_sync/lib/run-parameters.js +108 -0
  33. package/dist/modules/data_sync/lib/run-parameters.js.map +7 -0
  34. package/dist/modules/data_sync/lib/start-run.js +1 -0
  35. package/dist/modules/data_sync/lib/start-run.js.map +2 -2
  36. package/dist/modules/data_sync/lib/sync-engine.js +4 -2
  37. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  38. package/dist/modules/data_sync/lib/sync-run-service.js +1 -0
  39. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  40. package/dist/modules/data_sync/migrations/Migration20260810120000.js +13 -0
  41. package/dist/modules/data_sync/migrations/Migration20260810120000.js.map +7 -0
  42. package/dist/modules/data_sync/workers/sync-scheduled.js +23 -2
  43. package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
  44. package/dist/modules/query_index/di.js +125 -48
  45. package/dist/modules/query_index/di.js.map +2 -2
  46. package/dist/modules/sales/api/documents/factory.js +39 -1
  47. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  48. package/dist/modules/sales/commands/documents.js +26 -5
  49. package/dist/modules/sales/commands/documents.js.map +2 -2
  50. package/generated/entities/sync_run/index.ts +1 -0
  51. package/generated/entity-fields-registry.ts +1 -0
  52. package/package.json +7 -7
  53. package/src/modules/auth/api/sidebar/preferences/route.ts +2 -2
  54. package/src/modules/auth/lib/backendChrome.tsx +2 -2
  55. package/src/modules/auth/services/sidebarPreferencesService.ts +22 -3
  56. package/src/modules/data_sync/AGENTS.md +83 -8
  57. package/src/modules/data_sync/api/options.ts +1 -0
  58. package/src/modules/data_sync/api/run.ts +17 -0
  59. package/src/modules/data_sync/api/runs/[id]/retry.ts +32 -1
  60. package/src/modules/data_sync/api/runs/[id]/route.ts +1 -0
  61. package/src/modules/data_sync/backend/data-sync/page.tsx +73 -17
  62. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +62 -2
  63. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +28 -15
  64. package/src/modules/data_sync/components/RunParameterFields.tsx +229 -0
  65. package/src/modules/data_sync/data/entities.ts +4 -1
  66. package/src/modules/data_sync/data/validators.ts +3 -0
  67. package/src/modules/data_sync/i18n/de.json +10 -0
  68. package/src/modules/data_sync/i18n/en.json +10 -0
  69. package/src/modules/data_sync/i18n/es.json +10 -0
  70. package/src/modules/data_sync/i18n/ko.json +10 -0
  71. package/src/modules/data_sync/i18n/pl.json +10 -0
  72. package/src/modules/data_sync/lib/adapter.ts +88 -0
  73. package/src/modules/data_sync/lib/run-parameters.ts +196 -0
  74. package/src/modules/data_sync/lib/start-run.ts +2 -0
  75. package/src/modules/data_sync/lib/sync-engine.ts +5 -1
  76. package/src/modules/data_sync/lib/sync-run-service.ts +2 -0
  77. package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +17 -1
  78. package/src/modules/data_sync/migrations/Migration20260810120000.ts +13 -0
  79. package/src/modules/data_sync/workers/sync-scheduled.ts +32 -1
  80. package/src/modules/query_index/di.ts +153 -56
  81. package/src/modules/sales/api/documents/factory.ts +62 -1
  82. package/src/modules/sales/commands/documents.ts +40 -4
  83. package/src/modules/sales/i18n/de.json +1 -0
  84. package/src/modules/sales/i18n/en.json +1 -0
  85. package/src/modules/sales/i18n/es.json +1 -0
  86. package/src/modules/sales/i18n/ko.json +1 -0
  87. package/src/modules/sales/i18n/pl.json +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/data_sync/workers/sync-scheduled.ts"],
4
- "sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { SyncSchedule } from '../data/entities'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { resolveAdapterForIntegration, resolveStartCursor } from '../lib/start-cursor'\n\ntype ScheduledSyncPayload = {\n scheduleId: string\n scope: {\n organizationId: string\n tenantId: string\n }\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'data-sync-scheduled',\n id: 'data-sync:scheduled',\n concurrency: 3,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(job: QueuedJob<ScheduledSyncPayload>, ctx: HandlerContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n const syncRunService = ctx.resolve<SyncRunService>('dataSyncRunService')\n const progressService = ctx.resolve<ProgressService>('progressService')\n const integrationStateService = ctx.resolve<IntegrationStateService>('integrationStateService')\n\n const schedule = await findOneWithDecryption(\n em,\n SyncSchedule,\n {\n id: job.payload.scheduleId,\n organizationId: job.payload.scope.organizationId,\n tenantId: job.payload.scope.tenantId,\n deletedAt: null,\n },\n undefined,\n job.payload.scope,\n )\n\n if (!schedule || !schedule.isEnabled) {\n return\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(schedule.integrationId, job.payload.scope)\n if (!integrationEnabled) {\n return\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n schedule.integrationId,\n schedule.entityType,\n schedule.direction,\n job.payload.scope,\n )\n if (overlap) {\n return\n }\n\n const cursor = schedule.fullSync\n ? null\n : await resolveStartCursor({\n syncRunService,\n adapter: resolveAdapterForIntegration(schedule.integrationId),\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n direction: schedule.direction,\n scope: job.payload.scope,\n })\n\n schedule.lastRunAt = new Date()\n await em.flush()\n\n await startDataSyncRun({\n syncRunService,\n progressService,\n scope: job.payload.scope,\n input: {\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n direction: schedule.direction,\n cursor,\n triggeredBy: 'scheduler',\n },\n })\n}\n"],
5
- "mappings": "AAEA,SAAS,6BAA6B;AAItC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,8BAA8B,0BAA0B;AAU1D,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAMA,eAAO,OAA8B,KAAsC,KAAoC;AAC7G,QAAM,KAAK,IAAI,QAAuB,IAAI;AAC1C,QAAM,iBAAiB,IAAI,QAAwB,oBAAoB;AACvE,QAAM,kBAAkB,IAAI,QAAyB,iBAAiB;AACtE,QAAM,0BAA0B,IAAI,QAAiC,yBAAyB;AAE9F,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,MACE,IAAI,IAAI,QAAQ;AAAA,MAChB,gBAAgB,IAAI,QAAQ,MAAM;AAAA,MAClC,UAAU,IAAI,QAAQ,MAAM;AAAA,MAC5B,WAAW;AAAA,IACb;AAAA,IACA;AAAA,IACA,IAAI,QAAQ;AAAA,EACd;AAEA,MAAI,CAAC,YAAY,CAAC,SAAS,WAAW;AACpC;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM,wBAAwB,UAAU,SAAS,eAAe,IAAI,QAAQ,KAAK;AAC5G,MAAI,CAAC,oBAAoB;AACvB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,eAAe;AAAA,IACnC,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,IAAI,QAAQ;AAAA,EACd;AACA,MAAI,SAAS;AACX;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,WACpB,OACA,MAAM,mBAAmB;AAAA,IACvB;AAAA,IACA,SAAS,6BAA6B,SAAS,aAAa;AAAA,IAC5D,eAAe,SAAS;AAAA,IACxB,YAAY,SAAS;AAAA,IACrB,WAAW,SAAS;AAAA,IACpB,OAAO,IAAI,QAAQ;AAAA,EACrB,CAAC;AAEL,WAAS,YAAY,oBAAI,KAAK;AAC9B,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,OAAO,IAAI,QAAQ;AAAA,IACnB,OAAO;AAAA,MACL,eAAe,SAAS;AAAA,MACxB,YAAY,SAAS;AAAA,MACrB,WAAW,SAAS;AAAA,MACpB;AAAA,MACA,aAAa;AAAA,IACf;AAAA,EACF,CAAC;AACH;",
4
+ "sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { SyncSchedule } from '../data/entities'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { resolveAdapterForIntegration, resolveStartCursor } from '../lib/start-cursor'\nimport { normalizeRunParameters } from '../lib/run-parameters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('data_sync').child({ component: 'sync-scheduled' })\n\ntype ScheduledSyncPayload = {\n scheduleId: string\n scope: {\n organizationId: string\n tenantId: string\n }\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'data-sync-scheduled',\n id: 'data-sync:scheduled',\n concurrency: 3,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(job: QueuedJob<ScheduledSyncPayload>, ctx: HandlerContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n const syncRunService = ctx.resolve<SyncRunService>('dataSyncRunService')\n const progressService = ctx.resolve<ProgressService>('progressService')\n const integrationStateService = ctx.resolve<IntegrationStateService>('integrationStateService')\n\n const schedule = await findOneWithDecryption(\n em,\n SyncSchedule,\n {\n id: job.payload.scheduleId,\n organizationId: job.payload.scope.organizationId,\n tenantId: job.payload.scope.tenantId,\n deletedAt: null,\n },\n undefined,\n job.payload.scope,\n )\n\n if (!schedule || !schedule.isEnabled) {\n return\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(schedule.integrationId, job.payload.scope)\n if (!integrationEnabled) {\n return\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n schedule.integrationId,\n schedule.entityType,\n schedule.direction,\n job.payload.scope,\n )\n if (overlap) {\n return\n }\n\n const adapter = resolveAdapterForIntegration(schedule.integrationId)\n\n const cursor = schedule.fullSync\n ? null\n : await resolveStartCursor({\n syncRunService,\n adapter,\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n direction: schedule.direction,\n scope: job.payload.scope,\n })\n\n // A schedule carries no parameter form, but the adapter's declared defaults\n // still apply: normalizing an empty input materializes exactly those, so a\n // scheduled run reaches the adapter with the same set a manual run would\n // rather than an empty object. A declaration whose own default is invalid\n // stops the run instead of handing over a half-applied set.\n const normalizedParameters = normalizeRunParameters(\n adapter?.runParameters,\n schedule.direction,\n null,\n schedule.entityType,\n )\n if (!normalizedParameters.ok) {\n logger.error('Scheduled run skipped: adapter declares invalid run parameter defaults', {\n scheduleId: schedule.id,\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n keys: normalizedParameters.errors.map((error) => error.key),\n })\n return\n }\n const parameters = Object.keys(normalizedParameters.values).length > 0\n ? normalizedParameters.values\n : null\n\n schedule.lastRunAt = new Date()\n await em.flush()\n\n await startDataSyncRun({\n syncRunService,\n progressService,\n scope: job.payload.scope,\n input: {\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n direction: schedule.direction,\n cursor,\n triggeredBy: 'scheduler',\n parameters,\n },\n })\n}\n"],
5
+ "mappings": "AAEA,SAAS,6BAA6B;AAItC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,8BAA8B,0BAA0B;AACjE,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,iBAAiB,CAAC;AAUvE,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAMA,eAAO,OAA8B,KAAsC,KAAoC;AAC7G,QAAM,KAAK,IAAI,QAAuB,IAAI;AAC1C,QAAM,iBAAiB,IAAI,QAAwB,oBAAoB;AACvE,QAAM,kBAAkB,IAAI,QAAyB,iBAAiB;AACtE,QAAM,0BAA0B,IAAI,QAAiC,yBAAyB;AAE9F,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,MACE,IAAI,IAAI,QAAQ;AAAA,MAChB,gBAAgB,IAAI,QAAQ,MAAM;AAAA,MAClC,UAAU,IAAI,QAAQ,MAAM;AAAA,MAC5B,WAAW;AAAA,IACb;AAAA,IACA;AAAA,IACA,IAAI,QAAQ;AAAA,EACd;AAEA,MAAI,CAAC,YAAY,CAAC,SAAS,WAAW;AACpC;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM,wBAAwB,UAAU,SAAS,eAAe,IAAI,QAAQ,KAAK;AAC5G,MAAI,CAAC,oBAAoB;AACvB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,eAAe;AAAA,IACnC,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,IAAI,QAAQ;AAAA,EACd;AACA,MAAI,SAAS;AACX;AAAA,EACF;AAEA,QAAM,UAAU,6BAA6B,SAAS,aAAa;AAEnE,QAAM,SAAS,SAAS,WACpB,OACA,MAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,eAAe,SAAS;AAAA,IACxB,YAAY,SAAS;AAAA,IACrB,WAAW,SAAS;AAAA,IACpB,OAAO,IAAI,QAAQ;AAAA,EACrB,CAAC;AAOL,QAAM,uBAAuB;AAAA,IAC3B,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA,EACX;AACA,MAAI,CAAC,qBAAqB,IAAI;AAC5B,WAAO,MAAM,0EAA0E;AAAA,MACrF,YAAY,SAAS;AAAA,MACrB,eAAe,SAAS;AAAA,MACxB,YAAY,SAAS;AAAA,MACrB,MAAM,qBAAqB,OAAO,IAAI,CAAC,UAAU,MAAM,GAAG;AAAA,IAC5D,CAAC;AACD;AAAA,EACF;AACA,QAAM,aAAa,OAAO,KAAK,qBAAqB,MAAM,EAAE,SAAS,IACjE,qBAAqB,SACrB;AAEJ,WAAS,YAAY,oBAAI,KAAK;AAC9B,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,OAAO,IAAI,QAAQ;AAAA,IACnB,OAAO;AAAA,MACL,eAAe,SAAS;AAAA,MACxB,YAAY,SAAS;AAAA,MACrB,WAAW,SAAS;AAAA,MACpB;AAAA,MACA,aAAa;AAAA,MACb;AAAA,IACF;AAAA,EACF,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -1,11 +1,85 @@
1
- import { BasicQueryEngine, resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
2
- import { HybridQueryEngine } from "./lib/engine.js";
3
1
  import { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from "@open-mercato/shared/lib/crud/types";
4
- function toEntityTypeFromEvent(event) {
5
- const parts = event.split(".");
6
- if (parts.length !== 3) return null;
7
- const [mod, ent] = parts;
8
- return `${mod}:${ent}`;
2
+ import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
3
+ import { createLogger } from "@open-mercato/shared/lib/logger";
4
+ import { BasicQueryEngine } from "@open-mercato/shared/lib/query/engine";
5
+ import { HybridQueryEngine } from "./lib/engine.js";
6
+ import {
7
+ loadQueryIndexRowScope,
8
+ QueryIndexScopeError,
9
+ resolveQueryIndexRecordScope,
10
+ resolveQueryIndexSourceMetadata
11
+ } from "./lib/subscriber-scope.js";
12
+ const logger = createLogger("query_index").child({ component: "crud-bridge" });
13
+ function hasOwn(value, key) {
14
+ return !!value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key);
15
+ }
16
+ function readScopeValue(source, keys) {
17
+ const sourceRecord = source && typeof source === "object" ? source : {};
18
+ const key = keys.find((candidate) => hasOwn(source, candidate));
19
+ if (!key) return { value: void 0, present: false };
20
+ const value = sourceRecord[key];
21
+ if (value === null) return { value: null, present: true };
22
+ if (typeof value === "string" && value.trim().length > 0) return { value, present: true };
23
+ return { value: void 0, present: false };
24
+ }
25
+ async function resolveBridgeRecordScope(em, entityType, recordId, payload, action, options) {
26
+ let organization = readScopeValue(payload, ["organizationId", "orgId"]);
27
+ let tenant = readScopeValue(payload, ["tenantId"]);
28
+ if (organization.present && tenant.present && options?.validateCompletePayload !== true) {
29
+ return {
30
+ organizationId: organization.value ?? null,
31
+ tenantId: tenant.value ?? null,
32
+ sourceValidated: false
33
+ };
34
+ }
35
+ const source = resolveQueryIndexSourceMetadata(em, entityType);
36
+ const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
37
+ if (sourceScope.kind === "global") {
38
+ if (!organization.present) organization = { value: null, present: true };
39
+ if (!tenant.present) tenant = { value: null, present: true };
40
+ } else if (sourceScope.kind === "missing" && action === "upsert") {
41
+ throw new QueryIndexScopeError(
42
+ "Query index upsert event source row scope could not be resolved"
43
+ );
44
+ }
45
+ const resolvedScope = resolveQueryIndexRecordScope({
46
+ payloadOrganizationId: organization.value,
47
+ payloadTenantId: tenant.value,
48
+ hasPayloadOrganizationId: organization.present,
49
+ hasPayloadTenantId: tenant.present,
50
+ sourceScope
51
+ });
52
+ return { ...resolvedScope, sourceValidated: true };
53
+ }
54
+ async function recordBridgeError(input) {
55
+ const handler = `event:query_index.crud_bridge.${input.handler}`;
56
+ if (!input.em) {
57
+ logger.error("CRUD bridge event failed before the entity manager was resolved", {
58
+ handler,
59
+ entityType: input.entityType,
60
+ recordId: input.recordId,
61
+ err: input.error
62
+ });
63
+ return;
64
+ }
65
+ await recordIndexerError(
66
+ { em: input.em },
67
+ {
68
+ source: "query_index",
69
+ handler,
70
+ error: input.error,
71
+ entityType: input.entityType,
72
+ recordId: input.recordId,
73
+ payload: input.payload
74
+ }
75
+ ).catch((loggingError) => {
76
+ logger.error("Failed to record CRUD bridge error", {
77
+ handler,
78
+ entityType: input.entityType,
79
+ recordId: input.recordId,
80
+ err: loggingError
81
+ });
82
+ });
9
83
  }
10
84
  function register(container) {
11
85
  try {
@@ -61,23 +135,21 @@ function register(container) {
61
135
  return;
62
136
  }
63
137
  const makeUpsertHandler = (entityType) => async (payload, ctx) => {
138
+ let em = null;
139
+ let id = "";
64
140
  try {
65
141
  if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return;
66
- const em = ctx.resolve("em");
67
- let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null;
68
- let tenantId = payload?.tenantId || ctx?.tenantId || null;
69
- const id = String(payload?.id || payload?.recordId || "");
142
+ em = ctx.resolve("em");
143
+ id = String(payload?.id || payload?.recordId || "");
70
144
  if (!id) return;
71
- if (!orgId || !tenantId) {
72
- try {
73
- const db = em.getKysely();
74
- const table = resolveEntityTableName(em, entityType);
75
- const row = await db.selectFrom(table).select(["organization_id", "tenant_id"]).where("id", "=", id).executeTakeFirst();
76
- orgId = row?.organization_id ?? orgId;
77
- tenantId = row?.tenant_id ?? tenantId;
78
- } catch {
79
- }
80
- }
145
+ const { organizationId: orgId, tenantId, sourceValidated } = await resolveBridgeRecordScope(
146
+ em,
147
+ entityType,
148
+ id,
149
+ payload,
150
+ "upsert"
151
+ );
152
+ let hasCustomFields = null;
81
153
  try {
82
154
  const db = em.getKysely();
83
155
  let cfQuery = db.selectFrom("custom_field_defs").select(["id"]).where("entity_id", "=", entityType).where("is_active", "=", true);
@@ -97,42 +169,47 @@ function register(container) {
97
169
  } else {
98
170
  cfQuery = cfQuery.where("tenant_id", "is", null);
99
171
  }
100
- const hasCf = await cfQuery.executeTakeFirst();
101
- if (!hasCf) return;
172
+ hasCustomFields = !!await cfQuery.executeTakeFirst();
102
173
  } catch {
103
174
  }
104
- try {
105
- const bus2 = ctx.resolve("eventBus");
106
- await bus2.emitEvent("query_index.upsert_one", { entityType, recordId: id, organizationId: orgId, tenantId });
107
- } catch {
175
+ if (hasCustomFields === false) {
176
+ if (!sourceValidated) {
177
+ await resolveBridgeRecordScope(
178
+ em,
179
+ entityType,
180
+ id,
181
+ payload,
182
+ "upsert",
183
+ { validateCompletePayload: true }
184
+ );
185
+ }
186
+ return;
108
187
  }
109
- } catch {
188
+ const bus2 = ctx.resolve("eventBus");
189
+ await bus2.emitEvent("query_index.upsert_one", { entityType, recordId: id, organizationId: orgId, tenantId });
190
+ } catch (error) {
191
+ await recordBridgeError({ em, handler: "upsert", error, entityType, recordId: id, payload });
110
192
  }
111
193
  };
112
194
  const makeDeleteHandler = (entityType) => async (payload, ctx) => {
195
+ let em = null;
196
+ let id = "";
113
197
  try {
114
198
  if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return;
115
- const em = ctx.resolve("em");
116
- let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null;
117
- let tenantId = payload?.tenantId || ctx?.tenantId || null;
118
- const id = String(payload?.id || payload?.recordId || "");
199
+ em = ctx.resolve("em");
200
+ id = String(payload?.id || payload?.recordId || "");
119
201
  if (!id) return;
120
- if (!orgId || !tenantId) {
121
- try {
122
- const db = em.getKysely();
123
- const table = resolveEntityTableName(em, entityType);
124
- const row = await db.selectFrom(table).select(["organization_id", "tenant_id"]).where("id", "=", id).executeTakeFirst();
125
- orgId = row?.organization_id ?? orgId;
126
- tenantId = row?.tenant_id ?? tenantId;
127
- } catch {
128
- }
129
- }
130
- try {
131
- const bus2 = ctx.resolve("eventBus");
132
- await bus2.emitEvent("query_index.delete_one", { entityType, recordId: id, organizationId: orgId, tenantId });
133
- } catch {
134
- }
135
- } catch {
202
+ const { organizationId: orgId, tenantId } = await resolveBridgeRecordScope(
203
+ em,
204
+ entityType,
205
+ id,
206
+ payload,
207
+ "delete"
208
+ );
209
+ const bus2 = ctx.resolve("eventBus");
210
+ await bus2.emitEvent("query_index.delete_one", { entityType, recordId: id, organizationId: orgId, tenantId });
211
+ } catch (error) {
212
+ await recordBridgeError({ em, handler: "delete", error, entityType, recordId: id, payload });
136
213
  }
137
214
  };
138
215
  try {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/query_index/di.ts"],
4
- "sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { BasicQueryEngine, resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport { markDeleted } from './lib/indexer'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from '@open-mercato/shared/lib/crud/types'\n\nfunction toEntityTypeFromEvent(event: string): string | null {\n // Expect '<module>.<entity>.<action>'\n const parts = event.split('.')\n if (parts.length !== 3) return null\n const [mod, ent] = parts\n return `${mod}:${ent}`\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n // DataEngine emits the canonical query_index.upsert_one itself. The\n // bridge only covers domain events from write paths that do not own an\n // indexer, otherwise failures and error logs are duplicated.\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n // Optional: only index when custom field definitions exist for this entity (org/global)\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n const hasCf = await cfQuery.executeTakeFirst()\n if (!hasCf) return\n } catch {}\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
5
- "mappings": "AACA,SAAS,kBAAkB,8BAA8B;AACzD,SAAS,yBAAyB;AAIlC,SAAS,4CAA4C;AAErD,SAAS,sBAAsB,OAA8B;AAE3D,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,KAAK,GAAG,IAAI;AACnB,SAAO,GAAG,GAAG,IAAI,GAAG;AACtB;AAEO,SAAS,SAAS,WAAyB;AAEhD,MAAI;AACF,UAAM,KAAM,UAAU,QAAQ,IAAI;AAClC,UAAM,QAAQ,IAAI;AAAA,MAChB;AAAA,MACA;AAAA,MACA,MAAM;AACJ,YAAI;AACF,iBAAO,UAAU,QAAQ,yBAAyB;AAAA,QACpD,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,IAAI;AAAA,MACjB;AAAA,MACA;AAAA,MACA,MAAM;AACJ,YAAI;AACF,iBAAQ,UAAU,QAAQ,UAAU;AAAA,QACtC,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,MAAM;AACJ,YAAI;AACF,iBAAQ,UAAU,QAAQ,oBAAoB;AAAA,QAChD,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,MAAM;AACJ,YAAI;AACF,iBAAO,UAAU,QAAQ,yBAAyB;AAAA,QACpD,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAEC,IAAC,UAAkB,SAAS,EAAE,aAAa,EAAE,SAAS,MAAM,OAAO,EAAE,CAAC;AAAA,EACzE,QAAQ;AAAA,EAAC;AAGT,QAAM,QAAQ,MAAM;AAClB,QAAI;AACJ,QAAI;AAAE,YAAO,UAAU,QAAQ,UAAU;AAAA,IAAU,QAAQ;AAAE,YAAM;AAAA,IAAK;AACxE,QAAI,CAAC,KAAK;AAAE,iBAAW,OAAO,CAAC;AAAG;AAAA,IAAO;AAEzC,UAAM,oBAAoB,CAAC,eAAuB,OAAO,SAAc,QAAa;AAClF,UAAI;AAIF,YAAI,UAAU,oCAAoC,MAAM,KAAM;AAC9D,cAAM,KAAK,IAAI,QAAQ,IAAI;AAC3B,YAAI,QAAQ,SAAS,kBAAkB,SAAS,SAAS,KAAK,kBAAkB;AAChF,YAAI,WAAW,SAAS,YAAY,KAAK,YAAY;AACrD,cAAM,KAAK,OAAO,SAAS,MAAM,SAAS,YAAY,EAAE;AACxD,YAAI,CAAC,GAAI;AACT,YAAI,CAAC,SAAS,CAAC,UAAU;AACvB,cAAI;AACF,kBAAM,KAAM,GAAW,UAAU;AACjC,kBAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,kBAAM,MAAM,MAAM,GACf,WAAW,KAAY,EACvB,OAAO,CAAC,mBAA0B,WAAkB,CAAC,EACrD,MAAM,MAAa,KAAK,EAAE,EAC1B,iBAAiB;AACpB,oBAAQ,KAAK,mBAAmB;AAChC,uBAAW,KAAK,aAAa;AAAA,UAC/B,QAAQ;AAAA,UAAC;AAAA,QACX;AAEA,YAAI;AACF,gBAAM,KAAM,GAAW,UAAU;AACjC,cAAI,UAAU,GACX,WAAW,mBAA0B,EACrC,OAAO,CAAC,IAAW,CAAC,EACpB,MAAM,aAAoB,KAAK,UAAU,EACzC,MAAM,aAAoB,KAAK,IAAI;AACtC,cAAI,SAAS,MAAM;AACjB,sBAAU,QAAQ,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,cACzC,GAAG,mBAA0B,KAAK,KAAK;AAAA,cACvC,GAAG,mBAA0B,MAAM,IAAI;AAAA,YACzC,CAAC,CAAC;AAAA,UACJ,OAAO;AACL,sBAAU,QAAQ,MAAM,mBAA0B,MAAM,IAAW;AAAA,UACrE;AACA,cAAI,YAAY,MAAM;AACpB,sBAAU,QAAQ,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,cACzC,GAAG,aAAoB,KAAK,QAAQ;AAAA,cACpC,GAAG,aAAoB,MAAM,IAAI;AAAA,YACnC,CAAC,CAAC;AAAA,UACJ,OAAO;AACL,sBAAU,QAAQ,MAAM,aAAoB,MAAM,IAAW;AAAA,UAC/D;AACA,gBAAM,QAAQ,MAAM,QAAQ,iBAAiB;AAC7C,cAAI,CAAC,MAAO;AAAA,QACd,QAAQ;AAAA,QAAC;AACT,YAAI;AACF,gBAAMA,OAAM,IAAI,QAAQ,UAAU;AAClC,gBAAMA,KAAI,UAAU,0BAA0B,EAAE,YAAY,UAAU,IAAI,gBAAgB,OAAO,SAAS,CAAC;AAAA,QAC7G,QAAQ;AAAA,QAAC;AAAA,MACX,QAAQ;AAAA,MAAC;AAAA,IACX;AACA,UAAM,oBAAoB,CAAC,eAAuB,OAAO,SAAc,QAAa;AAClF,UAAI;AACF,YAAI,UAAU,oCAAoC,MAAM,KAAM;AAC9D,cAAM,KAAK,IAAI,QAAQ,IAAI;AAC3B,YAAI,QAAQ,SAAS,kBAAkB,SAAS,SAAS,KAAK,kBAAkB;AAChF,YAAI,WAAW,SAAS,YAAY,KAAK,YAAY;AACrD,cAAM,KAAK,OAAO,SAAS,MAAM,SAAS,YAAY,EAAE;AACxD,YAAI,CAAC,GAAI;AACT,YAAI,CAAC,SAAS,CAAC,UAAU;AACvB,cAAI;AACF,kBAAM,KAAM,GAAW,UAAU;AACjC,kBAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,kBAAM,MAAM,MAAM,GACf,WAAW,KAAY,EACvB,OAAO,CAAC,mBAA0B,WAAkB,CAAC,EACrD,MAAM,MAAa,KAAK,EAAE,EAC1B,iBAAiB;AACpB,oBAAQ,KAAK,mBAAmB;AAChC,uBAAW,KAAK,aAAa;AAAA,UAC/B,QAAQ;AAAA,UAAC;AAAA,QACX;AACA,YAAI;AACF,gBAAMA,OAAM,IAAI,QAAQ,UAAU;AAClC,gBAAMA,KAAI,UAAU,0BAA0B,EAAE,YAAY,UAAU,IAAI,gBAAgB,OAAO,SAAS,CAAC;AAAA,QAC7G,QAAQ;AAAA,QAAC;AAAA,MACX,QAAQ;AAAA,MAAC;AAAA,IACX;AAGA,QAAI;AACF,YAAM,KAAM,UAAU,QAAQ,IAAI;AAClC,YAAM,KAAM,GAAW,UAAU;AACjC,YAAM,cAAwB,CAAC;AAC/B,SACG,WAAW,mBAA0B,EACrC,OAAO,CAAC,WAAkB,CAAC,EAC3B,SAAS,EACT,QAAQ,EACR,KAAK,CAAC,SAAgB;AACrB,mBAAW,KAAK,QAAQ,CAAC,EAAG,aAAY,KAAK,OAAO,EAAE,SAAS,CAAC;AAAA,MAClE,CAAC,EACA,MAAM,MAAM;AAAA,MAAC,CAAC,EACd,QAAQ,MAAM;AACb,cAAM,UAAU,CAAC,QAAkB;AACjC,qBAAW,cAAc,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG;AACjD,kBAAM,CAAC,KAAK,GAAG,IAAI,WAAW,MAAM,GAAG;AACvC,gBAAI,CAAC,OAAO,CAAC,IAAK;AAClB,gBAAI,GAAG,GAAG,GAAG,IAAI,GAAG,YAAY,kBAAkB,UAAU,GAAG,EAAE,UAAU,cAAc,CAAC;AAC1F,gBAAI,GAAG,GAAG,GAAG,IAAI,GAAG,YAAY,kBAAkB,UAAU,GAAG,EAAE,UAAU,cAAc,CAAC;AAC1F,gBAAI,GAAG,GAAG,GAAG,IAAI,GAAG,YAAY,kBAAkB,UAAU,GAAG,EAAE,UAAU,cAAc,CAAC;AAAA,UAC5F;AAAA,QACF;AACA,YAAI,YAAY,SAAS,GAAG;AAC1B,kBAAQ,WAAW;AAAA,QACrB,OAAO;AAEP,iBAAO,mCAAmC,EAAE,KAAK,CAAC,SAAS;AACvD,kBAAM,UAAU,CAAC,MAAqB,OAAO,OAAO,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAW,OAAO,OAAO,KAAK,CAAC,CAAC,CAAa;AACnH,kBAAM,UAAU,oBAAI,IAAY,CAAC,GAAG,QAAS,KAAa,CAAC,CAAC,CAAC;AAC7D,oBAAQ,MAAM,KAAK,OAAO,CAAC;AAAA,UAC7B,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACL,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,MAAI;AAAE,UAAM;AAAA,EAAE,QAAQ;AAAA,EAAC;AACzB;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from '@open-mercato/shared/lib/crud/types'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { BasicQueryEngine } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport {\n loadQueryIndexRowScope,\n QueryIndexScopeError,\n resolveQueryIndexRecordScope,\n resolveQueryIndexSourceMetadata,\n} from './lib/subscriber-scope'\n\nconst logger = createLogger('query_index').child({ component: 'crud-bridge' })\n\nfunction hasOwn(value: unknown, key: string): boolean {\n return !!value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction readScopeValue(\n source: unknown,\n keys: string[],\n): { value: string | null | undefined; present: boolean } {\n const sourceRecord = source && typeof source === 'object' ? source as Record<string, unknown> : {}\n const key = keys.find((candidate) => hasOwn(source, candidate))\n if (!key) return { value: undefined, present: false }\n const value = sourceRecord[key]\n if (value === null) return { value: null, present: true }\n if (typeof value === 'string' && value.trim().length > 0) return { value, present: true }\n return { value: undefined, present: false }\n}\n\nasync function resolveBridgeRecordScope(\n em: EntityManager,\n entityType: string,\n recordId: string,\n payload: unknown,\n action: 'upsert' | 'delete',\n options?: { validateCompletePayload?: boolean },\n) {\n let organization = readScopeValue(payload, ['organizationId', 'orgId'])\n let tenant = readScopeValue(payload, ['tenantId'])\n if (organization.present && tenant.present && options?.validateCompletePayload !== true) {\n return {\n organizationId: organization.value ?? null,\n tenantId: tenant.value ?? null,\n sourceValidated: false,\n }\n }\n const source = resolveQueryIndexSourceMetadata(em, entityType)\n const sourceScope = await loadQueryIndexRowScope(em, source, recordId)\n\n if (sourceScope.kind === 'global') {\n if (!organization.present) organization = { value: null, present: true }\n if (!tenant.present) tenant = { value: null, present: true }\n } else if (sourceScope.kind === 'missing' && action === 'upsert') {\n throw new QueryIndexScopeError(\n 'Query index upsert event source row scope could not be resolved',\n )\n }\n\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: organization.value,\n payloadTenantId: tenant.value,\n hasPayloadOrganizationId: organization.present,\n hasPayloadTenantId: tenant.present,\n sourceScope,\n })\n return { ...resolvedScope, sourceValidated: true }\n}\n\nasync function recordBridgeError(input: {\n em: EntityManager | null\n handler: 'upsert' | 'delete'\n error: unknown\n entityType: string\n recordId: string\n payload: unknown\n}): Promise<void> {\n const handler = `event:query_index.crud_bridge.${input.handler}`\n if (!input.em) {\n logger.error('CRUD bridge event failed before the entity manager was resolved', {\n handler,\n entityType: input.entityType,\n recordId: input.recordId,\n err: input.error,\n })\n return\n }\n await recordIndexerError(\n { em: input.em },\n {\n source: 'query_index',\n handler,\n error: input.error,\n entityType: input.entityType,\n recordId: input.recordId,\n payload: input.payload,\n },\n ).catch((loggingError) => {\n logger.error('Failed to record CRUD bridge error', {\n handler,\n entityType: input.entityType,\n recordId: input.recordId,\n err: loggingError,\n })\n })\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n let em: EntityManager | null = null\n let id = ''\n try {\n // DataEngine emits the canonical query_index.upsert_one itself. The\n // bridge only covers domain events from write paths that do not own an\n // indexer, otherwise failures and error logs are duplicated.\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n em = ctx.resolve('em') as EntityManager\n id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n const { organizationId: orgId, tenantId, sourceValidated } = await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'upsert',\n )\n // Optional: only index when custom field definitions exist for this entity (org/global)\n let hasCustomFields: boolean | null = null\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n hasCustomFields = !!await cfQuery.executeTakeFirst()\n } catch {}\n if (hasCustomFields === false) {\n if (!sourceValidated) {\n await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'upsert',\n { validateCompletePayload: true },\n )\n }\n return\n }\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch (error) {\n await recordBridgeError({ em, handler: 'upsert', error, entityType, recordId: id, payload })\n }\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n let em: EntityManager | null = null\n let id = ''\n try {\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n em = ctx.resolve('em') as EntityManager\n id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n const { organizationId: orgId, tenantId } = await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'delete',\n )\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch (error) {\n await recordBridgeError({ em, handler: 'delete', error, entityType, recordId: id, payload })\n }\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["bus"]
7
7
  }
@@ -3,7 +3,8 @@ import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
3
3
  import { splitCustomFieldPayload, extractAllCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields";
4
4
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
5
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
- import { SalesDocumentTagAssignment } from "../../data/entities.js";
6
+ import { SalesChannel, SalesDocumentTagAssignment } from "../../data/entities.js";
7
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
7
8
  import {
8
9
  ORDER_PAYMENT_LEDGER_FIELDS,
9
10
  ORDER_PAYMENT_LEDGER_WARNING_CODE,
@@ -230,6 +231,39 @@ const attachTags = async (payload, ctx) => {
230
231
  if (list) item.tags = list;
231
232
  });
232
233
  };
234
+ const attachChannelNames = async (payload, ctx) => {
235
+ const items = Array.isArray(payload?.items) ? payload.items : [];
236
+ if (!items.length) return;
237
+ const channelIds = Array.from(
238
+ new Set(
239
+ items.map((item) => item && typeof item.channelId === "string" ? item.channelId : null).filter((value) => !!value)
240
+ )
241
+ );
242
+ if (!channelIds.length) return;
243
+ const em = ctx?.container?.resolve ? ctx.container.resolve("em") : null;
244
+ if (!em) return;
245
+ const where = { id: { $in: channelIds } };
246
+ if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId;
247
+ const orgIds = Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length ? ctx.organizationIds.filter((val) => !!val) : ctx?.selectedOrganizationId ? [ctx.selectedOrganizationId] : [];
248
+ if (orgIds.length) where.organizationId = { $in: orgIds };
249
+ const channels = await findWithDecryption(
250
+ em,
251
+ SalesChannel,
252
+ where,
253
+ { fields: ["id", "name", "code"] },
254
+ {
255
+ tenantId: ctx?.auth?.tenantId ?? null,
256
+ organizationId: ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null
257
+ }
258
+ );
259
+ const byId = new Map(channels.map((channel) => [channel.id, channel]));
260
+ items.forEach((item) => {
261
+ if (!item || typeof item.channelId !== "string") return;
262
+ const channel = byId.get(item.channelId);
263
+ item.channelName = channel?.name ?? null;
264
+ item.channelCode = channel?.code ?? null;
265
+ });
266
+ };
233
267
  async function ensureNumberEditPermission(ctx, translate) {
234
268
  const rbac = ctx.container?.resolve?.("rbacService");
235
269
  const auth = ctx.auth;
@@ -462,6 +496,7 @@ function buildDocumentCrudOptions(binding) {
462
496
  hooks: {
463
497
  afterList: async (payload, ctx) => {
464
498
  await attachTags(payload, { ...ctx, bindingKind: binding.kind });
499
+ await attachChannelNames(payload, ctx);
465
500
  if (binding.kind === "order" && Array.isArray(payload?.items) && payload.items.length === 1) {
466
501
  const item = payload.items[0];
467
502
  const orderId = typeof item?.id === "string" ? item.id : null;
@@ -527,6 +562,8 @@ function buildDocumentOpenApi(binding) {
527
562
  paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),
528
563
  currencyCode: z.string().nullable(),
529
564
  channelId: z.string().uuid().nullable(),
565
+ channelName: z.string().nullable().optional(),
566
+ channelCode: z.string().nullable().optional(),
530
567
  organizationId: z.string().uuid().nullable(),
531
568
  tenantId: z.string().uuid().nullable(),
532
569
  validFrom: z.string().nullable().optional(),
@@ -578,6 +615,7 @@ function createDocumentCrudRoute(binding) {
578
615
  return { GET, POST, PUT, DELETE, openApi: buildDocumentOpenApi(binding), metadata: crud.metadata };
579
616
  }
580
617
  export {
618
+ attachChannelNames,
581
619
  buildDocumentCrudOptions,
582
620
  buildDocumentOpenApi,
583
621
  createDocumentCrudRoute