@fullstackhouse/open-mercato-data-sync-durable 0.1.0
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.
- package/README.md +86 -0
- package/dist/engine/durable-run.js +130 -0
- package/dist/engine/durable-run.js.map +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/kinds/data-sync-run.js +57 -0
- package/dist/kinds/data-sync-run.js.map +7 -0
- package/dist/mirror-version.js +5 -0
- package/dist/mirror-version.js.map +7 -0
- package/dist/mirror.manifest.json +65 -0
- package/dist/modules/data_sync/acl.js +8 -0
- package/dist/modules/data_sync/acl.js.map +7 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js +17 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/mappings/route.js +15 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +7 -0
- package/dist/modules/data_sync/api/options.js +13 -0
- package/dist/modules/data_sync/api/options.js.map +7 -0
- package/dist/modules/data_sync/api/run.js +59 -0
- package/dist/modules/data_sync/api/run.js.map +7 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +13 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +7 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js +13 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +7 -0
- package/dist/modules/data_sync/api/runs/[id]/route.js +13 -0
- package/dist/modules/data_sync/api/runs/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/runs.js +13 -0
- package/dist/modules/data_sync/api/runs.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js +17 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/route.js +15 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/serialize.js +6 -0
- package/dist/modules/data_sync/api/schedules/serialize.js.map +7 -0
- package/dist/modules/data_sync/api/validate.js +13 -0
- package/dist/modules/data_sync/api/validate.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.js +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.meta.js +6 -0
- package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +7 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js +6 -0
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js.map +7 -0
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +9 -0
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +7 -0
- package/dist/modules/data_sync/components/RunParameterFields.js +13 -0
- package/dist/modules/data_sync/components/RunParameterFields.js.map +7 -0
- package/dist/modules/data_sync/data/entities.js +9 -0
- package/dist/modules/data_sync/data/entities.js.map +7 -0
- package/dist/modules/data_sync/data/validators.js +12 -0
- package/dist/modules/data_sync/data/validators.js.map +7 -0
- package/dist/modules/data_sync/di.js +33 -0
- package/dist/modules/data_sync/di.js.map +7 -0
- package/dist/modules/data_sync/events.js +9 -0
- package/dist/modules/data_sync/events.js.map +7 -0
- package/dist/modules/data_sync/extension-points.js +8 -0
- package/dist/modules/data_sync/extension-points.js.map +7 -0
- package/dist/modules/data_sync/i18n/de.json +135 -0
- package/dist/modules/data_sync/i18n/en.json +135 -0
- package/dist/modules/data_sync/i18n/es.json +135 -0
- package/dist/modules/data_sync/i18n/ko.json +135 -0
- package/dist/modules/data_sync/i18n/pl.json +135 -0
- package/dist/modules/data_sync/index.js +6 -0
- package/dist/modules/data_sync/index.js.map +7 -0
- package/dist/modules/data_sync/lib/abandoned-run.js +6 -0
- package/dist/modules/data_sync/lib/abandoned-run.js.map +7 -0
- package/dist/modules/data_sync/lib/adapter-registry.js +10 -0
- package/dist/modules/data_sync/lib/adapter-registry.js.map +7 -0
- package/dist/modules/data_sync/lib/adapter.js +2 -0
- package/dist/modules/data_sync/lib/adapter.js.map +7 -0
- package/dist/modules/data_sync/lib/adopt-on-delivery.js +35 -0
- package/dist/modules/data_sync/lib/adopt-on-delivery.js.map +7 -0
- package/dist/modules/data_sync/lib/batch-stream.js +6 -0
- package/dist/modules/data_sync/lib/batch-stream.js.map +7 -0
- package/dist/modules/data_sync/lib/id-mapping.js +6 -0
- package/dist/modules/data_sync/lib/id-mapping.js.map +7 -0
- package/dist/modules/data_sync/lib/queue-policy.js +11 -0
- package/dist/modules/data_sync/lib/queue-policy.js.map +7 -0
- package/dist/modules/data_sync/lib/queue.js +6 -0
- package/dist/modules/data_sync/lib/queue.js.map +7 -0
- package/dist/modules/data_sync/lib/run-parameters.js +8 -0
- package/dist/modules/data_sync/lib/run-parameters.js.map +7 -0
- package/dist/modules/data_sync/lib/start-cursor.js +8 -0
- package/dist/modules/data_sync/lib/start-cursor.js.map +7 -0
- package/dist/modules/data_sync/lib/start-run.js +51 -0
- package/dist/modules/data_sync/lib/start-run.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +6 -0
- package/dist/modules/data_sync/lib/sync-engine.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-run-service.js +7 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-schedule-service.js +6 -0
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +7 -0
- package/dist/modules/data_sync/lib/syncRunStatus.js +9 -0
- package/dist/modules/data_sync/lib/syncRunStatus.js.map +7 -0
- package/dist/modules/data_sync/lib/version-guard.js +14 -0
- package/dist/modules/data_sync/lib/version-guard.js.map +7 -0
- package/dist/modules/data_sync/migrations/Migration20260304113737.js +6 -0
- package/dist/modules/data_sync/migrations/Migration20260304113737.js.map +7 -0
- package/dist/modules/data_sync/migrations/Migration20260810120000.js +6 -0
- package/dist/modules/data_sync/migrations/Migration20260810120000.js.map +7 -0
- package/dist/modules/data_sync/setup.js +8 -0
- package/dist/modules/data_sync/setup.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-export.js +19 -0
- package/dist/modules/data_sync/workers/sync-export.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-import.js +19 -0
- package/dist/modules/data_sync/workers/sync-import.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js +8 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +7 -0
- package/generated/entities/sync_cursor/index.ts +8 -0
- package/generated/entities/sync_mapping/index.ts +8 -0
- package/generated/entities/sync_run/index.ts +22 -0
- package/generated/entities/sync_schedule/index.ts +16 -0
- package/generated/entities.ids.generated.ts +13 -0
- package/package.json +135 -0
- package/src/compat/mirror-shape.test.ts +106 -0
- package/src/compat/seams.test.ts +97 -0
- package/src/engine/durable-run.ts +231 -0
- package/src/index.ts +4 -0
- package/src/kinds/data-sync-run.ts +104 -0
- package/src/mirror-version.ts +3 -0
- package/src/mirror.manifest.json +65 -0
- package/src/modules/data_sync/__integration__/TC-DSD-001.spec.ts +80 -0
- package/src/modules/data_sync/acl.ts +5 -0
- package/src/modules/data_sync/api/mappings/[id]/route.ts +12 -0
- package/src/modules/data_sync/api/mappings/route.ts +11 -0
- package/src/modules/data_sync/api/options.ts +10 -0
- package/src/modules/data_sync/api/run.ts +103 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +10 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +10 -0
- package/src/modules/data_sync/api/runs/[id]/route.ts +10 -0
- package/src/modules/data_sync/api/runs.ts +10 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +12 -0
- package/src/modules/data_sync/api/schedules/route.ts +11 -0
- package/src/modules/data_sync/api/schedules/serialize.ts +4 -0
- package/src/modules/data_sync/api/validate.ts +10 -0
- package/src/modules/data_sync/backend/data-sync/page.meta.ts +4 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +5 -0
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.meta.ts +4 -0
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +5 -0
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +6 -0
- package/src/modules/data_sync/components/RunParameterFields.tsx +5 -0
- package/src/modules/data_sync/data/entities.ts +4 -0
- package/src/modules/data_sync/data/validators.ts +4 -0
- package/src/modules/data_sync/di.ts +57 -0
- package/src/modules/data_sync/events.ts +5 -0
- package/src/modules/data_sync/extension-points.ts +5 -0
- package/src/modules/data_sync/i18n/de.json +135 -0
- package/src/modules/data_sync/i18n/en.json +135 -0
- package/src/modules/data_sync/i18n/es.json +135 -0
- package/src/modules/data_sync/i18n/ko.json +135 -0
- package/src/modules/data_sync/i18n/pl.json +135 -0
- package/src/modules/data_sync/index.ts +4 -0
- package/src/modules/data_sync/lib/adapter-registry.ts +4 -0
- package/src/modules/data_sync/lib/adapter.ts +3 -0
- package/src/modules/data_sync/lib/adopt-on-delivery.ts +69 -0
- package/src/modules/data_sync/lib/batch-stream.ts +4 -0
- package/src/modules/data_sync/lib/id-mapping.ts +4 -0
- package/src/modules/data_sync/lib/queue-policy.ts +4 -0
- package/src/modules/data_sync/lib/queue.ts +4 -0
- package/src/modules/data_sync/lib/run-parameters.ts +4 -0
- package/src/modules/data_sync/lib/start-cursor.ts +4 -0
- package/src/modules/data_sync/lib/start-run.ts +106 -0
- package/src/modules/data_sync/lib/sync-engine.ts +4 -0
- package/src/modules/data_sync/lib/sync-run-service.ts +4 -0
- package/src/modules/data_sync/lib/sync-schedule-service.ts +4 -0
- package/src/modules/data_sync/lib/syncRunStatus.ts +4 -0
- package/src/modules/data_sync/lib/version-guard.ts +27 -0
- package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +1013 -0
- package/src/modules/data_sync/migrations/Migration20260304113737.ts +4 -0
- package/src/modules/data_sync/migrations/Migration20260810120000.ts +4 -0
- package/src/modules/data_sync/setup.ts +5 -0
- package/src/modules/data_sync/workers/sync-export.ts +20 -0
- package/src/modules/data_sync/workers/sync-import.ts +20 -0
- package/src/modules/data_sync/workers/sync-scheduled.ts +5 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// The job kinds a `data_sync` run becomes.
|
|
2
|
+
//
|
|
3
|
+
// A slice is one call into core's own engine, with the run service and the progress service
|
|
4
|
+
// decorated for its duration. The engine does all the work it always did; what changes is where
|
|
5
|
+
// its cursor commits go (through the lease fence), when it stops (at a batch boundary when the
|
|
6
|
+
// slice budget is spent), and who writes the terminal state (the durable transition, in the
|
|
7
|
+
// same transaction as the job's own).
|
|
8
|
+
|
|
9
|
+
import type { KindDefinition, SliceContext, SliceOutcome, SqlExecutor } from '@fullstackhouse/open-mercato-durable-work'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
mirrorRunStatus,
|
|
13
|
+
outcomeOf,
|
|
14
|
+
recordSlice,
|
|
15
|
+
reopenRun,
|
|
16
|
+
type ProgressServiceLike,
|
|
17
|
+
type SyncRunServiceLike,
|
|
18
|
+
type SyncScope,
|
|
19
|
+
} from '../engine/durable-run'
|
|
20
|
+
|
|
21
|
+
export const IMPORT_KIND = 'data_sync.import'
|
|
22
|
+
export const EXPORT_KIND = 'data_sync.export'
|
|
23
|
+
export const DATA_SYNC_QUEUE = 'durable-work.data-sync'
|
|
24
|
+
|
|
25
|
+
export type SyncRunInput = {
|
|
26
|
+
runId: string
|
|
27
|
+
batchSize: number
|
|
28
|
+
direction: 'import' | 'export'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** What a slice needs from the host, resolved per slice from the container. */
|
|
32
|
+
export type SyncEngineLike = {
|
|
33
|
+
runImport(runId: string, batchSize: number, scope: SyncScope): Promise<void>
|
|
34
|
+
runExport(runId: string, batchSize: number, scope: SyncScope): Promise<void>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type DataSyncKindDeps = {
|
|
38
|
+
/** Resolved per slice, because each slice runs on its own EntityManager. */
|
|
39
|
+
resolve(): Promise<{
|
|
40
|
+
engine: (services: { runService: SyncRunServiceLike; progressService: ProgressServiceLike }) => SyncEngineLike
|
|
41
|
+
runService: SyncRunServiceLike
|
|
42
|
+
progressService: ProgressServiceLike
|
|
43
|
+
}>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runSlice(ctx: SliceContext<SyncRunInput>, deps: DataSyncKindDeps): Promise<SliceOutcome> {
|
|
47
|
+
const input = ctx.job.input as SyncRunInput
|
|
48
|
+
const scope: SyncScope = { tenantId: ctx.scope.tenantId, organizationId: ctx.scope.organizationId }
|
|
49
|
+
const { engine, runService, progressService } = await deps.resolve()
|
|
50
|
+
|
|
51
|
+
const recorder = recordSlice(ctx, runService, progressService)
|
|
52
|
+
const decorated = engine({ runService: recorder.runService, progressService: recorder.progressService })
|
|
53
|
+
|
|
54
|
+
if (input.direction === 'export') await decorated.runExport(input.runId, input.batchSize, scope)
|
|
55
|
+
else await decorated.runImport(input.runId, input.batchSize, scope)
|
|
56
|
+
|
|
57
|
+
const after = await runService.getRun(input.runId, scope)
|
|
58
|
+
return outcomeOf(recorder, input.runId, after?.status)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds both kinds.
|
|
63
|
+
*
|
|
64
|
+
* `orphanPolicy: 'redrive'` is deliberate and is the one place the adopter asserts something
|
|
65
|
+
* about the work rather than the mechanism: a sync run keeps a committed cursor, so re-running
|
|
66
|
+
* it after a worker died resumes rather than repeats. That is what makes automatic recovery
|
|
67
|
+
* safe here when the mechanism's default is to park and wait for a human.
|
|
68
|
+
*/
|
|
69
|
+
export function dataSyncKinds(deps: DataSyncKindDeps): KindDefinition<SyncRunInput, never>[] {
|
|
70
|
+
const shared = {
|
|
71
|
+
queue: DATA_SYNC_QUEUE,
|
|
72
|
+
requiredFeatures: ['data_sync.run'],
|
|
73
|
+
orphanPolicy: 'redrive' as const,
|
|
74
|
+
// Long enough that a slow adapter page does not look like a dead worker, short enough that
|
|
75
|
+
// a dead worker is noticed in about a minute.
|
|
76
|
+
lease: { ttlMs: 60_000, sliceBudgetMs: 300_000, pendingTtlMs: 900_000 },
|
|
77
|
+
|
|
78
|
+
async onTransition(job: { id: string; input: unknown; status: string; errorMessage: string | null }, _scope: unknown, tx: SqlExecutor) {
|
|
79
|
+
const { runId } = job.input as SyncRunInput
|
|
80
|
+
const status = job.status === 'completed' ? 'completed' : job.status === 'cancelled' ? 'cancelled' : 'failed'
|
|
81
|
+
return mirrorRunStatus(tx, runId, status, job.errorMessage)
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async onRedrive(job: { input: unknown }, _scope: unknown, tx: SqlExecutor) {
|
|
85
|
+
return reopenRun(tx, (job.input as SyncRunInput).runId)
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [
|
|
90
|
+
{ ...shared, kind: IMPORT_KIND, step: (ctx) => runSlice(ctx, deps) } as KindDefinition<SyncRunInput, never>,
|
|
91
|
+
{ ...shared, kind: EXPORT_KIND, step: (ctx) => runSlice(ctx, deps) } as KindDefinition<SyncRunInput, never>,
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The single-runner key: one live run per integration, entity and direction. Two imports of
|
|
96
|
+
* the same entity would interleave over one cursor. */
|
|
97
|
+
export function syncLockKey(integrationId: string, entityType: string, direction: string): string {
|
|
98
|
+
return `data_sync:${integrationId}:${entityType}:${direction}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Makes starting a run twice for the same run id return the first job rather than a second. */
|
|
102
|
+
export function syncIdempotencyKey(runId: string): string {
|
|
103
|
+
return `data_sync.run:${runId}`
|
|
104
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"coreVersion": "0.7.0",
|
|
3
|
+
"moduleId": "data_sync",
|
|
4
|
+
"generated": {
|
|
5
|
+
"acl.ts": "3b30f591eae78964",
|
|
6
|
+
"api/mappings/[id]/route.ts": "7b3fb917db7bd831",
|
|
7
|
+
"api/mappings/route.ts": "5551bc5ff61dfebb",
|
|
8
|
+
"api/options.ts": "bff527b4061b6c2b",
|
|
9
|
+
"api/runs.ts": "a2024a96d896c814",
|
|
10
|
+
"api/runs/[id]/cancel.ts": "1c99c66183d994ae",
|
|
11
|
+
"api/runs/[id]/retry.ts": "cf6ea67efcebdeb9",
|
|
12
|
+
"api/runs/[id]/route.ts": "2d0e39daef513f92",
|
|
13
|
+
"api/schedules/[id]/route.ts": "af2b16bd45fd02b5",
|
|
14
|
+
"api/schedules/route.ts": "2dbf0d2f04cdc100",
|
|
15
|
+
"api/schedules/serialize.ts": "44664c58abafd26f",
|
|
16
|
+
"api/validate.ts": "9fd095700abd39cb",
|
|
17
|
+
"backend/data-sync/page.meta.ts": "0dd9b6b406fa2699",
|
|
18
|
+
"backend/data-sync/page.tsx": "e68b00a4242abbbb",
|
|
19
|
+
"backend/data-sync/runs/[id]/page.meta.ts": "a1946eb5885a2109",
|
|
20
|
+
"backend/data-sync/runs/[id]/page.tsx": "963778ff29f7d96f",
|
|
21
|
+
"components/IntegrationScheduleTab.tsx": "185732c76a2f953a",
|
|
22
|
+
"components/RunParameterFields.tsx": "3f49886ef0a55193",
|
|
23
|
+
"data/entities.ts": "16476c912a6c7d40",
|
|
24
|
+
"data/validators.ts": "6c238076541373b0",
|
|
25
|
+
"events.ts": "8eea801045ff9789",
|
|
26
|
+
"extension-points.ts": "903cae9d931e0fb3",
|
|
27
|
+
"i18n/de.json": "943e5875db1ab98b",
|
|
28
|
+
"i18n/en.json": "ae6c5edcba6bb7f7",
|
|
29
|
+
"i18n/es.json": "de135edcf1917269",
|
|
30
|
+
"i18n/ko.json": "cb75626e56a0d267",
|
|
31
|
+
"i18n/pl.json": "6096d5c85f98ba8a",
|
|
32
|
+
"index.ts": "c15d4c24db598205",
|
|
33
|
+
"lib/adapter-registry.ts": "9ddb1b6cbfa911d7",
|
|
34
|
+
"lib/adapter.ts": "3902953e7bdc6021",
|
|
35
|
+
"lib/batch-stream.ts": "db921759b23aef76",
|
|
36
|
+
"lib/id-mapping.ts": "08628d924244bfc8",
|
|
37
|
+
"lib/queue-policy.ts": "7c75ee82eca0ca70",
|
|
38
|
+
"lib/queue.ts": "0fb590d7695c3837",
|
|
39
|
+
"lib/run-parameters.ts": "145b0a8697d34c82",
|
|
40
|
+
"lib/start-cursor.ts": "2620c39153035fb5",
|
|
41
|
+
"lib/sync-engine.ts": "bf3dbfdb1676190c",
|
|
42
|
+
"lib/sync-run-service.ts": "f55c4ec8c547cbd9",
|
|
43
|
+
"lib/sync-schedule-service.ts": "fe2e6bafc37ed319",
|
|
44
|
+
"lib/syncRunStatus.ts": "c30a457245c9cd11",
|
|
45
|
+
"migrations/.snapshot-open-mercato.json": "662107af1e52e5d3",
|
|
46
|
+
"migrations/Migration20260304113737.ts": "e3f0edec96d4a4d8",
|
|
47
|
+
"migrations/Migration20260810120000.ts": "29a2154fcf03c5ce",
|
|
48
|
+
"setup.ts": "f33921e5cb6aafa3",
|
|
49
|
+
"workers/sync-scheduled.ts": "bd1c585595bc7148"
|
|
50
|
+
},
|
|
51
|
+
"replaced": {
|
|
52
|
+
"api/run.ts": "0eda5b6869a0b95d",
|
|
53
|
+
"di.ts": "d351534bd20b2b29",
|
|
54
|
+
"lib/start-run.ts": "74081590458b202b",
|
|
55
|
+
"workers/sync-export.ts": "a23cad4d03dd42da",
|
|
56
|
+
"workers/sync-import.ts": "21e5356d2d394b6d"
|
|
57
|
+
},
|
|
58
|
+
"entityDescriptor": [
|
|
59
|
+
"entities.ids.generated.ts",
|
|
60
|
+
"entities/sync_run/index.ts",
|
|
61
|
+
"entities/sync_cursor/index.ts",
|
|
62
|
+
"entities/sync_mapping/index.ts",
|
|
63
|
+
"entities/sync_schedule/index.ts"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// The `data_sync` drop-in, against a running app.
|
|
2
|
+
//
|
|
3
|
+
// The point of these is that nothing in them is durable-work specific: they exercise core's own
|
|
4
|
+
// `data_sync` API and assert core's own behaviour. If this package is doing its job, a host
|
|
5
|
+
// cannot tell the difference — except that a run now leaves a durable job behind it.
|
|
6
|
+
|
|
7
|
+
import { expect, test } from '@playwright/test'
|
|
8
|
+
import { apiRequest, getAuthToken } from '@open-mercato/core/helpers/integration/api'
|
|
9
|
+
|
|
10
|
+
type Options = { items: Array<{ integrationId: string; canStartRun?: boolean; isEnabled?: boolean }> }
|
|
11
|
+
type StartedRun = { id: string; progressJobId: string }
|
|
12
|
+
type Job = {
|
|
13
|
+
id: string
|
|
14
|
+
kind: string
|
|
15
|
+
status: string
|
|
16
|
+
lockKey: string | null
|
|
17
|
+
idempotencyKey: string | null
|
|
18
|
+
subject: { type: string; id: string } | null
|
|
19
|
+
redrivable: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const startRun = async (request: Parameters<typeof apiRequest>[0], token: string, parameters: Record<string, unknown>) =>
|
|
23
|
+
apiRequest(request, 'POST', '/api/data_sync/run', {
|
|
24
|
+
token,
|
|
25
|
+
data: { integrationId: 'example_sync', entityType: 'example.record', direction: 'import', parameters },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Serial: these share one lock key, so a run left live by one test is the reason the next
|
|
29
|
+
// would fail. Ordering them is honest — a parallel run of the same integration is exactly what
|
|
30
|
+
// the single-runner guarantee forbids.
|
|
31
|
+
test.describe.serial('data_sync served from the durable package', () => {
|
|
32
|
+
test('keeps core\'s API surface: the runs list answers as it always did', async ({ request }) => {
|
|
33
|
+
const token = await getAuthToken(request, 'admin')
|
|
34
|
+
const response = await apiRequest(request, 'GET', '/api/data_sync/runs', { token })
|
|
35
|
+
expect(response.status()).toBe(200)
|
|
36
|
+
expect(await response.json()).toMatchObject({ items: expect.any(Array) })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('keeps core\'s adapter registry: the scripted integration is startable', async ({ request }) => {
|
|
40
|
+
// `example_sync` registers through core's own adapter registry from an `@app` module. That
|
|
41
|
+
// it is visible here is the evidence that swapping the package did not break the registry
|
|
42
|
+
// every other provider shares.
|
|
43
|
+
const token = await getAuthToken(request, 'admin')
|
|
44
|
+
const response = await apiRequest(request, 'GET', '/api/data_sync/options', { token })
|
|
45
|
+
expect(response.status()).toBe(200)
|
|
46
|
+
|
|
47
|
+
const body = (await response.json()) as Options
|
|
48
|
+
const example = body.items.find((item) => item.integrationId === 'example_sync')
|
|
49
|
+
expect(example, 'example_sync should be registered and enabled').toBeTruthy()
|
|
50
|
+
expect(example?.canStartRun).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('a started run becomes a durable job with its lock, idempotency key and subject', async ({ request }) => {
|
|
54
|
+
const token = await getAuthToken(request, 'admin')
|
|
55
|
+
const started = await startRun(request, token, { batches: 3, itemsPerBatch: 1 })
|
|
56
|
+
expect(started.status()).toBe(201)
|
|
57
|
+
// Reported by the route so a failure here says *why* rather than just "no job found".
|
|
58
|
+
expect(started.headers()['x-durable-work'], started.headers()['x-durable-work-reason'] ?? 'no reason reported').toBe('created')
|
|
59
|
+
const run = (await started.json()) as StartedRun
|
|
60
|
+
expect(run.progressJobId, 'a progress job is required: core consults it for the per-batch stop').toBeTruthy()
|
|
61
|
+
|
|
62
|
+
const jobs = await apiRequest(request, 'GET', `/api/durable_work/jobs?kind=data_sync.import&pageSize=50`, { token })
|
|
63
|
+
const body = (await jobs.json()) as { items: Job[] }
|
|
64
|
+
const job = body.items.find((item) => item.subject?.id === run.id)
|
|
65
|
+
|
|
66
|
+
expect(job, 'the run should have produced a durable job').toBeTruthy()
|
|
67
|
+
expect(job!.lockKey).toBe('data_sync:example_sync:example.record:import')
|
|
68
|
+
expect(job!.idempotencyKey).toBe(`data_sync.run:${run.id}`)
|
|
69
|
+
expect(job!.subject).toEqual({ type: 'data_sync.run', id: run.id })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('refuses a second live run for the same integration, entity and direction', async ({ request }) => {
|
|
73
|
+
// The single-runner guarantee. Two imports of one entity would interleave over a single
|
|
74
|
+
// cursor, and the rows one of them skipped would never be noticed.
|
|
75
|
+
const token = await getAuthToken(request, 'admin')
|
|
76
|
+
await startRun(request, token, { batches: 3 })
|
|
77
|
+
const second = await startRun(request, token, { batches: 3 })
|
|
78
|
+
expect(second.status()).toBe(409)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (acl.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/acl'
|
|
4
|
+
export { default } from '@open-mercato/core/modules/data_sync/acl'
|
|
5
|
+
export { features } from '@open-mercato/core/modules/data_sync/acl'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/mappings/[id]/route.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/mappings/[id]/route'
|
|
4
|
+
export { GET, PUT, DELETE } from '@open-mercato/core/modules/data_sync/api/mappings/[id]/route'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/mappings/[id]/route'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
10
|
+
PUT: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
11
|
+
DELETE: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/mappings/route.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/mappings/route'
|
|
4
|
+
export { GET, POST } from '@open-mercato/core/modules/data_sync/api/mappings/route'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/mappings/route'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/options.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/options'
|
|
4
|
+
export { GET } from '@open-mercato/core/modules/data_sync/api/options'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/options'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.view'] },
|
|
10
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Core's run route, with the durable job created alongside the run.
|
|
2
|
+
//
|
|
3
|
+
// Wrapped rather than copied. Core's route is 180 lines of validation, ACL guards, cursor
|
|
4
|
+
// resolution and parameter normalisation, and duplicating that is precisely the liability
|
|
5
|
+
// ADR 0004 removed for the engine — the same argument applies here.
|
|
6
|
+
//
|
|
7
|
+
// Why the wrapper is needed at all: replacing `lib/start-run.ts` redirects only callers that
|
|
8
|
+
// import *our* deep path. Core's own route imports its own sibling, so a run started through
|
|
9
|
+
// it never reaches our start path. This is where that is fixed.
|
|
10
|
+
//
|
|
11
|
+
// The durable job is created after core's transaction rather than inside it, which is weaker
|
|
12
|
+
// than the transactional start our own path gives. It is not a hole: core also enqueues onto
|
|
13
|
+
// its queue, our adopt-on-delivery worker is registered under core's worker id, and both paths
|
|
14
|
+
// carry the same idempotency key — so a crash between the two ends with the delivery adopting
|
|
15
|
+
// the run instead. What the wrapper buys is that the job normally exists immediately, rather
|
|
16
|
+
// than whenever the queue gets to it.
|
|
17
|
+
|
|
18
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
19
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
20
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
21
|
+
import { POST as corePost, openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/run'
|
|
22
|
+
import type { DurableWorkService } from '@fullstackhouse/open-mercato-durable-work'
|
|
23
|
+
|
|
24
|
+
import { EXPORT_KIND, IMPORT_KIND, syncIdempotencyKey, syncLockKey } from '../../../kinds/data-sync-run'
|
|
25
|
+
|
|
26
|
+
const logger = createLogger('data_sync').child({ component: 'durable-run-route' })
|
|
27
|
+
|
|
28
|
+
// Copied from core, because the generator reads it by AST and cannot follow a re-export.
|
|
29
|
+
export const metadata = {
|
|
30
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const openApi = coreOpenApi
|
|
34
|
+
|
|
35
|
+
type StartedRun = { id: string; progressJobId: string | null }
|
|
36
|
+
type RunRow = { id: string; integrationId: string; entityType: string; direction: string; progressJobId?: string | null }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Says what happened to the durable job, without changing core's response body.
|
|
40
|
+
*
|
|
41
|
+
* created — the job exists now
|
|
42
|
+
* deferred — it does not, and the queue delivery will adopt the run instead
|
|
43
|
+
*
|
|
44
|
+
* A header rather than a log line because the difference matters to whoever called: `created`
|
|
45
|
+
* means the run is already under a lease, `deferred` means it is not yet. Buried in a log,
|
|
46
|
+
* that distinction is unavailable to exactly the people who need it, including a test.
|
|
47
|
+
*/
|
|
48
|
+
const DURABLE_HEADER = 'x-durable-work'
|
|
49
|
+
|
|
50
|
+
export async function POST(req: Request) {
|
|
51
|
+
const response = await corePost(req)
|
|
52
|
+
if (response.status !== 201) return response
|
|
53
|
+
|
|
54
|
+
// Cloned: the caller still gets core's response untouched, whatever happens below.
|
|
55
|
+
const started = (await response.clone().json()) as StartedRun
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const auth = await getAuthFromRequest(req)
|
|
59
|
+
if (!auth?.tenantId) {
|
|
60
|
+
response.headers.set(DURABLE_HEADER, 'deferred')
|
|
61
|
+
return response
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const container = await createRequestContainer()
|
|
65
|
+
const durable = container.resolve('durableWorkService') as DurableWorkService
|
|
66
|
+
const runService = container.resolve('dataSyncRunService') as {
|
|
67
|
+
getRun(id: string, scope: unknown): Promise<RunRow | null>
|
|
68
|
+
}
|
|
69
|
+
const scope = { tenantId: auth.tenantId, organizationId: auth.orgId ?? null }
|
|
70
|
+
|
|
71
|
+
const run = await runService.getRun(started.id, { ...scope, organizationId: auth.orgId })
|
|
72
|
+
if (!run) {
|
|
73
|
+
response.headers.set(DURABLE_HEADER, 'deferred')
|
|
74
|
+
return response
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await durable.startAndEnqueue(
|
|
78
|
+
{
|
|
79
|
+
kind: run.direction === 'export' ? EXPORT_KIND : IMPORT_KIND,
|
|
80
|
+
input: { runId: run.id, batchSize: 100, direction: run.direction === 'export' ? 'export' : 'import' },
|
|
81
|
+
lockKey: syncLockKey(run.integrationId, run.entityType, run.direction),
|
|
82
|
+
idempotencyKey: syncIdempotencyKey(run.id),
|
|
83
|
+
subject: { type: 'data_sync.run', id: run.id },
|
|
84
|
+
progressJobId: run.progressJobId ?? started.progressJobId ?? null,
|
|
85
|
+
createdBy: auth.sub ?? null,
|
|
86
|
+
},
|
|
87
|
+
scope,
|
|
88
|
+
)
|
|
89
|
+
response.headers.set(DURABLE_HEADER, 'created')
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// Never fails the request. The run exists and core has already enqueued it, so the
|
|
92
|
+
// adopt-on-delivery worker will make it durable; turning that into a 500 would refuse a
|
|
93
|
+
// run that is going to happen anyway.
|
|
94
|
+
logger.warn('Could not create the durable job for a started run; the queue delivery will adopt it', {
|
|
95
|
+
runId: started.id,
|
|
96
|
+
err: error,
|
|
97
|
+
})
|
|
98
|
+
response.headers.set(DURABLE_HEADER, 'deferred')
|
|
99
|
+
response.headers.set(`${DURABLE_HEADER}-reason`, error instanceof Error ? error.message.slice(0, 200) : 'unknown')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return response
|
|
103
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/runs/[id]/cancel.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/runs/[id]/cancel'
|
|
4
|
+
export { POST } from '@open-mercato/core/modules/data_sync/api/runs/[id]/cancel'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/runs/[id]/cancel'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/runs/[id]/retry.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/runs/[id]/retry'
|
|
4
|
+
export { POST } from '@open-mercato/core/modules/data_sync/api/runs/[id]/retry'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/runs/[id]/retry'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/runs/[id]/route.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/runs/[id]/route'
|
|
4
|
+
export { GET } from '@open-mercato/core/modules/data_sync/api/runs/[id]/route'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/runs/[id]/route'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.view'] },
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/runs.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/runs'
|
|
4
|
+
export { GET } from '@open-mercato/core/modules/data_sync/api/runs'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/runs'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.view'] },
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/schedules/[id]/route.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/schedules/[id]/route'
|
|
4
|
+
export { GET, PUT, DELETE } from '@open-mercato/core/modules/data_sync/api/schedules/[id]/route'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/schedules/[id]/route'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
10
|
+
PUT: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
11
|
+
DELETE: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/schedules/route.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/schedules/route'
|
|
4
|
+
export { GET, POST } from '@open-mercato/core/modules/data_sync/api/schedules/route'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/schedules/route'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/schedules/serialize.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/schedules/serialize'
|
|
4
|
+
export { serializeSchedule } from '@open-mercato/core/modules/data_sync/api/schedules/serialize'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (api/validate.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/api/validate'
|
|
4
|
+
export { POST } from '@open-mercato/core/modules/data_sync/api/validate'
|
|
5
|
+
import { openApi as coreOpenApi } from '@open-mercato/core/modules/data_sync/api/validate'
|
|
6
|
+
export const openApi = coreOpenApi
|
|
7
|
+
// Copied from core: the generator reads this by AST and cannot follow a re-export.
|
|
8
|
+
export const metadata = {
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ['data_sync.configure'] },
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (backend/data-sync/page.meta.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/backend/data-sync/page.meta'
|
|
4
|
+
export { metadata } from '@open-mercato/core/modules/data_sync/backend/data-sync/page.meta'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (backend/data-sync/page.tsx). Do not edit.
|
|
3
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
4
|
+
export * from '@open-mercato/core/modules/data_sync/backend/data-sync/page'
|
|
5
|
+
export { default } from '@open-mercato/core/modules/data_sync/backend/data-sync/page'
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (backend/data-sync/runs/[id]/page.meta.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/backend/data-sync/runs/[id]/page.meta'
|
|
4
|
+
export { metadata } from '@open-mercato/core/modules/data_sync/backend/data-sync/runs/[id]/page.meta'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (backend/data-sync/runs/[id]/page.tsx). Do not edit.
|
|
3
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
4
|
+
export * from '@open-mercato/core/modules/data_sync/backend/data-sync/runs/[id]/page'
|
|
5
|
+
export { default } from '@open-mercato/core/modules/data_sync/backend/data-sync/runs/[id]/page'
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (components/IntegrationScheduleTab.tsx). Do not edit.
|
|
3
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
4
|
+
export * from '@open-mercato/core/modules/data_sync/components/IntegrationScheduleTab'
|
|
5
|
+
export { default } from '@open-mercato/core/modules/data_sync/components/IntegrationScheduleTab'
|
|
6
|
+
export { IntegrationScheduleTab } from '@open-mercato/core/modules/data_sync/components/IntegrationScheduleTab'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (components/RunParameterFields.tsx). Do not edit.
|
|
3
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
4
|
+
export * from '@open-mercato/core/modules/data_sync/components/RunParameterFields'
|
|
5
|
+
export { RunParameterFields, buildDefaultRunParameterValues, buildRetryFailureMessage, buildRunFailureMessage, buildRunParametersPayload, hasRequiredRunParameterWithoutDefault, resolveRunParameterText } from '@open-mercato/core/modules/data_sync/components/RunParameterFields'
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (data/entities.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/data/entities'
|
|
4
|
+
export { SyncCursor, SyncMapping, SyncRun, SyncSchedule } from '@open-mercato/core/modules/data_sync/data/entities'
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (data/validators.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/data/validators'
|
|
4
|
+
export { createSyncScheduleSchema, listSyncRunsQuerySchema, listSyncSchedulesQuerySchema, retrySyncSchema, runSyncSchema, updateSyncScheduleSchema, validateConnectionSchema } from '@open-mercato/core/modules/data_sync/data/validators'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Core's DI, plus the two job kinds a run becomes.
|
|
2
|
+
//
|
|
3
|
+
// Core's registrations are reused as they are — the engine, the run service, the mapping and
|
|
4
|
+
// schedule services are all core's, unchanged. The decoration that makes a run durable happens
|
|
5
|
+
// per slice, inside the kind, not here (see docs/adr/0004): a container-level decoration would
|
|
6
|
+
// apply to every caller of the engine, including ones not running under a lease.
|
|
7
|
+
|
|
8
|
+
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
9
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
10
|
+
import { register as registerCore } from '@open-mercato/core/modules/data_sync/di'
|
|
11
|
+
import { createSyncEngine } from '@open-mercato/core/modules/data_sync/lib/sync-engine'
|
|
12
|
+
import { registry } from '@fullstackhouse/open-mercato-durable-work'
|
|
13
|
+
|
|
14
|
+
import { dataSyncKinds } from '../../kinds/data-sync-run'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Built once, at module scope, not once per container.
|
|
18
|
+
*
|
|
19
|
+
* `register` runs on every container build — which in a web process means every request — and
|
|
20
|
+
* the registry refuses two different handlers under one kind id, because silently accepting
|
|
21
|
+
* the second would make "which code is running this job?" depend on request ordering. Kinds
|
|
22
|
+
* created per call would be a different handler every time, so the second request would throw.
|
|
23
|
+
*
|
|
24
|
+
* Each slice resolves its own container instead, which it needs anyway: slices run in the
|
|
25
|
+
* worker process, outside any request, and each needs its own EntityManager rather than one
|
|
26
|
+
* captured from whichever request happened to build the registry first.
|
|
27
|
+
*/
|
|
28
|
+
const KINDS = dataSyncKinds({
|
|
29
|
+
resolve: async () => {
|
|
30
|
+
const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')
|
|
31
|
+
const container = await createRequestContainer()
|
|
32
|
+
const em = container.resolve<EntityManager>('em')
|
|
33
|
+
return {
|
|
34
|
+
runService: container.resolve('dataSyncRunService'),
|
|
35
|
+
progressService: container.resolve('progressService'),
|
|
36
|
+
engine: ({ runService, progressService }) =>
|
|
37
|
+
createSyncEngine({
|
|
38
|
+
em,
|
|
39
|
+
syncRunService: runService as never,
|
|
40
|
+
integrationCredentialsService: container.resolve('integrationCredentialsService'),
|
|
41
|
+
integrationLogService: container.resolve('integrationLogService'),
|
|
42
|
+
integrationStateService: container.resolve('integrationStateService'),
|
|
43
|
+
progressService: progressService as never,
|
|
44
|
+
}),
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export function register(container: AppContainer) {
|
|
50
|
+
registerCore(container)
|
|
51
|
+
// Idempotent: the same handler objects every time, so re-registering is a no-op. The web
|
|
52
|
+
// process needs them to re-drive and cancel, the worker to run slices, and the reconciler to
|
|
53
|
+
// know whether a job has a handler at all.
|
|
54
|
+
for (const kind of KINDS) registry.register(kind)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default register
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (events.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/events'
|
|
4
|
+
export { default } from '@open-mercato/core/modules/data_sync/events'
|
|
5
|
+
export { emitDataSyncEvent, eventsConfig } from '@open-mercato/core/modules/data_sync/events'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-mirror.mjs from @open-mercato/core@0.7.0 (extension-points.ts). Do not edit.
|
|
2
|
+
// This package mirrors core's data_sync module file-for-file; see docs/adr/0006-drop-in-data-sync.md.
|
|
3
|
+
export * from '@open-mercato/core/modules/data_sync/extension-points'
|
|
4
|
+
export { default } from '@open-mercato/core/modules/data_sync/extension-points'
|
|
5
|
+
export { extensionPoints } from '@open-mercato/core/modules/data_sync/extension-points'
|