@open-mercato/core 0.4.6-develop-6d72ec5960 → 0.4.6-develop-cd1e2a9a0e

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 (226) hide show
  1. package/AGENTS.md +10 -0
  2. package/dist/generated/entities/integration_credentials/index.js +19 -0
  3. package/dist/generated/entities/integration_credentials/index.js.map +7 -0
  4. package/dist/generated/entities/integration_log/index.js +27 -0
  5. package/dist/generated/entities/integration_log/index.js.map +7 -0
  6. package/dist/generated/entities/integration_state/index.js +27 -0
  7. package/dist/generated/entities/integration_state/index.js.map +7 -0
  8. package/dist/generated/entities/sync_cursor/index.js +19 -0
  9. package/dist/generated/entities/sync_cursor/index.js.map +7 -0
  10. package/dist/generated/entities/sync_external_id_mapping/index.js +27 -0
  11. package/dist/generated/entities/sync_external_id_mapping/index.js.map +7 -0
  12. package/dist/generated/entities/sync_mapping/index.js +19 -0
  13. package/dist/generated/entities/sync_mapping/index.js.map +7 -0
  14. package/dist/generated/entities/sync_run/index.js +45 -0
  15. package/dist/generated/entities/sync_run/index.js.map +7 -0
  16. package/dist/generated/entities/sync_schedule/index.js +35 -0
  17. package/dist/generated/entities/sync_schedule/index.js.map +7 -0
  18. package/dist/generated/entities.ids.generated.js +14 -0
  19. package/dist/generated/entities.ids.generated.js.map +2 -2
  20. package/dist/generated/entity-fields-registry.js +16 -0
  21. package/dist/generated/entity-fields-registry.js.map +2 -2
  22. package/dist/modules/data_sync/acl.js +11 -0
  23. package/dist/modules/data_sync/acl.js.map +7 -0
  24. package/dist/modules/data_sync/api/mappings/[id]/route.js +137 -0
  25. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +7 -0
  26. package/dist/modules/data_sync/api/mappings/route.js +132 -0
  27. package/dist/modules/data_sync/api/mappings/route.js.map +7 -0
  28. package/dist/modules/data_sync/api/run.js +87 -0
  29. package/dist/modules/data_sync/api/run.js.map +7 -0
  30. package/dist/modules/data_sync/api/runs/[id]/cancel.js +49 -0
  31. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +7 -0
  32. package/dist/modules/data_sync/api/runs/[id]/retry.js +93 -0
  33. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +7 -0
  34. package/dist/modules/data_sync/api/runs/[id]/route.js +69 -0
  35. package/dist/modules/data_sync/api/runs/[id]/route.js.map +7 -0
  36. package/dist/modules/data_sync/api/runs.js +66 -0
  37. package/dist/modules/data_sync/api/runs.js.map +7 -0
  38. package/dist/modules/data_sync/api/validate.js +66 -0
  39. package/dist/modules/data_sync/api/validate.js.map +7 -0
  40. package/dist/modules/data_sync/backend/data-sync/page.js +216 -0
  41. package/dist/modules/data_sync/backend/data-sync/page.js.map +7 -0
  42. package/dist/modules/data_sync/backend/data-sync/page.meta.js +25 -0
  43. package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +7 -0
  44. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +178 -0
  45. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +7 -0
  46. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js +14 -0
  47. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js.map +7 -0
  48. package/dist/modules/data_sync/data/entities.js +228 -0
  49. package/dist/modules/data_sync/data/entities.js.map +7 -0
  50. package/dist/modules/data_sync/data/validators.js +32 -0
  51. package/dist/modules/data_sync/data/validators.js.map +7 -0
  52. package/dist/modules/data_sync/di.js +26 -0
  53. package/dist/modules/data_sync/di.js.map +7 -0
  54. package/dist/modules/data_sync/events.js +16 -0
  55. package/dist/modules/data_sync/events.js.map +7 -0
  56. package/dist/modules/data_sync/index.js +9 -0
  57. package/dist/modules/data_sync/index.js.map +7 -0
  58. package/dist/modules/data_sync/lib/adapter-registry.js +16 -0
  59. package/dist/modules/data_sync/lib/adapter-registry.js.map +7 -0
  60. package/dist/modules/data_sync/lib/adapter.js +1 -0
  61. package/dist/modules/data_sync/lib/adapter.js.map +7 -0
  62. package/dist/modules/data_sync/lib/id-mapping.js +79 -0
  63. package/dist/modules/data_sync/lib/id-mapping.js.map +7 -0
  64. package/dist/modules/data_sync/lib/queue.js +17 -0
  65. package/dist/modules/data_sync/lib/queue.js.map +7 -0
  66. package/dist/modules/data_sync/lib/sync-engine.js +309 -0
  67. package/dist/modules/data_sync/lib/sync-engine.js.map +7 -0
  68. package/dist/modules/data_sync/lib/sync-run-service.js +148 -0
  69. package/dist/modules/data_sync/lib/sync-run-service.js.map +7 -0
  70. package/dist/modules/data_sync/migrations/Migration20260304113737.js +17 -0
  71. package/dist/modules/data_sync/migrations/Migration20260304113737.js.map +7 -0
  72. package/dist/modules/data_sync/setup.js +13 -0
  73. package/dist/modules/data_sync/setup.js.map +7 -0
  74. package/dist/modules/data_sync/workers/sync-export.js +14 -0
  75. package/dist/modules/data_sync/workers/sync-export.js.map +7 -0
  76. package/dist/modules/data_sync/workers/sync-import.js +14 -0
  77. package/dist/modules/data_sync/workers/sync-import.js.map +7 -0
  78. package/dist/modules/data_sync/workers/sync-scheduled.js +63 -0
  79. package/dist/modules/data_sync/workers/sync-scheduled.js.map +7 -0
  80. package/dist/modules/entities/lib/encryptionDefaults.js +4 -0
  81. package/dist/modules/entities/lib/encryptionDefaults.js.map +2 -2
  82. package/dist/modules/integrations/acl.js +4 -1
  83. package/dist/modules/integrations/acl.js.map +2 -2
  84. package/dist/modules/integrations/api/[id]/credentials/route.js +127 -0
  85. package/dist/modules/integrations/api/[id]/credentials/route.js.map +7 -0
  86. package/dist/modules/integrations/api/[id]/health/route.js +46 -0
  87. package/dist/modules/integrations/api/[id]/health/route.js.map +7 -0
  88. package/dist/modules/integrations/api/[id]/route.js +65 -0
  89. package/dist/modules/integrations/api/[id]/route.js.map +7 -0
  90. package/dist/modules/integrations/api/[id]/state/route.js +109 -0
  91. package/dist/modules/integrations/api/[id]/state/route.js.map +7 -0
  92. package/dist/modules/integrations/api/[id]/version/route.js +117 -0
  93. package/dist/modules/integrations/api/[id]/version/route.js.map +7 -0
  94. package/dist/modules/integrations/api/guards.js +31 -0
  95. package/dist/modules/integrations/api/guards.js.map +7 -0
  96. package/dist/modules/integrations/api/logs/route.js +60 -0
  97. package/dist/modules/integrations/api/logs/route.js.map +7 -0
  98. package/dist/modules/integrations/api/openapi.js +25 -0
  99. package/dist/modules/integrations/api/openapi.js.map +7 -0
  100. package/dist/modules/integrations/api/route.js +68 -0
  101. package/dist/modules/integrations/api/route.js.map +7 -0
  102. package/dist/modules/integrations/backend/integrations/[id]/page.js +313 -0
  103. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +7 -0
  104. package/dist/modules/integrations/backend/integrations/[id]/page.meta.js +15 -0
  105. package/dist/modules/integrations/backend/integrations/[id]/page.meta.js.map +7 -0
  106. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +189 -0
  107. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +7 -0
  108. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.meta.js +15 -0
  109. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.meta.js.map +7 -0
  110. package/dist/modules/integrations/backend/integrations/page.js +212 -0
  111. package/dist/modules/integrations/backend/integrations/page.js.map +7 -0
  112. package/dist/modules/integrations/backend/integrations/page.meta.js +22 -0
  113. package/dist/modules/integrations/backend/integrations/page.meta.js.map +7 -0
  114. package/dist/modules/integrations/data/enrichers.js +27 -12
  115. package/dist/modules/integrations/data/enrichers.js.map +2 -2
  116. package/dist/modules/integrations/data/entities.js +136 -1
  117. package/dist/modules/integrations/data/entities.js.map +2 -2
  118. package/dist/modules/integrations/data/validators.js +36 -0
  119. package/dist/modules/integrations/data/validators.js.map +7 -0
  120. package/dist/modules/integrations/di.js +24 -0
  121. package/dist/modules/integrations/di.js.map +7 -0
  122. package/dist/modules/integrations/events.js +19 -0
  123. package/dist/modules/integrations/events.js.map +7 -0
  124. package/dist/modules/integrations/lib/credentials-service.js +159 -0
  125. package/dist/modules/integrations/lib/credentials-service.js.map +7 -0
  126. package/dist/modules/integrations/lib/health-service.js +37 -0
  127. package/dist/modules/integrations/lib/health-service.js.map +7 -0
  128. package/dist/modules/integrations/lib/log-service.js +66 -0
  129. package/dist/modules/integrations/lib/log-service.js.map +7 -0
  130. package/dist/modules/integrations/lib/registry-service.js +33 -0
  131. package/dist/modules/integrations/lib/registry-service.js.map +7 -0
  132. package/dist/modules/integrations/lib/state-service.js +55 -0
  133. package/dist/modules/integrations/lib/state-service.js.map +7 -0
  134. package/dist/modules/integrations/lib/types.js +1 -0
  135. package/dist/modules/integrations/lib/types.js.map +7 -0
  136. package/dist/modules/integrations/migrations/Migration20260304113737.js +19 -0
  137. package/dist/modules/integrations/migrations/Migration20260304113737.js.map +7 -0
  138. package/dist/modules/integrations/setup.js +2 -2
  139. package/dist/modules/integrations/setup.js.map +2 -2
  140. package/dist/modules/integrations/widgets/injection-table.js.map +1 -1
  141. package/dist/modules/integrations/workers/log-pruner.js +18 -0
  142. package/dist/modules/integrations/workers/log-pruner.js.map +7 -0
  143. package/generated/entities/integration_credentials/index.ts +8 -0
  144. package/generated/entities/integration_log/index.ts +12 -0
  145. package/generated/entities/integration_state/index.ts +12 -0
  146. package/generated/entities/sync_cursor/index.ts +8 -0
  147. package/generated/entities/sync_external_id_mapping/index.ts +12 -0
  148. package/generated/entities/sync_mapping/index.ts +8 -0
  149. package/generated/entities/sync_run/index.ts +21 -0
  150. package/generated/entities/sync_schedule/index.ts +16 -0
  151. package/generated/entities.ids.generated.ts +14 -0
  152. package/generated/entity-fields-registry.ts +16 -0
  153. package/package.json +2 -2
  154. package/src/modules/data_sync/AGENTS.md +157 -0
  155. package/src/modules/data_sync/acl.ts +7 -0
  156. package/src/modules/data_sync/api/mappings/[id]/route.ts +158 -0
  157. package/src/modules/data_sync/api/mappings/route.ts +144 -0
  158. package/src/modules/data_sync/api/run.ts +97 -0
  159. package/src/modules/data_sync/api/runs/[id]/cancel.ts +57 -0
  160. package/src/modules/data_sync/api/runs/[id]/retry.ts +108 -0
  161. package/src/modules/data_sync/api/runs/[id]/route.ts +81 -0
  162. package/src/modules/data_sync/api/runs.ts +69 -0
  163. package/src/modules/data_sync/api/validate.ts +73 -0
  164. package/src/modules/data_sync/backend/data-sync/page.meta.ts +21 -0
  165. package/src/modules/data_sync/backend/data-sync/page.tsx +244 -0
  166. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.meta.ts +10 -0
  167. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +278 -0
  168. package/src/modules/data_sync/data/entities.ts +180 -0
  169. package/src/modules/data_sync/data/validators.ts +35 -0
  170. package/src/modules/data_sync/di.ts +38 -0
  171. package/src/modules/data_sync/events.ts +12 -0
  172. package/src/modules/data_sync/i18n/de.json +48 -0
  173. package/src/modules/data_sync/i18n/en.json +48 -0
  174. package/src/modules/data_sync/i18n/es.json +48 -0
  175. package/src/modules/data_sync/i18n/pl.json +48 -0
  176. package/src/modules/data_sync/index.ts +5 -0
  177. package/src/modules/data_sync/lib/adapter-registry.ts +15 -0
  178. package/src/modules/data_sync/lib/adapter.ts +90 -0
  179. package/src/modules/data_sync/lib/id-mapping.ts +95 -0
  180. package/src/modules/data_sync/lib/queue.ts +19 -0
  181. package/src/modules/data_sync/lib/sync-engine.ts +375 -0
  182. package/src/modules/data_sync/lib/sync-run-service.ts +187 -0
  183. package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +653 -0
  184. package/src/modules/data_sync/migrations/Migration20260304113737.ts +19 -0
  185. package/src/modules/data_sync/setup.ts +11 -0
  186. package/src/modules/data_sync/workers/sync-export.ts +27 -0
  187. package/src/modules/data_sync/workers/sync-import.ts +27 -0
  188. package/src/modules/data_sync/workers/sync-scheduled.ts +84 -0
  189. package/src/modules/entities/lib/encryptionDefaults.ts +4 -0
  190. package/src/modules/integrations/AGENTS.md +160 -0
  191. package/src/modules/integrations/acl.ts +3 -0
  192. package/src/modules/integrations/api/[id]/credentials/route.ts +142 -0
  193. package/src/modules/integrations/api/[id]/health/route.ts +53 -0
  194. package/src/modules/integrations/api/[id]/route.ts +76 -0
  195. package/src/modules/integrations/api/[id]/state/route.ts +121 -0
  196. package/src/modules/integrations/api/[id]/version/route.ts +132 -0
  197. package/src/modules/integrations/api/guards.ts +59 -0
  198. package/src/modules/integrations/api/logs/route.ts +63 -0
  199. package/src/modules/integrations/api/openapi.ts +22 -0
  200. package/src/modules/integrations/api/route.ts +73 -0
  201. package/src/modules/integrations/backend/integrations/[id]/page.meta.ts +11 -0
  202. package/src/modules/integrations/backend/integrations/[id]/page.tsx +424 -0
  203. package/src/modules/integrations/backend/integrations/bundle/[id]/page.meta.ts +11 -0
  204. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +249 -0
  205. package/src/modules/integrations/backend/integrations/page.meta.ts +18 -0
  206. package/src/modules/integrations/backend/integrations/page.tsx +296 -0
  207. package/src/modules/integrations/data/enrichers.ts +35 -18
  208. package/src/modules/integrations/data/entities.ts +114 -5
  209. package/src/modules/integrations/data/validators.ts +41 -0
  210. package/src/modules/integrations/di.ts +31 -0
  211. package/src/modules/integrations/events.ts +17 -0
  212. package/src/modules/integrations/i18n/de.json +70 -0
  213. package/src/modules/integrations/i18n/en.json +70 -0
  214. package/src/modules/integrations/i18n/es.json +70 -0
  215. package/src/modules/integrations/i18n/pl.json +70 -0
  216. package/src/modules/integrations/lib/credentials-service.ts +204 -0
  217. package/src/modules/integrations/lib/health-service.ts +59 -0
  218. package/src/modules/integrations/lib/log-service.ts +84 -0
  219. package/src/modules/integrations/lib/registry-service.ts +42 -0
  220. package/src/modules/integrations/lib/state-service.ts +64 -0
  221. package/src/modules/integrations/lib/types.ts +4 -0
  222. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +582 -0
  223. package/src/modules/integrations/migrations/Migration20260304113737.ts +21 -0
  224. package/src/modules/integrations/setup.ts +2 -2
  225. package/src/modules/integrations/widgets/injection-table.ts +1 -1
  226. package/src/modules/integrations/workers/log-pruner.ts +30 -0
@@ -0,0 +1,180 @@
1
+ import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core'
2
+
3
+ @Entity({ tableName: 'sync_runs' })
4
+ @Index({ properties: ['integrationId', 'entityType', 'status', 'organizationId', 'tenantId'] })
5
+ export class SyncRun {
6
+ [OptionalProps]?: 'status' | 'cursor' | 'initialCursor' | 'createdCount' | 'updatedCount' | 'skippedCount' | 'failedCount' | 'batchesCompleted' | 'lastError' | 'progressJobId' | 'jobId' | 'triggeredBy' | 'createdAt' | 'updatedAt' | 'deletedAt'
7
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
8
+ id!: string
9
+
10
+ @Property({ name: 'integration_id', type: 'text' })
11
+ integrationId!: string
12
+
13
+ @Property({ name: 'entity_type', type: 'text' })
14
+ entityType!: string
15
+
16
+ @Property({ name: 'direction', type: 'text' })
17
+ direction!: 'import' | 'export'
18
+
19
+ @Property({ name: 'status', type: 'text' })
20
+ status!: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused'
21
+
22
+ @Property({ name: 'cursor', type: 'text', nullable: true })
23
+ cursor?: string | null
24
+
25
+ @Property({ name: 'initial_cursor', type: 'text', nullable: true })
26
+ initialCursor?: string | null
27
+
28
+ @Property({ name: 'created_count', type: 'int', default: 0 })
29
+ createdCount: number = 0
30
+
31
+ @Property({ name: 'updated_count', type: 'int', default: 0 })
32
+ updatedCount: number = 0
33
+
34
+ @Property({ name: 'skipped_count', type: 'int', default: 0 })
35
+ skippedCount: number = 0
36
+
37
+ @Property({ name: 'failed_count', type: 'int', default: 0 })
38
+ failedCount: number = 0
39
+
40
+ @Property({ name: 'batches_completed', type: 'int', default: 0 })
41
+ batchesCompleted: number = 0
42
+
43
+ @Property({ name: 'last_error', type: 'text', nullable: true })
44
+ lastError?: string | null
45
+
46
+ @Property({ name: 'progress_job_id', type: 'uuid', nullable: true })
47
+ progressJobId?: string | null
48
+
49
+ @Property({ name: 'job_id', type: 'text', nullable: true })
50
+ jobId?: string | null
51
+
52
+ @Property({ name: 'triggered_by', type: 'text', nullable: true })
53
+ triggeredBy?: string | null
54
+
55
+ @Property({ name: 'organization_id', type: 'uuid' })
56
+ organizationId!: string
57
+
58
+ @Property({ name: 'tenant_id', type: 'uuid' })
59
+ tenantId!: string
60
+
61
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
62
+ createdAt: Date = new Date()
63
+
64
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
65
+ updatedAt: Date = new Date()
66
+
67
+ @Property({ name: 'deleted_at', type: Date, nullable: true })
68
+ deletedAt?: Date | null
69
+ }
70
+
71
+ @Entity({ tableName: 'sync_cursors' })
72
+ @Index({ properties: ['integrationId', 'entityType', 'direction', 'organizationId', 'tenantId'], options: { unique: true } })
73
+ export class SyncCursor {
74
+ [OptionalProps]?: 'cursor' | 'updatedAt'
75
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
76
+ id!: string
77
+
78
+ @Property({ name: 'integration_id', type: 'text' })
79
+ integrationId!: string
80
+
81
+ @Property({ name: 'entity_type', type: 'text' })
82
+ entityType!: string
83
+
84
+ @Property({ name: 'direction', type: 'text' })
85
+ direction!: 'import' | 'export'
86
+
87
+ @Property({ name: 'cursor', type: 'text', nullable: true })
88
+ cursor?: string | null
89
+
90
+ @Property({ name: 'organization_id', type: 'uuid' })
91
+ organizationId!: string
92
+
93
+ @Property({ name: 'tenant_id', type: 'uuid' })
94
+ tenantId!: string
95
+
96
+ @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })
97
+ updatedAt: Date = new Date()
98
+ }
99
+
100
+ @Entity({ tableName: 'sync_mappings' })
101
+ @Index({ properties: ['integrationId', 'entityType', 'organizationId', 'tenantId'], options: { unique: true } })
102
+ export class SyncMapping {
103
+ [OptionalProps]?: 'createdAt' | 'updatedAt'
104
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
105
+ id!: string
106
+
107
+ @Property({ name: 'integration_id', type: 'text' })
108
+ integrationId!: string
109
+
110
+ @Property({ name: 'entity_type', type: 'text' })
111
+ entityType!: string
112
+
113
+ @Property({ name: 'mapping', type: 'json' })
114
+ mapping!: Record<string, unknown>
115
+
116
+ @Property({ name: 'organization_id', type: 'uuid' })
117
+ organizationId!: string
118
+
119
+ @Property({ name: 'tenant_id', type: 'uuid' })
120
+ tenantId!: string
121
+
122
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
123
+ createdAt: Date = new Date()
124
+
125
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
126
+ updatedAt: Date = new Date()
127
+ }
128
+
129
+ @Entity({ tableName: 'sync_schedules' })
130
+ @Index({ properties: ['integrationId', 'entityType', 'direction', 'organizationId', 'tenantId'] })
131
+ export class SyncSchedule {
132
+ [OptionalProps]?: 'timezone' | 'fullSync' | 'isEnabled' | 'scheduledJobId' | 'lastRunAt' | 'createdAt' | 'updatedAt' | 'deletedAt'
133
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
134
+ id!: string
135
+
136
+ @Property({ name: 'integration_id', type: 'text' })
137
+ integrationId!: string
138
+
139
+ @Property({ name: 'entity_type', type: 'text' })
140
+ entityType!: string
141
+
142
+ @Property({ name: 'direction', type: 'text' })
143
+ direction!: 'import' | 'export'
144
+
145
+ @Property({ name: 'schedule_type', type: 'text' })
146
+ scheduleType!: 'cron' | 'interval'
147
+
148
+ @Property({ name: 'schedule_value', type: 'text' })
149
+ scheduleValue!: string
150
+
151
+ @Property({ name: 'timezone', type: 'text', default: 'UTC' })
152
+ timezone: string = 'UTC'
153
+
154
+ @Property({ name: 'full_sync', type: 'boolean', default: false })
155
+ fullSync: boolean = false
156
+
157
+ @Property({ name: 'is_enabled', type: 'boolean', default: true })
158
+ isEnabled: boolean = true
159
+
160
+ @Property({ name: 'scheduled_job_id', type: 'uuid', nullable: true })
161
+ scheduledJobId?: string | null
162
+
163
+ @Property({ name: 'last_run_at', type: Date, nullable: true })
164
+ lastRunAt?: Date | null
165
+
166
+ @Property({ name: 'organization_id', type: 'uuid' })
167
+ organizationId!: string
168
+
169
+ @Property({ name: 'tenant_id', type: 'uuid' })
170
+ tenantId!: string
171
+
172
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
173
+ createdAt: Date = new Date()
174
+
175
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
176
+ updatedAt: Date = new Date()
177
+
178
+ @Property({ name: 'deleted_at', type: Date, nullable: true })
179
+ deletedAt?: Date | null
180
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod'
2
+
3
+ export const runSyncSchema = z.object({
4
+ integrationId: z.string().min(1),
5
+ entityType: z.string().min(1),
6
+ direction: z.enum(['import', 'export']),
7
+ fullSync: z.boolean().default(false),
8
+ batchSize: z.number().int().min(1).max(1000).default(100),
9
+ triggeredBy: z.string().optional(),
10
+ })
11
+
12
+ export type RunSyncInput = z.infer<typeof runSyncSchema>
13
+
14
+ export const retrySyncSchema = z.object({
15
+ fromBeginning: z.boolean().default(false),
16
+ })
17
+
18
+ export type RetrySyncInput = z.infer<typeof retrySyncSchema>
19
+
20
+ export const validateConnectionSchema = z.object({
21
+ integrationId: z.string().min(1),
22
+ entityType: z.string().min(1),
23
+ direction: z.enum(['import', 'export']),
24
+ })
25
+
26
+ export const listSyncRunsQuerySchema = z.object({
27
+ integrationId: z.string().optional(),
28
+ entityType: z.string().optional(),
29
+ direction: z.enum(['import', 'export']).optional(),
30
+ status: z.enum(['pending', 'running', 'completed', 'failed', 'cancelled', 'paused']).optional(),
31
+ page: z.coerce.number().int().min(1).default(1),
32
+ pageSize: z.coerce.number().int().min(1).max(100).default(20),
33
+ })
34
+
35
+ export type ListSyncRunsQuery = z.infer<typeof listSyncRunsQuerySchema>
@@ -0,0 +1,38 @@
1
+ import { asFunction, asValue } from 'awilix'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
4
+ import type { CredentialsService } from '../integrations/lib/credentials-service'
5
+ import type { IntegrationLogService } from '../integrations/lib/log-service'
6
+ import type { ProgressService } from '../progress/lib/progressService'
7
+ import { SyncCursor, SyncMapping, SyncRun, SyncSchedule } from './data/entities'
8
+ import { createExternalIdMappingService } from './lib/id-mapping'
9
+ import { createSyncRunService } from './lib/sync-run-service'
10
+ import { createSyncEngine } from './lib/sync-engine'
11
+
12
+ type Cradle = {
13
+ em: EntityManager
14
+ integrationCredentialsService: CredentialsService
15
+ integrationLogService: IntegrationLogService
16
+ progressService: ProgressService
17
+ }
18
+
19
+ export function register(container: AppContainer) {
20
+ container.register({
21
+ externalIdMappingService: asFunction(({ em }: Cradle) => createExternalIdMappingService(em)).scoped().proxy(),
22
+ dataSyncRunService: asFunction(({ em }: Cradle) => createSyncRunService(em)).scoped().proxy(),
23
+ dataSyncEngine: asFunction(({ em, dataSyncRunService, integrationCredentialsService, integrationLogService, progressService }: Cradle & {
24
+ dataSyncRunService: ReturnType<typeof createSyncRunService>
25
+ }) => createSyncEngine({
26
+ em,
27
+ syncRunService: dataSyncRunService,
28
+ integrationCredentialsService,
29
+ integrationLogService,
30
+ progressService,
31
+ })).scoped().proxy(),
32
+
33
+ SyncRun: asValue(SyncRun),
34
+ SyncCursor: asValue(SyncCursor),
35
+ SyncMapping: asValue(SyncMapping),
36
+ SyncSchedule: asValue(SyncSchedule),
37
+ })
38
+ }
@@ -0,0 +1,12 @@
1
+ import { createModuleEvents } from '@open-mercato/shared/modules/events'
2
+
3
+ const events = [
4
+ { id: 'data_sync.run.started', label: 'Sync Run Started', category: 'lifecycle', entity: 'run' },
5
+ { id: 'data_sync.run.completed', label: 'Sync Run Completed', category: 'lifecycle', entity: 'run' },
6
+ { id: 'data_sync.run.failed', label: 'Sync Run Failed', category: 'lifecycle', entity: 'run' },
7
+ { id: 'data_sync.run.cancelled', label: 'Sync Run Cancelled', category: 'lifecycle', entity: 'run' },
8
+ ] as const
9
+
10
+ export const eventsConfig = createModuleEvents({ moduleId: 'data_sync', events })
11
+ export const emitDataSyncEvent = eventsConfig.emit
12
+ export default eventsConfig
@@ -0,0 +1,48 @@
1
+ {
2
+ "data_sync.dashboard.actions.view": "Anzeigen",
3
+ "data_sync.dashboard.columns.created": "Erstellt",
4
+ "data_sync.dashboard.columns.createdAt": "Gestartet",
5
+ "data_sync.dashboard.columns.direction": "Richtung",
6
+ "data_sync.dashboard.columns.entityType": "Entitätstyp",
7
+ "data_sync.dashboard.columns.failed": "Fehlgeschlagen",
8
+ "data_sync.dashboard.columns.integration": "Integration",
9
+ "data_sync.dashboard.columns.status": "Status",
10
+ "data_sync.dashboard.columns.updated": "Aktualisiert",
11
+ "data_sync.dashboard.direction.export": "Export",
12
+ "data_sync.dashboard.direction.import": "Import",
13
+ "data_sync.dashboard.filters.allDirections": "Alle Richtungen",
14
+ "data_sync.dashboard.filters.allStatuses": "Alle Status",
15
+ "data_sync.dashboard.filters.status": "Status",
16
+ "data_sync.dashboard.loadError": "Synchronisierungsläufe konnten nicht geladen werden",
17
+ "data_sync.dashboard.noRuns": "Noch keine Synchronisierungsläufe",
18
+ "data_sync.dashboard.status.cancelled": "Abgebrochen",
19
+ "data_sync.dashboard.status.completed": "Abgeschlossen",
20
+ "data_sync.dashboard.status.failed": "Fehlgeschlagen",
21
+ "data_sync.dashboard.status.paused": "Pausiert",
22
+ "data_sync.dashboard.status.pending": "Ausstehend",
23
+ "data_sync.dashboard.status.running": "Läuft",
24
+ "data_sync.dashboard.title": "Synchronisierungsläufe",
25
+ "data_sync.nav.title": "Datensynchronisation",
26
+ "data_sync.runs.detail.back": "Zurück zu Läufen",
27
+ "data_sync.runs.detail.cancel": "Abbrechen",
28
+ "data_sync.runs.detail.cancelError": "Lauf konnte nicht abgebrochen werden",
29
+ "data_sync.runs.detail.cancelSuccess": "Lauf abgebrochen",
30
+ "data_sync.runs.detail.counters.created": "Erstellt",
31
+ "data_sync.runs.detail.counters.failed": "Fehlgeschlagen",
32
+ "data_sync.runs.detail.counters.skipped": "Übersprungen",
33
+ "data_sync.runs.detail.counters.updated": "Aktualisiert",
34
+ "data_sync.runs.detail.error": "Fehler",
35
+ "data_sync.runs.detail.loadError": "Lauf konnte nicht geladen werden",
36
+ "data_sync.runs.detail.logs": "Betriebsprotokolle",
37
+ "data_sync.runs.detail.logs.level": "Stufe",
38
+ "data_sync.runs.detail.logs.message": "Nachricht",
39
+ "data_sync.runs.detail.logs.time": "Zeit",
40
+ "data_sync.runs.detail.noLogs": "Keine Protokolleinträge",
41
+ "data_sync.runs.detail.progress": "Fortschritt",
42
+ "data_sync.runs.detail.progress.batches": "{count} Stapel",
43
+ "data_sync.runs.detail.progress.itemsProcessed": "{count} Elemente verarbeitet",
44
+ "data_sync.runs.detail.retry": "Wiederholen",
45
+ "data_sync.runs.detail.retryError": "Lauf konnte nicht wiederholt werden",
46
+ "data_sync.runs.detail.retrySuccess": "Lauf wiederholt",
47
+ "data_sync.runs.detail.title": "Synchronisierungslauf"
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "data_sync.dashboard.actions.view": "View",
3
+ "data_sync.dashboard.columns.created": "Created",
4
+ "data_sync.dashboard.columns.createdAt": "Started",
5
+ "data_sync.dashboard.columns.direction": "Direction",
6
+ "data_sync.dashboard.columns.entityType": "Entity Type",
7
+ "data_sync.dashboard.columns.failed": "Failed",
8
+ "data_sync.dashboard.columns.integration": "Integration",
9
+ "data_sync.dashboard.columns.status": "Status",
10
+ "data_sync.dashboard.columns.updated": "Updated",
11
+ "data_sync.dashboard.direction.export": "Export",
12
+ "data_sync.dashboard.direction.import": "Import",
13
+ "data_sync.dashboard.filters.allDirections": "All Directions",
14
+ "data_sync.dashboard.filters.allStatuses": "All Statuses",
15
+ "data_sync.dashboard.filters.status": "Status",
16
+ "data_sync.dashboard.loadError": "Failed to load sync runs",
17
+ "data_sync.dashboard.noRuns": "No sync runs yet",
18
+ "data_sync.dashboard.status.cancelled": "Cancelled",
19
+ "data_sync.dashboard.status.completed": "Completed",
20
+ "data_sync.dashboard.status.failed": "Failed",
21
+ "data_sync.dashboard.status.paused": "Paused",
22
+ "data_sync.dashboard.status.pending": "Pending",
23
+ "data_sync.dashboard.status.running": "Running",
24
+ "data_sync.dashboard.title": "Sync Runs",
25
+ "data_sync.nav.title": "Data Sync",
26
+ "data_sync.runs.detail.back": "Back to Sync Runs",
27
+ "data_sync.runs.detail.cancel": "Cancel",
28
+ "data_sync.runs.detail.cancelError": "Failed to cancel sync run",
29
+ "data_sync.runs.detail.cancelSuccess": "Sync run cancelled",
30
+ "data_sync.runs.detail.counters.created": "Created",
31
+ "data_sync.runs.detail.counters.failed": "Failed",
32
+ "data_sync.runs.detail.counters.skipped": "Skipped",
33
+ "data_sync.runs.detail.counters.updated": "Updated",
34
+ "data_sync.runs.detail.error": "Error",
35
+ "data_sync.runs.detail.loadError": "Failed to load sync run",
36
+ "data_sync.runs.detail.logs": "Operation Logs",
37
+ "data_sync.runs.detail.logs.level": "Level",
38
+ "data_sync.runs.detail.logs.message": "Message",
39
+ "data_sync.runs.detail.logs.time": "Time",
40
+ "data_sync.runs.detail.noLogs": "No log entries",
41
+ "data_sync.runs.detail.progress": "Progress",
42
+ "data_sync.runs.detail.progress.batches": "{count} batches",
43
+ "data_sync.runs.detail.progress.itemsProcessed": "{count} items processed",
44
+ "data_sync.runs.detail.retry": "Retry",
45
+ "data_sync.runs.detail.retryError": "Failed to retry sync run",
46
+ "data_sync.runs.detail.retrySuccess": "Sync run retried",
47
+ "data_sync.runs.detail.title": "Sync Run"
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "data_sync.dashboard.actions.view": "Ver",
3
+ "data_sync.dashboard.columns.created": "Creados",
4
+ "data_sync.dashboard.columns.createdAt": "Iniciado",
5
+ "data_sync.dashboard.columns.direction": "Dirección",
6
+ "data_sync.dashboard.columns.entityType": "Tipo de entidad",
7
+ "data_sync.dashboard.columns.failed": "Fallidos",
8
+ "data_sync.dashboard.columns.integration": "Integración",
9
+ "data_sync.dashboard.columns.status": "Estado",
10
+ "data_sync.dashboard.columns.updated": "Actualizados",
11
+ "data_sync.dashboard.direction.export": "Exportación",
12
+ "data_sync.dashboard.direction.import": "Importación",
13
+ "data_sync.dashboard.filters.allDirections": "Todas las direcciones",
14
+ "data_sync.dashboard.filters.allStatuses": "Todos los estados",
15
+ "data_sync.dashboard.filters.status": "Estado",
16
+ "data_sync.dashboard.loadError": "No se pudieron cargar las ejecuciones de sincronización",
17
+ "data_sync.dashboard.noRuns": "Sin ejecuciones de sincronización",
18
+ "data_sync.dashboard.status.cancelled": "Cancelado",
19
+ "data_sync.dashboard.status.completed": "Completado",
20
+ "data_sync.dashboard.status.failed": "Fallido",
21
+ "data_sync.dashboard.status.paused": "Pausado",
22
+ "data_sync.dashboard.status.pending": "Pendiente",
23
+ "data_sync.dashboard.status.running": "En ejecución",
24
+ "data_sync.dashboard.title": "Ejecuciones de sincronización",
25
+ "data_sync.nav.title": "Sincronización de datos",
26
+ "data_sync.runs.detail.back": "Volver a ejecuciones",
27
+ "data_sync.runs.detail.cancel": "Cancelar",
28
+ "data_sync.runs.detail.cancelError": "No se pudo cancelar la ejecución",
29
+ "data_sync.runs.detail.cancelSuccess": "Ejecución cancelada",
30
+ "data_sync.runs.detail.counters.created": "Creados",
31
+ "data_sync.runs.detail.counters.failed": "Fallidos",
32
+ "data_sync.runs.detail.counters.skipped": "Omitidos",
33
+ "data_sync.runs.detail.counters.updated": "Actualizados",
34
+ "data_sync.runs.detail.error": "Error",
35
+ "data_sync.runs.detail.loadError": "No se pudo cargar la ejecución",
36
+ "data_sync.runs.detail.logs": "Registros de operación",
37
+ "data_sync.runs.detail.logs.level": "Nivel",
38
+ "data_sync.runs.detail.logs.message": "Mensaje",
39
+ "data_sync.runs.detail.logs.time": "Hora",
40
+ "data_sync.runs.detail.noLogs": "Sin entradas de registro",
41
+ "data_sync.runs.detail.progress": "Progreso",
42
+ "data_sync.runs.detail.progress.batches": "{count} lotes",
43
+ "data_sync.runs.detail.progress.itemsProcessed": "{count} elementos procesados",
44
+ "data_sync.runs.detail.retry": "Reintentar",
45
+ "data_sync.runs.detail.retryError": "No se pudo reintentar la ejecución",
46
+ "data_sync.runs.detail.retrySuccess": "Ejecución reintentada",
47
+ "data_sync.runs.detail.title": "Ejecución de sincronización"
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "data_sync.dashboard.actions.view": "Zobacz",
3
+ "data_sync.dashboard.columns.created": "Utworzone",
4
+ "data_sync.dashboard.columns.createdAt": "Rozpoczęto",
5
+ "data_sync.dashboard.columns.direction": "Kierunek",
6
+ "data_sync.dashboard.columns.entityType": "Typ encji",
7
+ "data_sync.dashboard.columns.failed": "Nieudane",
8
+ "data_sync.dashboard.columns.integration": "Integracja",
9
+ "data_sync.dashboard.columns.status": "Status",
10
+ "data_sync.dashboard.columns.updated": "Zaktualizowane",
11
+ "data_sync.dashboard.direction.export": "Eksport",
12
+ "data_sync.dashboard.direction.import": "Import",
13
+ "data_sync.dashboard.filters.allDirections": "Wszystkie kierunki",
14
+ "data_sync.dashboard.filters.allStatuses": "Wszystkie statusy",
15
+ "data_sync.dashboard.filters.status": "Status",
16
+ "data_sync.dashboard.loadError": "Nie udało się załadować przebiegów",
17
+ "data_sync.dashboard.noRuns": "Brak przebiegów synchronizacji",
18
+ "data_sync.dashboard.status.cancelled": "Anulowany",
19
+ "data_sync.dashboard.status.completed": "Zakończony",
20
+ "data_sync.dashboard.status.failed": "Nieudany",
21
+ "data_sync.dashboard.status.paused": "Wstrzymany",
22
+ "data_sync.dashboard.status.pending": "Oczekujący",
23
+ "data_sync.dashboard.status.running": "W toku",
24
+ "data_sync.dashboard.title": "Przebiegi synchronizacji",
25
+ "data_sync.nav.title": "Synchronizacja danych",
26
+ "data_sync.runs.detail.back": "Powrót do przebiegów",
27
+ "data_sync.runs.detail.cancel": "Anuluj",
28
+ "data_sync.runs.detail.cancelError": "Nie udało się anulować przebiegu",
29
+ "data_sync.runs.detail.cancelSuccess": "Przebieg anulowany",
30
+ "data_sync.runs.detail.counters.created": "Utworzone",
31
+ "data_sync.runs.detail.counters.failed": "Nieudane",
32
+ "data_sync.runs.detail.counters.skipped": "Pominięte",
33
+ "data_sync.runs.detail.counters.updated": "Zaktualizowane",
34
+ "data_sync.runs.detail.error": "Błąd",
35
+ "data_sync.runs.detail.loadError": "Nie udało się załadować przebiegu",
36
+ "data_sync.runs.detail.logs": "Logi operacji",
37
+ "data_sync.runs.detail.logs.level": "Poziom",
38
+ "data_sync.runs.detail.logs.message": "Wiadomość",
39
+ "data_sync.runs.detail.logs.time": "Czas",
40
+ "data_sync.runs.detail.noLogs": "Brak wpisów w logach",
41
+ "data_sync.runs.detail.progress": "Postęp",
42
+ "data_sync.runs.detail.progress.batches": "{count} partii",
43
+ "data_sync.runs.detail.progress.itemsProcessed": "{count} elementów przetworzonych",
44
+ "data_sync.runs.detail.retry": "Ponów",
45
+ "data_sync.runs.detail.retryError": "Nie udało się ponowić przebiegu",
46
+ "data_sync.runs.detail.retrySuccess": "Przebieg ponowiony",
47
+ "data_sync.runs.detail.title": "Przebieg synchronizacji"
48
+ }
@@ -0,0 +1,5 @@
1
+ export const metadata = {
2
+ id: 'data_sync',
3
+ title: 'Data Sync',
4
+ description: 'Streaming data sync hub for import/export integrations.',
5
+ }
@@ -0,0 +1,15 @@
1
+ import type { DataSyncAdapter } from './adapter'
2
+
3
+ const adapters = new Map<string, DataSyncAdapter>()
4
+
5
+ export function registerDataSyncAdapter(adapter: DataSyncAdapter): void {
6
+ adapters.set(adapter.providerKey, adapter)
7
+ }
8
+
9
+ export function getDataSyncAdapter(providerKey: string): DataSyncAdapter | undefined {
10
+ return adapters.get(providerKey)
11
+ }
12
+
13
+ export function getAllDataSyncAdapters(): DataSyncAdapter[] {
14
+ return Array.from(adapters.values())
15
+ }
@@ -0,0 +1,90 @@
1
+ export interface TenantScope {
2
+ organizationId: string
3
+ tenantId: string
4
+ }
5
+
6
+ export interface FieldMapping {
7
+ externalField: string
8
+ localField: string
9
+ transform?: string
10
+ required?: boolean
11
+ defaultValue?: unknown
12
+ }
13
+
14
+ export interface DataMapping {
15
+ entityType: string
16
+ fields: FieldMapping[]
17
+ matchStrategy: 'externalId' | 'sku' | 'email' | 'custom'
18
+ matchField?: string
19
+ }
20
+
21
+ export interface StreamImportInput {
22
+ entityType: string
23
+ cursor?: string
24
+ batchSize: number
25
+ credentials: Record<string, unknown>
26
+ mapping: DataMapping
27
+ scope: TenantScope
28
+ }
29
+
30
+ export interface ImportItem {
31
+ externalId: string
32
+ data: Record<string, unknown>
33
+ action: 'create' | 'update' | 'skip'
34
+ hash?: string
35
+ }
36
+
37
+ export interface ImportBatch {
38
+ items: ImportItem[]
39
+ cursor: string
40
+ hasMore: boolean
41
+ totalEstimate?: number
42
+ batchIndex: number
43
+ }
44
+
45
+ export interface StreamExportInput {
46
+ entityType: string
47
+ cursor?: string
48
+ batchSize: number
49
+ credentials: Record<string, unknown>
50
+ mapping: DataMapping
51
+ scope: TenantScope
52
+ filter?: Record<string, unknown>
53
+ }
54
+
55
+ export interface ExportItemResult {
56
+ localId: string
57
+ externalId?: string
58
+ status: 'success' | 'error' | 'skipped'
59
+ error?: string
60
+ }
61
+
62
+ export interface ExportBatch {
63
+ results: ExportItemResult[]
64
+ cursor: string
65
+ hasMore: boolean
66
+ batchIndex: number
67
+ }
68
+
69
+ export interface ValidationResult {
70
+ ok: boolean
71
+ message?: string
72
+ details?: Record<string, unknown>
73
+ }
74
+
75
+ export interface DataSyncAdapter {
76
+ readonly providerKey: string
77
+ readonly direction: 'import' | 'export' | 'bidirectional'
78
+ readonly supportedEntities: string[]
79
+
80
+ streamImport?(input: StreamImportInput): AsyncIterable<ImportBatch>
81
+ streamExport?(input: StreamExportInput): AsyncIterable<ExportBatch>
82
+ getInitialCursor?(input: { entityType: string; scope: TenantScope }): Promise<string | null>
83
+ getMapping(input: { entityType: string; scope: TenantScope }): Promise<DataMapping>
84
+ validateConnection?(input: {
85
+ entityType: string
86
+ credentials: Record<string, unknown>
87
+ mapping: DataMapping
88
+ scope: TenantScope
89
+ }): Promise<ValidationResult>
90
+ }
@@ -0,0 +1,95 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { SyncExternalIdMapping } from '../../integrations/data/entities'
4
+
5
+ type MappingScope = {
6
+ organizationId: string
7
+ tenantId: string
8
+ }
9
+
10
+ export function createExternalIdMappingService(em: EntityManager) {
11
+ return {
12
+ async lookupLocalId(integrationId: string, entityType: string, externalId: string, scope: MappingScope): Promise<string | null> {
13
+ const row = await findOneWithDecryption(
14
+ em,
15
+ SyncExternalIdMapping,
16
+ {
17
+ integrationId,
18
+ internalEntityType: entityType,
19
+ externalId,
20
+ organizationId: scope.organizationId,
21
+ tenantId: scope.tenantId,
22
+ deletedAt: null,
23
+ },
24
+ undefined,
25
+ scope,
26
+ )
27
+ return row?.internalEntityId ?? null
28
+ },
29
+
30
+ async lookupExternalId(integrationId: string, entityType: string, localId: string, scope: MappingScope): Promise<string | null> {
31
+ const row = await findOneWithDecryption(
32
+ em,
33
+ SyncExternalIdMapping,
34
+ {
35
+ integrationId,
36
+ internalEntityType: entityType,
37
+ internalEntityId: localId,
38
+ organizationId: scope.organizationId,
39
+ tenantId: scope.tenantId,
40
+ deletedAt: null,
41
+ },
42
+ undefined,
43
+ scope,
44
+ )
45
+ return row?.externalId ?? null
46
+ },
47
+
48
+ async storeExternalIdMapping(
49
+ integrationId: string,
50
+ entityType: string,
51
+ localId: string,
52
+ externalId: string,
53
+ scope: MappingScope,
54
+ ): Promise<SyncExternalIdMapping> {
55
+ const existing = await findOneWithDecryption(
56
+ em,
57
+ SyncExternalIdMapping,
58
+ {
59
+ integrationId,
60
+ internalEntityType: entityType,
61
+ internalEntityId: localId,
62
+ organizationId: scope.organizationId,
63
+ tenantId: scope.tenantId,
64
+ deletedAt: null,
65
+ },
66
+ undefined,
67
+ scope,
68
+ )
69
+
70
+ if (existing) {
71
+ existing.externalId = externalId
72
+ existing.syncStatus = 'synced'
73
+ existing.lastSyncedAt = new Date()
74
+ await em.flush()
75
+ return existing
76
+ }
77
+
78
+ const created = em.create(SyncExternalIdMapping, {
79
+ integrationId,
80
+ internalEntityType: entityType,
81
+ internalEntityId: localId,
82
+ externalId,
83
+ syncStatus: 'synced',
84
+ lastSyncedAt: new Date(),
85
+ organizationId: scope.organizationId,
86
+ tenantId: scope.tenantId,
87
+ })
88
+
89
+ await em.persistAndFlush(created)
90
+ return created
91
+ },
92
+ }
93
+ }
94
+
95
+ export type ExternalIdMappingService = ReturnType<typeof createExternalIdMappingService>