@pol-studios/powersync 1.0.0 → 1.0.1

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.
@@ -0,0 +1,686 @@
1
+ import {
2
+ classifySupabaseError
3
+ } from "./chunk-CHRTN5PF.js";
4
+
5
+ // src/connector/types.ts
6
+ var defaultSchemaRouter = () => "public";
7
+
8
+ // src/conflicts/detect.ts
9
+ var TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
10
+ function validateTableName(table) {
11
+ if (!TABLE_NAME_REGEX.test(table)) {
12
+ throw new Error(`Invalid table name: ${table}`);
13
+ }
14
+ }
15
+ var DEFAULT_IGNORED_FIELDS = ["updatedAt", "createdAt", "_version", "id"];
16
+ async function detectConflicts(table, recordId, localVersion, serverVersion, pendingChanges, supabase, config) {
17
+ const ignoredFields = /* @__PURE__ */ new Set([
18
+ ...DEFAULT_IGNORED_FIELDS,
19
+ ...config?.ignoredFields ?? []
20
+ ]);
21
+ const filteredPendingChanges = {};
22
+ for (const [field, value] of Object.entries(pendingChanges)) {
23
+ if (!ignoredFields.has(field)) {
24
+ filteredPendingChanges[field] = value;
25
+ }
26
+ }
27
+ if (localVersion === serverVersion) {
28
+ return {
29
+ hasConflict: false,
30
+ conflicts: [],
31
+ nonConflictingChanges: Object.keys(filteredPendingChanges),
32
+ table,
33
+ recordId
34
+ };
35
+ }
36
+ const { data: auditLogs, error } = await supabase.schema("core").from("AuditLog").select("oldRecord, newRecord, changeBy, changeAt").eq("tableName", table).eq("recordId_text", recordId).order("changeAt", { ascending: false }).limit(20);
37
+ if (error) {
38
+ console.warn("[detectConflicts] Failed to query AuditLog:", error);
39
+ return {
40
+ hasConflict: false,
41
+ conflicts: [],
42
+ nonConflictingChanges: Object.keys(filteredPendingChanges),
43
+ table,
44
+ recordId
45
+ };
46
+ }
47
+ const serverChanges = /* @__PURE__ */ new Map();
48
+ for (const log of auditLogs ?? []) {
49
+ const oldRec = log.oldRecord;
50
+ const newRec = log.newRecord;
51
+ if (!oldRec || !newRec) continue;
52
+ for (const [field, newValue] of Object.entries(newRec)) {
53
+ if (ignoredFields.has(field)) continue;
54
+ if (oldRec[field] !== newValue && !serverChanges.has(field)) {
55
+ serverChanges.set(field, {
56
+ newValue,
57
+ changedBy: log.changeBy,
58
+ changedAt: new Date(log.changeAt)
59
+ });
60
+ }
61
+ }
62
+ }
63
+ const conflicts = [];
64
+ const nonConflictingChanges = [];
65
+ for (const [field, localValue] of Object.entries(filteredPendingChanges)) {
66
+ if (serverChanges.has(field)) {
67
+ const serverChange = serverChanges.get(field);
68
+ conflicts.push({
69
+ field,
70
+ localValue,
71
+ serverValue: serverChange.newValue,
72
+ changedBy: serverChange.changedBy,
73
+ changedAt: serverChange.changedAt
74
+ });
75
+ } else {
76
+ nonConflictingChanges.push(field);
77
+ }
78
+ }
79
+ return {
80
+ hasConflict: conflicts.length > 0,
81
+ conflicts,
82
+ nonConflictingChanges,
83
+ table,
84
+ recordId
85
+ };
86
+ }
87
+ async function hasVersionColumn(table, db) {
88
+ try {
89
+ validateTableName(table);
90
+ const result = await db.getAll(
91
+ `PRAGMA table_info("${table}")`
92
+ );
93
+ return result.some((col) => col.name === "_version");
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ async function fetchServerVersion(table, recordId, schema, supabase) {
99
+ const query = schema === "public" ? supabase.from(table) : supabase.schema(schema).from(table);
100
+ const { data, error } = await query.select("_version").eq("id", recordId).single();
101
+ if (error || !data) {
102
+ return null;
103
+ }
104
+ return data._version ?? null;
105
+ }
106
+ async function getLocalVersion(table, recordId, db) {
107
+ validateTableName(table);
108
+ const result = await db.get(
109
+ `SELECT _version FROM "${table}" WHERE id = ?`,
110
+ [recordId]
111
+ );
112
+ return result?._version ?? null;
113
+ }
114
+
115
+ // src/connector/supabase-connector.ts
116
+ var SupabaseConnector = class {
117
+ supabase;
118
+ powerSyncUrl;
119
+ schemaRouter;
120
+ crudHandler;
121
+ logger;
122
+ onTransactionSuccess;
123
+ onTransactionFailure;
124
+ onTransactionComplete;
125
+ shouldUploadFn;
126
+ // Conflict detection configuration
127
+ conflictDetection;
128
+ conflictHandler;
129
+ conflictBus;
130
+ // Cache for version column existence checks (table -> hasVersionColumn)
131
+ versionColumnCache = /* @__PURE__ */ new Map();
132
+ // Active project IDs for scoped sync (optional feature)
133
+ activeProjectIds = [];
134
+ constructor(options) {
135
+ this.supabase = options.supabaseClient;
136
+ this.powerSyncUrl = options.powerSyncUrl;
137
+ this.schemaRouter = options.schemaRouter ?? defaultSchemaRouter;
138
+ this.crudHandler = options.crudHandler;
139
+ this.logger = options.logger;
140
+ this.onTransactionSuccess = options.onTransactionSuccess;
141
+ this.onTransactionFailure = options.onTransactionFailure;
142
+ this.onTransactionComplete = options.onTransactionComplete;
143
+ this.shouldUploadFn = options.shouldUpload;
144
+ this.conflictDetection = options.conflictDetection;
145
+ this.conflictHandler = options.conflictHandler;
146
+ this.conflictBus = options.conflictBus;
147
+ }
148
+ /**
149
+ * Set the active project IDs for scoped sync.
150
+ * Call this when user selects/opens projects.
151
+ */
152
+ setActiveProjectIds(projectIds) {
153
+ this.activeProjectIds = projectIds;
154
+ }
155
+ /**
156
+ * Get the current active project IDs.
157
+ */
158
+ getActiveProjectIds() {
159
+ return this.activeProjectIds;
160
+ }
161
+ /**
162
+ * Get credentials for PowerSync connection.
163
+ * Uses Supabase session token.
164
+ *
165
+ * Note: Token refresh is handled by Supabase's startAutoRefresh() which must be
166
+ * called on app initialization. getSession() returns the auto-refreshed token.
167
+ */
168
+ async fetchCredentials() {
169
+ this.logger?.debug("[Connector] Fetching credentials...");
170
+ const {
171
+ data: { session },
172
+ error
173
+ } = await this.supabase.auth.getSession();
174
+ if (error) {
175
+ this.logger?.error("[Connector] Auth error:", error);
176
+ throw new Error(`Failed to get Supabase session: ${error.message}`);
177
+ }
178
+ if (!session) {
179
+ this.logger?.error("[Connector] No active session");
180
+ throw new Error("No active Supabase session");
181
+ }
182
+ this.logger?.debug(
183
+ "[Connector] Credentials fetched, token expires at:",
184
+ session.expires_at
185
+ );
186
+ return {
187
+ endpoint: this.powerSyncUrl,
188
+ token: session.access_token,
189
+ expiresAt: session.expires_at ? new Date(session.expires_at * 1e3) : void 0
190
+ };
191
+ }
192
+ /**
193
+ * Upload local changes to Supabase.
194
+ * Called automatically by PowerSync when there are pending uploads.
195
+ *
196
+ * When conflict detection is enabled:
197
+ * 1. Checks if table has _version column (cached)
198
+ * 2. If yes, compares local vs server version
199
+ * 3. On version mismatch, queries AuditLog for field conflicts
200
+ * 4. If conflicts found, calls handler or publishes to conflict bus
201
+ * 5. Applies resolution or skips entry based on handler response
202
+ */
203
+ async uploadData(database) {
204
+ if (this.shouldUploadFn && !this.shouldUploadFn()) {
205
+ if (__DEV__) {
206
+ console.log("[Connector] Upload skipped - sync mode does not allow uploads");
207
+ }
208
+ this.logger?.debug("[Connector] Upload skipped - sync mode does not allow uploads");
209
+ return;
210
+ }
211
+ if (__DEV__) {
212
+ console.log("[Connector] uploadData called, fetching next CRUD transaction...");
213
+ }
214
+ const transaction = await database.getNextCrudTransaction();
215
+ if (!transaction) {
216
+ if (__DEV__) {
217
+ console.log("[Connector] No pending CRUD transaction found");
218
+ }
219
+ return;
220
+ }
221
+ if (__DEV__) {
222
+ console.log("[Connector] Transaction fetched:", {
223
+ crudCount: transaction.crud.length,
224
+ entries: transaction.crud.map((e) => ({
225
+ table: e.table,
226
+ op: e.op,
227
+ id: e.id,
228
+ opDataKeys: e.opData ? Object.keys(e.opData) : []
229
+ }))
230
+ });
231
+ }
232
+ const conflictDetectionEnabled = this.conflictDetection?.enabled !== false;
233
+ if (!conflictDetectionEnabled) {
234
+ await this.processTransaction(transaction, database);
235
+ return;
236
+ }
237
+ const { crud } = transaction;
238
+ const skipTables = new Set(this.conflictDetection?.skipTables ?? []);
239
+ const entriesToProcess = [];
240
+ const entriesQueuedForUI = [];
241
+ const entriesDiscarded = [];
242
+ const partialResolutions = [];
243
+ for (const entry of crud) {
244
+ if (entry.op === "DELETE") {
245
+ entriesToProcess.push(entry);
246
+ continue;
247
+ }
248
+ if (skipTables.has(entry.table)) {
249
+ entriesToProcess.push(entry);
250
+ continue;
251
+ }
252
+ const hasVersion = await this.checkVersionColumn(entry.table, database);
253
+ if (!hasVersion) {
254
+ entriesToProcess.push(entry);
255
+ continue;
256
+ }
257
+ const localVersion = await getLocalVersion(entry.table, entry.id, database);
258
+ const schema = this.schemaRouter(entry.table);
259
+ const serverVersion = await fetchServerVersion(
260
+ entry.table,
261
+ entry.id,
262
+ schema,
263
+ this.supabase
264
+ );
265
+ if (localVersion === null || serverVersion === null) {
266
+ entriesToProcess.push(entry);
267
+ continue;
268
+ }
269
+ const conflictResult = await detectConflicts(
270
+ entry.table,
271
+ entry.id,
272
+ localVersion,
273
+ serverVersion,
274
+ entry.opData ?? {},
275
+ this.supabase,
276
+ this.conflictDetection
277
+ );
278
+ if (!conflictResult.hasConflict) {
279
+ entriesToProcess.push(entry);
280
+ continue;
281
+ }
282
+ this.conflictBus?.emitConflict(conflictResult);
283
+ if (this.conflictHandler) {
284
+ const resolution = await this.conflictHandler.onConflict(conflictResult);
285
+ if (resolution === null) {
286
+ entriesQueuedForUI.push(entry);
287
+ if (__DEV__) {
288
+ console.log("[Connector] Conflict queued for UI resolution:", {
289
+ table: entry.table,
290
+ id: entry.id,
291
+ conflicts: conflictResult.conflicts.map((c) => c.field)
292
+ });
293
+ }
294
+ continue;
295
+ }
296
+ switch (resolution.action) {
297
+ case "overwrite":
298
+ entriesToProcess.push(entry);
299
+ break;
300
+ case "keep-server":
301
+ entriesDiscarded.push(entry);
302
+ if (__DEV__) {
303
+ console.log("[Connector] Conflict resolved with keep-server, discarding local changes:", {
304
+ table: entry.table,
305
+ id: entry.id
306
+ });
307
+ }
308
+ break;
309
+ case "partial":
310
+ const partialEntry = {
311
+ ...entry,
312
+ opData: this.filterFields(entry.opData ?? {}, resolution.fields)
313
+ };
314
+ entriesToProcess.push(partialEntry);
315
+ partialResolutions.push({
316
+ originalConflict: conflictResult,
317
+ syncedFields: resolution.fields
318
+ });
319
+ break;
320
+ }
321
+ } else {
322
+ console.warn("[Connector] Conflict detected but no handler:", {
323
+ table: entry.table,
324
+ id: entry.id,
325
+ conflicts: conflictResult.conflicts
326
+ });
327
+ entriesToProcess.push(entry);
328
+ }
329
+ }
330
+ if (entriesQueuedForUI.length > 0) {
331
+ if (__DEV__) {
332
+ console.log("[Connector] Entries queued for UI resolution, leaving in queue:", {
333
+ queuedForUI: entriesQueuedForUI.length,
334
+ discarded: entriesDiscarded.length,
335
+ toProcess: entriesToProcess.length
336
+ });
337
+ }
338
+ this.onTransactionComplete?.(entriesQueuedForUI);
339
+ return;
340
+ }
341
+ if (entriesToProcess.length === 0 && entriesDiscarded.length > 0) {
342
+ if (__DEV__) {
343
+ console.log("[Connector] All entries resolved with keep-server, completing transaction to discard local changes");
344
+ }
345
+ try {
346
+ await transaction.complete();
347
+ this.onTransactionSuccess?.(entriesDiscarded);
348
+ this.onTransactionComplete?.(entriesDiscarded);
349
+ } catch (error) {
350
+ const classified = classifySupabaseError(error);
351
+ this.onTransactionFailure?.(
352
+ entriesDiscarded,
353
+ error instanceof Error ? error : new Error(String(error)),
354
+ classified
355
+ );
356
+ throw error;
357
+ }
358
+ return;
359
+ }
360
+ try {
361
+ for (const entry of entriesToProcess) {
362
+ if (__DEV__) {
363
+ console.log("[Connector] Processing CRUD entry:", {
364
+ table: entry.table,
365
+ op: entry.op,
366
+ id: entry.id,
367
+ opData: entry.opData
368
+ });
369
+ }
370
+ await this.processCrudEntry(entry);
371
+ }
372
+ if (__DEV__) {
373
+ console.log("[Connector] All CRUD entries processed, completing transaction...");
374
+ }
375
+ await transaction.complete();
376
+ if (__DEV__) {
377
+ console.log("[Connector] Transaction completed successfully:", {
378
+ entriesCount: entriesToProcess.length,
379
+ discardedCount: entriesDiscarded.length
380
+ });
381
+ }
382
+ if (this.conflictBus && partialResolutions.length > 0) {
383
+ for (const { originalConflict, syncedFields } of partialResolutions) {
384
+ const syncedFieldSet = new Set(syncedFields);
385
+ const remainingConflicts = originalConflict.conflicts.filter(
386
+ (c) => !syncedFieldSet.has(c.field)
387
+ );
388
+ if (remainingConflicts.length > 0) {
389
+ if (__DEV__) {
390
+ console.log("[Connector] Re-emitting conflict for remaining fields:", {
391
+ table: originalConflict.table,
392
+ recordId: originalConflict.recordId,
393
+ syncedFields,
394
+ remainingConflictFields: remainingConflicts.map((c) => c.field)
395
+ });
396
+ }
397
+ this.conflictBus.emitConflict({
398
+ ...originalConflict,
399
+ conflicts: remainingConflicts,
400
+ // All remaining are conflicts now - clear nonConflictingChanges since
401
+ // the non-conflicting ones were already synced in the partial resolution
402
+ nonConflictingChanges: []
403
+ });
404
+ }
405
+ }
406
+ }
407
+ this.onTransactionSuccess?.(entriesToProcess);
408
+ this.onTransactionComplete?.(entriesToProcess);
409
+ } catch (error) {
410
+ const classified = classifySupabaseError(error);
411
+ console.error("[PowerSync Connector] Upload FAILED:", {
412
+ errorMessage: error instanceof Error ? error.message : String(error),
413
+ errorKeys: error && typeof error === "object" ? Object.keys(error) : [],
414
+ errorObject: JSON.stringify(error, null, 2),
415
+ classified,
416
+ isPermanent: classified.isPermanent,
417
+ entries: entriesToProcess.map((e) => ({
418
+ table: e.table,
419
+ op: e.op,
420
+ id: e.id
421
+ }))
422
+ });
423
+ this.logger?.error("[Connector] Upload error:", {
424
+ error,
425
+ classified,
426
+ entries: entriesToProcess.map((e) => ({ table: e.table, op: e.op, id: e.id }))
427
+ });
428
+ this.onTransactionFailure?.(
429
+ entriesToProcess,
430
+ error instanceof Error ? error : new Error(String(error)),
431
+ classified
432
+ );
433
+ throw error;
434
+ }
435
+ }
436
+ /**
437
+ * Process a transaction without conflict detection.
438
+ * Used when conflict detection is disabled.
439
+ */
440
+ async processTransaction(transaction, _database) {
441
+ try {
442
+ for (const entry of transaction.crud) {
443
+ if (__DEV__) {
444
+ console.log("[Connector] Processing CRUD entry:", {
445
+ table: entry.table,
446
+ op: entry.op,
447
+ id: entry.id,
448
+ opData: entry.opData
449
+ });
450
+ }
451
+ await this.processCrudEntry(entry);
452
+ }
453
+ if (__DEV__) {
454
+ console.log("[Connector] All CRUD entries processed, completing transaction...");
455
+ }
456
+ await transaction.complete();
457
+ if (__DEV__) {
458
+ console.log("[Connector] Transaction completed successfully:", {
459
+ entriesCount: transaction.crud.length
460
+ });
461
+ }
462
+ this.onTransactionSuccess?.(transaction.crud);
463
+ this.onTransactionComplete?.(transaction.crud);
464
+ } catch (error) {
465
+ const classified = classifySupabaseError(error);
466
+ console.error("[PowerSync Connector] Upload FAILED:", {
467
+ errorMessage: error instanceof Error ? error.message : String(error),
468
+ errorKeys: error && typeof error === "object" ? Object.keys(error) : [],
469
+ errorObject: JSON.stringify(error, null, 2),
470
+ classified,
471
+ isPermanent: classified.isPermanent,
472
+ entries: transaction.crud.map((e) => ({
473
+ table: e.table,
474
+ op: e.op,
475
+ id: e.id
476
+ }))
477
+ });
478
+ this.logger?.error("[Connector] Upload error:", {
479
+ error,
480
+ classified,
481
+ entries: transaction.crud.map((e) => ({ table: e.table, op: e.op, id: e.id }))
482
+ });
483
+ this.onTransactionFailure?.(
484
+ transaction.crud,
485
+ error instanceof Error ? error : new Error(String(error)),
486
+ classified
487
+ );
488
+ throw error;
489
+ }
490
+ }
491
+ /**
492
+ * Check if a table has a _version column (cached).
493
+ */
494
+ async checkVersionColumn(table, db) {
495
+ if (this.versionColumnCache.has(table)) {
496
+ return this.versionColumnCache.get(table);
497
+ }
498
+ const hasVersion = await hasVersionColumn(table, db);
499
+ this.versionColumnCache.set(table, hasVersion);
500
+ return hasVersion;
501
+ }
502
+ /**
503
+ * Filter opData to only include specified fields.
504
+ * Used for partial sync resolution.
505
+ */
506
+ filterFields(opData, fields) {
507
+ const fieldSet = new Set(fields);
508
+ const filtered = {};
509
+ for (const [key, value] of Object.entries(opData)) {
510
+ if (fieldSet.has(key)) {
511
+ filtered[key] = value;
512
+ }
513
+ }
514
+ return filtered;
515
+ }
516
+ /**
517
+ * Process a single CRUD operation.
518
+ *
519
+ * UUID-native tables (public schema, post-migration) use `id` as the UUID column.
520
+ * Core schema tables (Profile, Comment, CommentSection) still use a separate `uuid` column.
521
+ */
522
+ /**
523
+ * Process a single CRUD operation.
524
+ *
525
+ * All synced tables use `id` as their UUID primary key column.
526
+ */
527
+ async processCrudEntry(entry) {
528
+ const table = entry.table;
529
+ const id = entry.id;
530
+ const schema = this.schemaRouter(table);
531
+ if (this.crudHandler) {
532
+ let handled = false;
533
+ switch (entry.op) {
534
+ case "PUT" /* PUT */:
535
+ handled = await this.crudHandler.handlePut?.(entry, this.supabase, schema) ?? false;
536
+ break;
537
+ case "PATCH" /* PATCH */:
538
+ handled = await this.crudHandler.handlePatch?.(
539
+ entry,
540
+ this.supabase,
541
+ schema
542
+ ) ?? false;
543
+ break;
544
+ case "DELETE" /* DELETE */:
545
+ handled = await this.crudHandler.handleDelete?.(
546
+ entry,
547
+ this.supabase,
548
+ schema
549
+ ) ?? false;
550
+ break;
551
+ }
552
+ if (handled) {
553
+ this.logger?.debug(
554
+ `[Connector] Custom handler processed ${entry.op} for ${schema}.${table}`
555
+ );
556
+ return;
557
+ }
558
+ }
559
+ const query = schema === "public" ? this.supabase.from(table) : this.supabase.schema(schema).from(table);
560
+ switch (entry.op) {
561
+ case "PUT" /* PUT */:
562
+ if (__DEV__) {
563
+ console.log("[Connector] Executing PUT/UPSERT:", {
564
+ schema,
565
+ table,
566
+ id,
567
+ data: { id, ...entry.opData }
568
+ });
569
+ }
570
+ const { data: upsertData, error: upsertError } = await query.upsert(
571
+ { id, ...entry.opData },
572
+ { onConflict: "id" }
573
+ ).select();
574
+ if (upsertError) {
575
+ if (__DEV__) {
576
+ console.error("[Connector] PUT/UPSERT FAILED:", {
577
+ schema,
578
+ table,
579
+ id,
580
+ error: upsertError,
581
+ errorMessage: upsertError.message,
582
+ errorCode: upsertError.code,
583
+ errorDetails: upsertError.details,
584
+ errorHint: upsertError.hint
585
+ });
586
+ }
587
+ throw new Error(
588
+ `Upsert failed for ${schema}.${table}: ${upsertError.message}`
589
+ );
590
+ }
591
+ if (__DEV__) {
592
+ console.log("[Connector] PUT/UPSERT SUCCESS:", {
593
+ schema,
594
+ table,
595
+ id,
596
+ responseData: upsertData
597
+ });
598
+ }
599
+ break;
600
+ case "PATCH" /* PATCH */:
601
+ if (__DEV__) {
602
+ console.log("[Connector] Executing PATCH/UPDATE:", {
603
+ schema,
604
+ table,
605
+ id,
606
+ opData: entry.opData
607
+ });
608
+ }
609
+ const { data: updateData, error: updateError } = await query.update(entry.opData).eq("id", id).select();
610
+ if (updateError) {
611
+ if (__DEV__) {
612
+ console.error("[Connector] PATCH/UPDATE FAILED:", {
613
+ schema,
614
+ table,
615
+ id,
616
+ error: updateError,
617
+ errorMessage: updateError.message,
618
+ errorCode: updateError.code,
619
+ errorDetails: updateError.details,
620
+ errorHint: updateError.hint
621
+ });
622
+ }
623
+ throw new Error(
624
+ `Update failed for ${schema}.${table}: ${updateError.message}`
625
+ );
626
+ }
627
+ if (__DEV__) {
628
+ console.log("[Connector] PATCH/UPDATE SUCCESS:", {
629
+ schema,
630
+ table,
631
+ id,
632
+ responseData: updateData
633
+ });
634
+ }
635
+ break;
636
+ case "DELETE" /* DELETE */:
637
+ if (__DEV__) {
638
+ console.log("[Connector] Executing DELETE:", {
639
+ schema,
640
+ table,
641
+ id
642
+ });
643
+ }
644
+ const { data: deleteData, error: deleteError } = await query.delete().eq("id", id).select();
645
+ if (deleteError) {
646
+ if (__DEV__) {
647
+ console.error("[Connector] DELETE FAILED:", {
648
+ schema,
649
+ table,
650
+ id,
651
+ error: deleteError,
652
+ errorMessage: deleteError.message,
653
+ errorCode: deleteError.code,
654
+ errorDetails: deleteError.details,
655
+ errorHint: deleteError.hint
656
+ });
657
+ }
658
+ throw new Error(
659
+ `Delete failed for ${schema}.${table}: ${deleteError.message}`
660
+ );
661
+ }
662
+ if (__DEV__) {
663
+ console.log("[Connector] DELETE SUCCESS:", {
664
+ schema,
665
+ table,
666
+ id,
667
+ responseData: deleteData
668
+ });
669
+ }
670
+ break;
671
+ }
672
+ this.logger?.debug(
673
+ `[Connector] Processed ${entry.op} for ${schema}.${table} (id: ${id})`
674
+ );
675
+ }
676
+ };
677
+
678
+ export {
679
+ defaultSchemaRouter,
680
+ detectConflicts,
681
+ hasVersionColumn,
682
+ fetchServerVersion,
683
+ getLocalVersion,
684
+ SupabaseConnector
685
+ };
686
+ //# sourceMappingURL=chunk-MB2RC3NS.js.map