@pattern-stack/codegen 0.4.1 → 0.4.2

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 (133) hide show
  1. package/package.json +2 -1
  2. package/runtime/analytics/index.ts +31 -0
  3. package/runtime/analytics/metrics.ts +85 -0
  4. package/runtime/analytics/packs/crm-entity-measures.ts +20 -0
  5. package/runtime/analytics/packs/index.ts +5 -0
  6. package/runtime/analytics/packs/monetary-measures.ts +20 -0
  7. package/runtime/analytics/specs.ts +54 -0
  8. package/runtime/analytics/types.ts +105 -0
  9. package/runtime/base-classes/activity-entity-repository.ts +50 -0
  10. package/runtime/base-classes/activity-entity-service.ts +48 -0
  11. package/runtime/base-classes/base-read-use-cases.ts +88 -0
  12. package/runtime/base-classes/base-repository.ts +289 -0
  13. package/runtime/base-classes/base-service.ts +183 -0
  14. package/runtime/base-classes/index.ts +38 -0
  15. package/runtime/base-classes/knowledge-entity-repository.ts +12 -0
  16. package/runtime/base-classes/knowledge-entity-service.ts +14 -0
  17. package/runtime/base-classes/lifecycle-events.ts +152 -0
  18. package/runtime/base-classes/metadata-entity-repository.ts +80 -0
  19. package/runtime/base-classes/metadata-entity-service.ts +48 -0
  20. package/runtime/base-classes/synced-entity-repository.ts +57 -0
  21. package/runtime/base-classes/synced-entity-service.ts +50 -0
  22. package/runtime/base-classes/with-analytics.ts +22 -0
  23. package/runtime/constants/tokens.ts +29 -0
  24. package/runtime/eav-helpers.ts +74 -0
  25. package/runtime/pipes/zod-validation.pipe.ts +64 -0
  26. package/runtime/shared/openapi/error-response.dto.ts +24 -0
  27. package/runtime/shared/openapi/errors.ts +39 -0
  28. package/runtime/shared/openapi/index.ts +20 -0
  29. package/runtime/shared/openapi/registry.tokens.ts +13 -0
  30. package/runtime/shared/openapi/registry.ts +151 -0
  31. package/runtime/subsystems/analytics/analytics-query.protocol.ts +37 -0
  32. package/runtime/subsystems/analytics/analytics.module.ts +64 -0
  33. package/runtime/subsystems/analytics/analytics.tokens.ts +24 -0
  34. package/runtime/subsystems/analytics/cube-backend.ts +75 -0
  35. package/runtime/subsystems/analytics/index.ts +15 -0
  36. package/runtime/subsystems/analytics/noop-backend.ts +27 -0
  37. package/runtime/subsystems/auth/auth.module.ts +91 -0
  38. package/runtime/subsystems/auth/auth.tokens.ts +27 -0
  39. package/runtime/subsystems/auth/backends/encryption-key/env.ts +76 -0
  40. package/runtime/subsystems/auth/backends/oauth-state-store/in-memory.ts +42 -0
  41. package/runtime/subsystems/auth/index.ts +77 -0
  42. package/runtime/subsystems/auth/protocols/auth-strategy.ts +46 -0
  43. package/runtime/subsystems/auth/protocols/encryption-key.ts +21 -0
  44. package/runtime/subsystems/auth/protocols/integration-store.ts +66 -0
  45. package/runtime/subsystems/auth/protocols/oauth-state-store.ts +16 -0
  46. package/runtime/subsystems/auth/runtime/integration-broken.error.ts +21 -0
  47. package/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.ts +189 -0
  48. package/runtime/subsystems/auth/runtime/session-expired.error.ts +39 -0
  49. package/runtime/subsystems/auth/runtime/with-auth-retry.ts +50 -0
  50. package/runtime/subsystems/bridge/assert-tenant-id.ts +57 -0
  51. package/runtime/subsystems/bridge/bridge-delivery-handler.ts +220 -0
  52. package/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.ts +149 -0
  53. package/runtime/subsystems/bridge/bridge-delivery.memory-backend.ts +140 -0
  54. package/runtime/subsystems/bridge/bridge-delivery.schema.ts +142 -0
  55. package/runtime/subsystems/bridge/bridge-errors.ts +112 -0
  56. package/runtime/subsystems/bridge/bridge-outbox-drain-hook.ts +175 -0
  57. package/runtime/subsystems/bridge/bridge.module.ts +160 -0
  58. package/runtime/subsystems/bridge/bridge.protocol.ts +351 -0
  59. package/runtime/subsystems/bridge/bridge.tokens.ts +68 -0
  60. package/runtime/subsystems/bridge/event-flow.service.ts +175 -0
  61. package/runtime/subsystems/bridge/generated/.gitkeep +0 -0
  62. package/runtime/subsystems/bridge/generated/registry.ts +6 -0
  63. package/runtime/subsystems/bridge/index.ts +84 -0
  64. package/runtime/subsystems/bridge/reserved-pools.ts +36 -0
  65. package/runtime/subsystems/cache/cache.drizzle-backend.ts +150 -0
  66. package/runtime/subsystems/cache/cache.memory-backend.ts +116 -0
  67. package/runtime/subsystems/cache/cache.module.ts +115 -0
  68. package/runtime/subsystems/cache/cache.protocol.ts +45 -0
  69. package/runtime/subsystems/cache/cache.schema.ts +27 -0
  70. package/runtime/subsystems/cache/cache.tokens.ts +17 -0
  71. package/runtime/subsystems/cache/index.ts +22 -0
  72. package/runtime/subsystems/events/domain-events.schema.ts +77 -0
  73. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +327 -0
  74. package/runtime/subsystems/events/event-bus.memory-backend.ts +142 -0
  75. package/runtime/subsystems/events/event-bus.protocol.ts +86 -0
  76. package/runtime/subsystems/events/event-bus.redis-backend.ts +304 -0
  77. package/runtime/subsystems/events/events-errors.ts +30 -0
  78. package/runtime/subsystems/events/events.module.ts +230 -0
  79. package/runtime/subsystems/events/events.tokens.ts +62 -0
  80. package/runtime/subsystems/events/generated/bus.ts +103 -0
  81. package/runtime/subsystems/events/generated/index.ts +7 -0
  82. package/runtime/subsystems/events/generated/registry.ts +84 -0
  83. package/runtime/subsystems/events/generated/schemas.ts +59 -0
  84. package/runtime/subsystems/events/generated/types.ts +94 -0
  85. package/runtime/subsystems/events/index.ts +21 -0
  86. package/runtime/subsystems/index.ts +63 -0
  87. package/runtime/subsystems/jobs/generated/job-orchestration.schema.multi-tenant.ts +217 -0
  88. package/runtime/subsystems/jobs/generated/job-orchestration.schema.single-tenant.ts +217 -0
  89. package/runtime/subsystems/jobs/generated/scope-entity-type.ts +10 -0
  90. package/runtime/subsystems/jobs/index.ts +120 -0
  91. package/runtime/subsystems/jobs/job-handler.base.ts +206 -0
  92. package/runtime/subsystems/jobs/job-orchestration.schema.ts +217 -0
  93. package/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts +536 -0
  94. package/runtime/subsystems/jobs/job-orchestrator.memory-backend.ts +850 -0
  95. package/runtime/subsystems/jobs/job-orchestrator.protocol.ts +179 -0
  96. package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +171 -0
  97. package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +165 -0
  98. package/runtime/subsystems/jobs/job-run-service.protocol.ts +79 -0
  99. package/runtime/subsystems/jobs/job-step-service.drizzle-backend.ts +66 -0
  100. package/runtime/subsystems/jobs/job-step-service.memory-backend.ts +119 -0
  101. package/runtime/subsystems/jobs/job-step-service.protocol.ts +53 -0
  102. package/runtime/subsystems/jobs/job-worker.module.ts +302 -0
  103. package/runtime/subsystems/jobs/job-worker.ts +615 -0
  104. package/runtime/subsystems/jobs/jobs-domain.module.ts +119 -0
  105. package/runtime/subsystems/jobs/jobs-domain.tokens.ts +30 -0
  106. package/runtime/subsystems/jobs/jobs-errors.ts +150 -0
  107. package/runtime/subsystems/jobs/memory-job-store.ts +35 -0
  108. package/runtime/subsystems/jobs/pool-config.loader.ts +218 -0
  109. package/runtime/subsystems/storage/index.ts +18 -0
  110. package/runtime/subsystems/storage/storage.local-backend.ts +113 -0
  111. package/runtime/subsystems/storage/storage.memory-backend.ts +78 -0
  112. package/runtime/subsystems/storage/storage.module.ts +60 -0
  113. package/runtime/subsystems/storage/storage.protocol.ts +78 -0
  114. package/runtime/subsystems/storage/storage.tokens.ts +9 -0
  115. package/runtime/subsystems/storage/storage.utils.ts +20 -0
  116. package/runtime/subsystems/sync/deep-equal.differ.ts +198 -0
  117. package/runtime/subsystems/sync/execute-sync.use-case.ts +334 -0
  118. package/runtime/subsystems/sync/index.ts +98 -0
  119. package/runtime/subsystems/sync/sync-audit.schema.ts +300 -0
  120. package/runtime/subsystems/sync/sync-change-source.protocol.ts +99 -0
  121. package/runtime/subsystems/sync/sync-cursor-store.drizzle-backend.ts +104 -0
  122. package/runtime/subsystems/sync/sync-cursor-store.memory-backend.ts +64 -0
  123. package/runtime/subsystems/sync/sync-cursor-store.protocol.ts +53 -0
  124. package/runtime/subsystems/sync/sync-errors.ts +54 -0
  125. package/runtime/subsystems/sync/sync-field-diff.protocol.ts +61 -0
  126. package/runtime/subsystems/sync/sync-loopback.protocol.ts +33 -0
  127. package/runtime/subsystems/sync/sync-run-recorder.drizzle-backend.ts +123 -0
  128. package/runtime/subsystems/sync/sync-run-recorder.memory-backend.ts +143 -0
  129. package/runtime/subsystems/sync/sync-run-recorder.protocol.ts +86 -0
  130. package/runtime/subsystems/sync/sync-sink.protocol.ts +55 -0
  131. package/runtime/subsystems/sync/sync.module.ts +156 -0
  132. package/runtime/subsystems/sync/sync.tokens.ts +57 -0
  133. package/runtime/types/drizzle.ts +23 -0
@@ -0,0 +1,334 @@
1
+ /**
2
+ * ExecuteSyncUseCase — the generic sync orchestrator (SYNC-5).
3
+ *
4
+ * One class. Reused across every `(provider, detection-mode, canonical-entity)`
5
+ * tuple. Parameterized over `T` so canonical records stay typed end-to-end.
6
+ *
7
+ * Flow per run:
8
+ *
9
+ * 1. `recorder.startRun(...)` — opens a `sync_runs` row in 'running'.
10
+ * 2. for each change yielded by `source.listChanges(subscription)`:
11
+ * a. if loopback store says "echo of own write" → skip, record
12
+ * as `status: 'skipped'`, `operation: 'noop'`, changedFields `{}`.
13
+ * b. differ.diff(existing, incoming) → 'noop' short-circuits to
14
+ * a noop audit row (no sink write).
15
+ * c. sink.upsertByExternalId / softDeleteByExternalId → records
16
+ * the local id on the audit row.
17
+ * d. per-item try/catch — a failed item increments the failed
18
+ * counter and records `status: 'failed'` with `error`, but
19
+ * does NOT abort the run.
20
+ * e. advance `latestCursor = change.cursor` as the iterator moves.
21
+ * 3. `cursors.put(subscription.id, latestCursor)` when the loop completes
22
+ * AND at least one cursor advance happened. On exceptions from the
23
+ * source iterator (auth expiry, network error), we persist the
24
+ * last-good cursor so the next run resumes from the last known
25
+ * successful position.
26
+ * 4. `finally { recorder.completeRun(...) }` — always terminates the run.
27
+ *
28
+ * ## Generics
29
+ *
30
+ * - `T` = canonical record shape from the adapter side. Same `T` flows
31
+ * through `IChangeSource<T>`, `IFieldDiffer<T>`, `ISyncSink<T>`.
32
+ *
33
+ * ## No CRM bleed
34
+ *
35
+ * Per the SYNC-5 issue's extraction notes (HS-9 finding), this orchestrator
36
+ * is strictly provider-agnostic:
37
+ * - `entityType` is `string` throughout; no `'opportunity' | 'account' | ...`
38
+ * narrowing leaks into the use case
39
+ * - `loopback.isEchoOfOwnWrite` is typed against the same `T`, not a CRM
40
+ * union
41
+ * - dealbrain's `SyncRunRecorderService` class injection replaced with the
42
+ * `ISyncRunRecorder` protocol (backend lands in SYNC-4)
43
+ */
44
+ import { Inject, Injectable, Logger, Optional } from '@nestjs/common';
45
+ import type { IChangeSource, Change } from './sync-change-source.protocol';
46
+ import type { ICursorStore } from './sync-cursor-store.protocol';
47
+ import type { IFieldDiffer, FieldDiff } from './sync-field-diff.protocol';
48
+ import type { ISyncSink } from './sync-sink.protocol';
49
+ import type { ISyncRunRecorder } from './sync-run-recorder.protocol';
50
+ import type { ILoopbackFingerprintStore } from './sync-loopback.protocol';
51
+ import { assertTenantId } from './sync-errors';
52
+ import {
53
+ SYNC_CHANGE_SOURCE,
54
+ SYNC_CURSOR_STORE,
55
+ SYNC_FIELD_DIFFER,
56
+ SYNC_LOOPBACK_FINGERPRINT_STORE,
57
+ SYNC_MULTI_TENANT,
58
+ SYNC_RUN_RECORDER,
59
+ SYNC_SINK,
60
+ } from './sync.tokens';
61
+
62
+ // ============================================================================
63
+ // Inputs + result
64
+ // ============================================================================
65
+
66
+ export interface ExecuteSyncInput<T> {
67
+ /** The subscription whose cursor/identity frames this run. */
68
+ readonly subscription: {
69
+ readonly id: string;
70
+ readonly domain: string; // entityType — used on audit rows
71
+ readonly externalRef?: string | null;
72
+ };
73
+ /** Per-run user context; threaded through sink writes. */
74
+ readonly userId: string;
75
+ /** Provider label persisted on saved rows, e.g. `'salesforce-crm'`. */
76
+ readonly provider: string;
77
+ /** Run direction — almost always `'inbound'`. Reserved for writeback. */
78
+ readonly direction: 'inbound' | 'outbound';
79
+ /** Detection mode — maps 1:1 to `sync_runs.action`. */
80
+ readonly action: 'poll' | 'cdc' | 'webhook' | 'manual' | 'writeback';
81
+ /** Multi-tenant deployments pass the tenant id through. */
82
+ readonly tenantId?: string | null;
83
+ /**
84
+ * Optional override — inject a specific change source for this run when
85
+ * the DI-bound source is not the one to use (e.g. manual backfill with
86
+ * a custom cursor). Defaults to the DI-resolved `SYNC_CHANGE_SOURCE`.
87
+ */
88
+ readonly sourceOverride?: IChangeSource<T>;
89
+ }
90
+
91
+ export interface ExecuteSyncResult {
92
+ readonly runId: string;
93
+ readonly status: 'success' | 'no_changes' | 'failed';
94
+ readonly recordsFound: number;
95
+ readonly recordsProcessed: number;
96
+ readonly recordsFailed: number;
97
+ readonly cursorBefore: unknown | null;
98
+ readonly cursorAfter: unknown | null;
99
+ readonly durationMs: number;
100
+ readonly error?: string | null;
101
+ }
102
+
103
+ // ============================================================================
104
+ // ExecuteSyncUseCase
105
+ // ============================================================================
106
+
107
+ @Injectable()
108
+ export class ExecuteSyncUseCase<T extends Record<string, unknown>> {
109
+ private readonly logger = new Logger(ExecuteSyncUseCase.name);
110
+
111
+ constructor(
112
+ @Inject(SYNC_CHANGE_SOURCE) private readonly source: IChangeSource<T>,
113
+ @Inject(SYNC_CURSOR_STORE) private readonly cursors: ICursorStore,
114
+ @Inject(SYNC_FIELD_DIFFER) private readonly differ: IFieldDiffer<T>,
115
+ @Inject(SYNC_SINK) private readonly sink: ISyncSink<T>,
116
+ @Inject(SYNC_RUN_RECORDER) private readonly recorder: ISyncRunRecorder,
117
+ @Optional()
118
+ @Inject(SYNC_LOOPBACK_FINGERPRINT_STORE)
119
+ private readonly loopback?: ILoopbackFingerprintStore<T>,
120
+ @Optional()
121
+ @Inject(SYNC_MULTI_TENANT)
122
+ private readonly multiTenant: boolean = false,
123
+ ) {}
124
+
125
+ async execute(input: ExecuteSyncInput<T>): Promise<ExecuteSyncResult> {
126
+ // Defense-in-depth tenancy guard — fire BEFORE startRun so a rejected
127
+ // input never leaves a dangling `status=running` row. Backends also
128
+ // enforce (SYNC-4), but failing fast at the orchestrator boundary is
129
+ // cheaper for observability, metrics, and manual cleanup.
130
+ assertTenantId(input.tenantId, {
131
+ multiTenant: this.multiTenant,
132
+ operation: 'execute',
133
+ });
134
+
135
+ const source = input.sourceOverride ?? this.source;
136
+ const startedAt = Date.now();
137
+ const cursorBefore = await this.cursors.get(input.subscription.id, input.tenantId);
138
+
139
+ const { id: runId } = await this.recorder.startRun({
140
+ subscriptionId: input.subscription.id,
141
+ direction: input.direction,
142
+ action: input.action,
143
+ cursorBefore,
144
+ tenantId: input.tenantId,
145
+ });
146
+
147
+ let recordsFound = 0;
148
+ let recordsProcessed = 0;
149
+ let recordsFailed = 0;
150
+ let latestCursor: unknown | null = cursorBefore;
151
+ let cursorAdvanced = false;
152
+ let runError: string | null = null;
153
+ let status: 'success' | 'no_changes' | 'failed' = 'no_changes';
154
+
155
+ try {
156
+ for await (const change of source.listChanges(input.subscription)) {
157
+ recordsFound++;
158
+ latestCursor = change.cursor;
159
+ cursorAdvanced = true;
160
+
161
+ try {
162
+ await this.processChange(runId, input, change);
163
+ recordsProcessed++;
164
+ } catch (err) {
165
+ recordsFailed++;
166
+ const message = err instanceof Error ? err.message : String(err);
167
+ this.logger.warn(
168
+ `sync item failed: subscription=${input.subscription.id} externalId=${change.externalId}: ${message}`,
169
+ );
170
+ await this.recorder.recordItem({
171
+ syncRunId: runId,
172
+ entityType: input.subscription.domain,
173
+ externalId: change.externalId,
174
+ operation: change.operation === 'deleted' ? 'deleted' : 'updated',
175
+ status: 'failed',
176
+ changedFields: {},
177
+ error: message,
178
+ tenantId: input.tenantId,
179
+ });
180
+ }
181
+ }
182
+
183
+ if (recordsFailed > 0 && recordsProcessed === 0 && recordsFound > 0) {
184
+ // Every record we saw failed — call the run a failure, not a
185
+ // success. Partial success (some processed, some failed) still
186
+ // counts as 'success' so the cursor advances.
187
+ status = 'failed';
188
+ runError = `all ${recordsFailed} records failed`;
189
+ } else if (recordsFound === 0) {
190
+ status = 'no_changes';
191
+ } else {
192
+ status = 'success';
193
+ }
194
+ } catch (err) {
195
+ // Source iterator itself threw — cursor DOES NOT advance past the
196
+ // last-successful cursor. `latestCursor` still holds the last
197
+ // `change.cursor` we observed, which is the furthest we know to
198
+ // have delivered. Persist it (below) so next run resumes there.
199
+ status = 'failed';
200
+ runError = err instanceof Error ? err.message : String(err);
201
+ this.logger.error(
202
+ `sync source failed: subscription=${input.subscription.id}: ${runError}`,
203
+ );
204
+ }
205
+
206
+ // Persist cursor advance only when something actually moved. Never
207
+ // overwrite a valid cursor with `null` on a no-change run.
208
+ if (cursorAdvanced && latestCursor !== null && latestCursor !== undefined) {
209
+ try {
210
+ await this.cursors.put(input.subscription.id, latestCursor, input.tenantId);
211
+ } catch (err) {
212
+ const message = err instanceof Error ? err.message : String(err);
213
+ this.logger.error(
214
+ `cursor put failed: subscription=${input.subscription.id}: ${message}`,
215
+ );
216
+ if (status !== 'failed') {
217
+ status = 'failed';
218
+ runError = `cursor put failed: ${message}`;
219
+ }
220
+ }
221
+ }
222
+
223
+ const durationMs = Date.now() - startedAt;
224
+
225
+ await this.recorder.completeRun(runId, {
226
+ status,
227
+ recordsFound,
228
+ recordsProcessed,
229
+ cursorAfter: cursorAdvanced ? latestCursor : cursorBefore,
230
+ durationMs,
231
+ error: runError,
232
+ });
233
+
234
+ return {
235
+ runId,
236
+ status,
237
+ recordsFound,
238
+ recordsProcessed,
239
+ recordsFailed,
240
+ cursorBefore,
241
+ cursorAfter: cursorAdvanced ? latestCursor : cursorBefore,
242
+ durationMs,
243
+ error: runError,
244
+ };
245
+ }
246
+
247
+ private async processChange(
248
+ runId: string,
249
+ input: ExecuteSyncInput<T>,
250
+ change: Change<T>,
251
+ ): Promise<void> {
252
+ // Loopback filter — skip echoes of our own outbound writes.
253
+ if (this.loopback) {
254
+ const isEcho = await this.loopback.isEchoOfOwnWrite(
255
+ input.subscription.domain,
256
+ change.externalId,
257
+ change.record,
258
+ );
259
+ if (isEcho) {
260
+ await this.recorder.recordItem({
261
+ syncRunId: runId,
262
+ entityType: input.subscription.domain,
263
+ externalId: change.externalId,
264
+ operation: 'noop',
265
+ status: 'skipped',
266
+ changedFields: {},
267
+ tenantId: input.tenantId,
268
+ });
269
+ return;
270
+ }
271
+ }
272
+
273
+ // Deletion branch — no diff, no upsert; soft-delete via sink.
274
+ if (change.operation === 'deleted') {
275
+ const result = await this.sink.softDeleteByExternalId(
276
+ input.userId,
277
+ change.externalId,
278
+ );
279
+ await this.recorder.recordItem({
280
+ syncRunId: runId,
281
+ entityType: input.subscription.domain,
282
+ externalId: change.externalId,
283
+ localId: result?.id ?? null,
284
+ operation: result ? 'deleted' : 'noop',
285
+ status: 'success',
286
+ changedFields: {},
287
+ tenantId: input.tenantId,
288
+ });
289
+ return;
290
+ }
291
+
292
+ // Create/update path — diff against local state, short-circuit on noop.
293
+ const existing = await this.sink.findByExternalId(
294
+ input.userId,
295
+ change.externalId,
296
+ );
297
+ const diff = this.differ.diff(
298
+ existing,
299
+ change.record,
300
+ change.providerChangedFields,
301
+ );
302
+
303
+ if (diff === 'noop') {
304
+ await this.recorder.recordItem({
305
+ syncRunId: runId,
306
+ entityType: input.subscription.domain,
307
+ externalId: change.externalId,
308
+ localId: null,
309
+ operation: 'noop',
310
+ status: 'success',
311
+ changedFields: {},
312
+ tenantId: input.tenantId,
313
+ });
314
+ return;
315
+ }
316
+
317
+ const { id: localId } = await this.sink.upsertByExternalId(
318
+ input.userId,
319
+ change.record,
320
+ input.provider,
321
+ );
322
+
323
+ await this.recorder.recordItem({
324
+ syncRunId: runId,
325
+ entityType: input.subscription.domain,
326
+ externalId: change.externalId,
327
+ localId,
328
+ operation: existing === null ? 'created' : 'updated',
329
+ status: 'success',
330
+ changedFields: diff as FieldDiff,
331
+ tenantId: input.tenantId,
332
+ });
333
+ }
334
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Sync subsystem — public API
3
+ *
4
+ * Slices landed so far:
5
+ * - SYNC-2 — protocols + DI tokens (#134)
6
+ * - SYNC-1 — Drizzle audit-table schemas (#148)
7
+ * - SYNC-3 — MemoryCursorStore (#149)
8
+ * - SYNC-5 — ExecuteSyncUseCase + DeepEqualDiffer + recorder/loopback protocols (#150)
9
+ * - SYNC-4 — Drizzle backends (#151)
10
+ * - SYNC-6 — SyncModule + MemoryRunRecorder + multi-tenancy opt-in (this slice)
11
+ *
12
+ * Scaffold templates (SYNC-7) and docs/skills (SYNC-8) land in their own
13
+ * PRs. See epic #60.
14
+ */
15
+
16
+ // Protocols
17
+ export type {
18
+ Change,
19
+ ChangeSource,
20
+ IChangeSource,
21
+ SyncSubscriptionView,
22
+ } from './sync-change-source.protocol';
23
+ export type { ICursorStore } from './sync-cursor-store.protocol';
24
+ export type {
25
+ DiffResult,
26
+ FieldDiff,
27
+ FieldDiffValue,
28
+ IFieldDiffer,
29
+ } from './sync-field-diff.protocol';
30
+ export {
31
+ FieldDiffSchema,
32
+ FieldDiffValueSchema,
33
+ } from './sync-field-diff.protocol';
34
+ export type { ISyncSink } from './sync-sink.protocol';
35
+ export type {
36
+ CompleteRunInput,
37
+ ISyncRunRecorder,
38
+ RecordItemInput,
39
+ StartRunInput,
40
+ } from './sync-run-recorder.protocol';
41
+ export type { ILoopbackFingerprintStore } from './sync-loopback.protocol';
42
+
43
+ // Tokens
44
+ export {
45
+ SYNC_CHANGE_SOURCE,
46
+ SYNC_CURSOR_STORE,
47
+ SYNC_FIELD_DIFFER,
48
+ SYNC_LOOPBACK_FINGERPRINT_STORE,
49
+ SYNC_MODULE_OPTIONS,
50
+ SYNC_MULTI_TENANT,
51
+ SYNC_RUN_RECORDER,
52
+ SYNC_SINK,
53
+ } from './sync.tokens';
54
+
55
+ // Errors + shared guards
56
+ export { MissingTenantIdError, assertTenantId } from './sync-errors';
57
+
58
+ // Audit schemas (SYNC-1) — Drizzle pgTable declarations + row types + enums
59
+ export {
60
+ syncSubscriptions,
61
+ syncRuns,
62
+ syncRunItems,
63
+ syncRunDirectionEnum,
64
+ syncRunActionEnum,
65
+ syncRunStatusEnum,
66
+ syncRunItemOperationEnum,
67
+ syncRunItemStatusEnum,
68
+ } from './sync-audit.schema';
69
+ export type {
70
+ SyncSubscriptionRow,
71
+ SyncRunRow,
72
+ SyncRunItemRow,
73
+ } from './sync-audit.schema';
74
+
75
+ // Memory backends (SYNC-3, SYNC-6) — test doubles
76
+ export { MemoryCursorStore } from './sync-cursor-store.memory-backend';
77
+ export {
78
+ MemoryRunRecorder,
79
+ type MemoryRunRecord,
80
+ } from './sync-run-recorder.memory-backend';
81
+
82
+ // Runtime (SYNC-5) — orchestrator + default differ
83
+ export {
84
+ DeepEqualDiffer,
85
+ type DeepEqualDifferOptions,
86
+ } from './deep-equal.differ';
87
+ export {
88
+ ExecuteSyncUseCase,
89
+ type ExecuteSyncInput,
90
+ type ExecuteSyncResult,
91
+ } from './execute-sync.use-case';
92
+
93
+ // Drizzle backends (SYNC-4)
94
+ export { PostgresCursorStore } from './sync-cursor-store.drizzle-backend';
95
+ export { DrizzleSyncRunRecorder } from './sync-run-recorder.drizzle-backend';
96
+
97
+ // Module (SYNC-6)
98
+ export { SyncModule, type SyncModuleOptions } from './sync.module';