@pol-studios/db 1.0.51 → 1.0.53

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.
@@ -4143,334 +4143,6 @@ var ADAPTER_STRATEGIES = {
4143
4143
  AUTO: "auto"
4144
4144
  };
4145
4145
 
4146
- // src/adapters/auto-detector.ts
4147
- var BackendStatus = /* @__PURE__ */ ((BackendStatus2) => {
4148
- BackendStatus2["AVAILABLE"] = "available";
4149
- BackendStatus2["INITIALIZING"] = "initializing";
4150
- BackendStatus2["UNAVAILABLE"] = "unavailable";
4151
- return BackendStatus2;
4152
- })(BackendStatus || {});
4153
- var AdapterAutoDetector = class {
4154
- constructor(powerSyncDb, supabase, options = {}) {
4155
- this.powerSyncDb = powerSyncDb;
4156
- this.supabase = supabase;
4157
- this.options = {
4158
- preferPowerSync: options.preferPowerSync ?? true,
4159
- statusCheckTimeout: options.statusCheckTimeout ?? 1e3,
4160
- useOnlineUntilSynced: options.useOnlineUntilSynced ?? true
4161
- };
4162
- }
4163
- options;
4164
- listeners = /* @__PURE__ */ new Set();
4165
- lastResult = null;
4166
- syncStatus = null;
4167
- /**
4168
- * Update the PowerSync database reference.
4169
- * Called when PowerSync becomes available after initial construction.
4170
- *
4171
- * @param db - PowerSync database instance or null
4172
- */
4173
- setPowerSyncDb(db) {
4174
- this.powerSyncDb = db;
4175
- if (db) {
4176
- this.detect();
4177
- }
4178
- }
4179
- /**
4180
- * Update the sync status from PowerSync.
4181
- * Called when sync status changes to re-evaluate backend recommendation.
4182
- *
4183
- * @param status - Current sync status or null if not available
4184
- */
4185
- updateSyncStatus(status) {
4186
- const hadSynced = this.syncStatus?.hasSynced;
4187
- const hasSyncedNow = status?.hasSynced;
4188
- const changed = hadSynced !== hasSyncedNow;
4189
- this.syncStatus = status;
4190
- if (changed) {
4191
- this.detect();
4192
- }
4193
- }
4194
- /**
4195
- * Get current sync status.
4196
- * @returns Current sync status or null
4197
- */
4198
- getSyncStatus() {
4199
- return this.syncStatus;
4200
- }
4201
- // ===========================================================================
4202
- // Main Detection Methods
4203
- // ===========================================================================
4204
- /**
4205
- * Detect backend availability and recommend best option.
4206
- *
4207
- * The detection logic follows this priority:
4208
- * 1. If preferPowerSync is true and PowerSync is available, use PowerSync
4209
- * 2. If PowerSync is initializing and online with Supabase available, use Supabase temporarily
4210
- * 3. If online with Supabase available, use Supabase
4211
- * 4. If offline but PowerSync available, use PowerSync (offline mode)
4212
- * 5. If offline and PowerSync exists (even if initializing), use PowerSync local data
4213
- * 6. Default to Supabase as fallback
4214
- *
4215
- * @returns Detection result with recommendation and reasoning
4216
- */
4217
- detect() {
4218
- const result = this.performDetection();
4219
- if (this.hasResultChanged(result)) {
4220
- this.lastResult = result;
4221
- this.notifyListeners(result);
4222
- }
4223
- return result;
4224
- }
4225
- /**
4226
- * Detect backend availability WITHOUT notifying listeners.
4227
- *
4228
- * This method is safe to call during React's render phase because it does not
4229
- * trigger state updates in other components. Use this method when you need to
4230
- * check backend status synchronously during render (e.g., in getAdapter()).
4231
- *
4232
- * The regular detect() method with listener notifications should only be called
4233
- * from useEffect or event handlers to avoid the React warning:
4234
- * "Cannot update a component while rendering a different component"
4235
- *
4236
- * @returns Detection result with recommendation and reasoning
4237
- */
4238
- detectSilent() {
4239
- const result = this.performDetection();
4240
- this.lastResult = result;
4241
- return result;
4242
- }
4243
- /**
4244
- * Internal detection logic shared by detect() and detectSilent().
4245
- * @private
4246
- */
4247
- performDetection() {
4248
- const powerSyncStatus = this.detectPowerSyncStatus();
4249
- const supabaseStatus = this.detectSupabaseStatus();
4250
- const isOnline = this.checkOnlineStatus();
4251
- let recommendedBackend;
4252
- let reason;
4253
- if (this.options.preferPowerSync && powerSyncStatus === "available" /* AVAILABLE */) {
4254
- recommendedBackend = "powersync";
4255
- reason = "PowerSync is available and preferred for offline-first experience";
4256
- } else if (powerSyncStatus === "initializing" /* INITIALIZING */ && isOnline && supabaseStatus === "available" /* AVAILABLE */) {
4257
- recommendedBackend = "supabase";
4258
- reason = "PowerSync initial sync in progress; using Supabase for fresh data";
4259
- } else if (supabaseStatus === "available" /* AVAILABLE */ && isOnline) {
4260
- recommendedBackend = "supabase";
4261
- reason = "Using Supabase direct connection";
4262
- } else if (powerSyncStatus === "available" /* AVAILABLE */) {
4263
- recommendedBackend = "powersync";
4264
- reason = "Offline mode using PowerSync local data";
4265
- } else if (!isOnline && this.powerSyncDb) {
4266
- recommendedBackend = "powersync";
4267
- reason = "Offline mode - using PowerSync local data (initial sync may be incomplete)";
4268
- } else {
4269
- recommendedBackend = "supabase";
4270
- reason = "No confirmed available backend; defaulting to Supabase";
4271
- }
4272
- return {
4273
- powerSyncStatus,
4274
- supabaseStatus,
4275
- recommendedBackend,
4276
- isOnline,
4277
- reason
4278
- };
4279
- }
4280
- /**
4281
- * Check if PowerSync is available.
4282
- *
4283
- * PowerSync status depends on:
4284
- * 1. Database instance exists
4285
- * 2. If useOnlineUntilSynced is true, also checks if initial sync completed
4286
- *
4287
- * @returns PowerSync backend status
4288
- */
4289
- detectPowerSyncStatus() {
4290
- if (!this.powerSyncDb) {
4291
- return "unavailable" /* UNAVAILABLE */;
4292
- }
4293
- try {
4294
- if (typeof this.powerSyncDb.getAll !== "function") {
4295
- return "initializing" /* INITIALIZING */;
4296
- }
4297
- if (this.options.useOnlineUntilSynced) {
4298
- if (!this.syncStatus || !this.syncStatus.hasSynced) {
4299
- return "initializing" /* INITIALIZING */;
4300
- }
4301
- }
4302
- return "available" /* AVAILABLE */;
4303
- } catch {
4304
- return "initializing" /* INITIALIZING */;
4305
- }
4306
- }
4307
- /**
4308
- * Check if Supabase is available.
4309
- *
4310
- * Supabase is considered available if we have a client instance.
4311
- * The actual network connectivity is checked separately via checkOnlineStatus().
4312
- *
4313
- * @returns Supabase backend status
4314
- */
4315
- detectSupabaseStatus() {
4316
- if (!this.supabase) {
4317
- return "unavailable" /* UNAVAILABLE */;
4318
- }
4319
- try {
4320
- if (typeof this.supabase.from === "function") {
4321
- return "available" /* AVAILABLE */;
4322
- }
4323
- return "unavailable" /* UNAVAILABLE */;
4324
- } catch {
4325
- return "unavailable" /* UNAVAILABLE */;
4326
- }
4327
- }
4328
- /**
4329
- * Check if device is online.
4330
- *
4331
- * Prefers the sync status's isOnline property if available (from React Native NetInfo).
4332
- * Falls back to navigator.onLine in browser environments.
4333
- * Returns false by default for non-browser environments (React Native, Node.js, etc.)
4334
- * to ensure offline-first behavior works correctly.
4335
- *
4336
- * @returns Whether the device has network connectivity
4337
- */
4338
- checkOnlineStatus() {
4339
- if (this.syncStatus?.isOnline !== void 0) {
4340
- return this.syncStatus.isOnline;
4341
- }
4342
- if (typeof window !== "undefined" && typeof navigator !== "undefined") {
4343
- return navigator.onLine;
4344
- }
4345
- return false;
4346
- }
4347
- // ===========================================================================
4348
- // Instance Management Methods
4349
- // ===========================================================================
4350
- /**
4351
- * Update PowerSync instance (e.g., when it becomes available).
4352
- *
4353
- * @param db - New PowerSync database instance or null
4354
- */
4355
- setPowerSync(db) {
4356
- this.powerSyncDb = db;
4357
- }
4358
- /**
4359
- * Update Supabase instance.
4360
- *
4361
- * @param supabase - New Supabase client instance or null
4362
- */
4363
- setSupabase(supabase) {
4364
- this.supabase = supabase;
4365
- }
4366
- /**
4367
- * Get current PowerSync instance.
4368
- *
4369
- * @returns Current PowerSync database instance or null
4370
- */
4371
- getPowerSync() {
4372
- return this.powerSyncDb;
4373
- }
4374
- /**
4375
- * Get current Supabase instance.
4376
- *
4377
- * @returns Current Supabase client instance or null
4378
- */
4379
- getSupabase() {
4380
- return this.supabase;
4381
- }
4382
- // ===========================================================================
4383
- // Options Management
4384
- // ===========================================================================
4385
- /**
4386
- * Update detector options.
4387
- *
4388
- * @param options - New options to merge with existing
4389
- */
4390
- setOptions(options) {
4391
- this.options = {
4392
- ...this.options,
4393
- ...options
4394
- };
4395
- }
4396
- /**
4397
- * Get current detector options.
4398
- *
4399
- * @returns Current detector options
4400
- */
4401
- getOptions() {
4402
- return {
4403
- ...this.options
4404
- };
4405
- }
4406
- // ===========================================================================
4407
- // Listener Management
4408
- // ===========================================================================
4409
- /**
4410
- * Add a listener for backend change events.
4411
- *
4412
- * @param listener - Callback to invoke when detection result changes
4413
- * @returns Function to remove the listener
4414
- */
4415
- addListener(listener) {
4416
- this.listeners.add(listener);
4417
- return () => this.listeners.delete(listener);
4418
- }
4419
- /**
4420
- * Remove a listener for backend change events.
4421
- *
4422
- * @param listener - Listener to remove
4423
- */
4424
- removeListener(listener) {
4425
- this.listeners.delete(listener);
4426
- }
4427
- /**
4428
- * Get the last detection result.
4429
- *
4430
- * @returns Last detection result or null if never detected
4431
- */
4432
- getLastResult() {
4433
- return this.lastResult;
4434
- }
4435
- // ===========================================================================
4436
- // Private Helper Methods
4437
- // ===========================================================================
4438
- /**
4439
- * Check if the detection result has changed from the last result.
4440
- */
4441
- hasResultChanged(result) {
4442
- if (!this.lastResult) {
4443
- return true;
4444
- }
4445
- return this.lastResult.powerSyncStatus !== result.powerSyncStatus || this.lastResult.supabaseStatus !== result.supabaseStatus || this.lastResult.recommendedBackend !== result.recommendedBackend || this.lastResult.isOnline !== result.isOnline;
4446
- }
4447
- /**
4448
- * Notify all listeners of a detection result change.
4449
- */
4450
- notifyListeners(result) {
4451
- const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : process.env.NODE_ENV !== "production";
4452
- if (isDev) {
4453
- const prevBackend = this.lastResult?.recommendedBackend;
4454
- if (prevBackend && prevBackend !== result.recommendedBackend) {
4455
- console.log(`[DataLayer] Backend switched: ${prevBackend} \u2192 ${result.recommendedBackend}`, `| Reason: ${result.reason}`);
4456
- }
4457
- if (result.recommendedBackend === "supabase" && this.options.preferPowerSync) {
4458
- console.log(`[DataLayer] Using online fallback (Supabase)`, `| PowerSync: ${result.powerSyncStatus}`, `| Online: ${result.isOnline}`, `| Reason: ${result.reason}`);
4459
- }
4460
- }
4461
- Array.from(this.listeners).forEach((listener) => {
4462
- try {
4463
- listener(result);
4464
- } catch (error) {
4465
- console.error("Error in backend change listener:", error);
4466
- }
4467
- });
4468
- }
4469
- };
4470
- function createAdapterAutoDetector(powerSyncDb, supabase, options) {
4471
- return new AdapterAutoDetector(powerSyncDb, supabase, options);
4472
- }
4473
-
4474
4146
  // src/adapters/sync-tracking-adapter.ts
4475
4147
  var DEBUG_SYNC_TRACKING_VERBOSE = false;
4476
4148
  function debugLog(message, ...args) {
@@ -4619,500 +4291,815 @@ var AdapterRegistry = class {
4619
4291
  this.config = config;
4620
4292
  }
4621
4293
  /**
4622
- * Cache of created adapters by table name
4294
+ * Cache of created adapters by table name
4295
+ */
4296
+ adapters = /* @__PURE__ */ new Map();
4297
+ /**
4298
+ * PowerSync adapter instance (set during initialization or lazily via getter)
4299
+ */
4300
+ powerSyncAdapter = null;
4301
+ /**
4302
+ * Getter function to retrieve PowerSync database instance.
4303
+ * Used for lazy initialization when PowerSync becomes available after registry creation.
4304
+ */
4305
+ powerSyncGetter = null;
4306
+ /**
4307
+ * Factory function to create PowerSync adapter from database instance.
4308
+ * Set via setPowerSyncGetter along with the getter.
4309
+ */
4310
+ powerSyncAdapterFactory = null;
4311
+ /**
4312
+ * Supabase adapter instance (set during initialization)
4313
+ */
4314
+ supabaseAdapter = null;
4315
+ /**
4316
+ * Cached adapter instance (wraps Supabase with TanStack Query)
4317
+ */
4318
+ cachedAdapter = null;
4319
+ /**
4320
+ * Dependencies for creating adapters
4321
+ */
4322
+ deps = null;
4323
+ /**
4324
+ * Whether the registry has been initialized with adapters
4325
+ */
4326
+ _isInitialized = false;
4327
+ /**
4328
+ * Auto-detector instance for automatic backend selection
4329
+ */
4330
+ autoDetector = null;
4331
+ /**
4332
+ * Sync tracker for mutation tracking
4333
+ */
4334
+ syncTracker = null;
4335
+ /**
4336
+ * Sync tracking adapter that wraps PowerSync adapter
4337
+ */
4338
+ syncTrackingAdapter = null;
4339
+ /**
4340
+ * Listeners for backend change events
4341
+ */
4342
+ backendChangeListeners = /* @__PURE__ */ new Set();
4343
+ /**
4344
+ * Last auto-detection result for debugging and status
4345
+ */
4346
+ lastDetectionResult = null;
4347
+ // ===========================================================================
4348
+ // Initialization
4349
+ // ===========================================================================
4350
+ /**
4351
+ * Check if the registry has been initialized
4352
+ */
4353
+ get isInitialized() {
4354
+ return this._isInitialized;
4355
+ }
4356
+ /**
4357
+ * Initialize the registry with dependencies.
4358
+ * Called by DataLayerProvider when PowerSync and Supabase are ready.
4359
+ *
4360
+ * @param deps - Dependencies needed to create adapters
4361
+ */
4362
+ initialize(deps) {
4363
+ this.deps = deps;
4364
+ this._isInitialized = true;
4365
+ }
4366
+ /**
4367
+ * Set the PowerSync adapter instance
4368
+ *
4369
+ * @param adapter - PowerSync adapter implementation
4370
+ */
4371
+ setPowerSyncAdapter(adapter) {
4372
+ this.powerSyncAdapter = adapter;
4373
+ if (this.syncTracker) {
4374
+ this.syncTrackingAdapter = new SyncTrackingAdapter(adapter, this.syncTracker);
4375
+ } else {
4376
+ this.syncTrackingAdapter = null;
4377
+ }
4378
+ this.adapters.clear();
4379
+ }
4380
+ /**
4381
+ * Set a getter function for lazy PowerSync adapter initialization.
4382
+ *
4383
+ * This allows the registry to lazily create the PowerSync adapter when
4384
+ * PowerSync becomes available, rather than requiring it at initialization time.
4385
+ * Solves race conditions where queries/mutations run before PowerSync is ready.
4386
+ *
4387
+ * @param getter - Function that returns the PowerSync database instance (or null if not ready)
4388
+ * @param factory - Function that creates a TableDataAdapter from the database instance
4389
+ */
4390
+ setPowerSyncGetter(getter, factory) {
4391
+ this.powerSyncGetter = getter;
4392
+ this.powerSyncAdapterFactory = factory;
4393
+ }
4394
+ /**
4395
+ * Ensure PowerSync adapter is initialized if available.
4396
+ *
4397
+ * Checks the getter (if set) and lazily creates the adapter if PowerSync
4398
+ * is now available. Also updates the auto-detector with the new reference.
4399
+ *
4400
+ * @returns true if PowerSync adapter is available, false otherwise
4401
+ */
4402
+ ensurePowerSyncAdapter() {
4403
+ if (this.powerSyncAdapter) {
4404
+ return true;
4405
+ }
4406
+ if (!this.powerSyncGetter || !this.powerSyncAdapterFactory) {
4407
+ return false;
4408
+ }
4409
+ const db = this.powerSyncGetter();
4410
+ if (!db) {
4411
+ return false;
4412
+ }
4413
+ const adapter = this.powerSyncAdapterFactory(db);
4414
+ this.setPowerSyncAdapter(adapter);
4415
+ if (this.autoDetector) {
4416
+ this.autoDetector.setPowerSyncDb(db);
4417
+ }
4418
+ return true;
4419
+ }
4420
+ /**
4421
+ * Set the sync tracker for mutation tracking.
4422
+ * When set, write operations on PowerSync tables will
4423
+ * automatically track pending mutations.
4424
+ *
4425
+ * NOTE: This also clears the adapter cache to ensure any
4426
+ * previously cached adapters (which may have been created
4427
+ * before the sync tracker was available) are recreated
4428
+ * with the new sync tracker on next access.
4429
+ */
4430
+ setSyncTracker(tracker) {
4431
+ this.syncTracker = tracker;
4432
+ if (this.powerSyncAdapter) {
4433
+ this.syncTrackingAdapter = new SyncTrackingAdapter(this.powerSyncAdapter, tracker);
4434
+ }
4435
+ this.adapters.clear();
4436
+ }
4437
+ /**
4438
+ * Set the Supabase adapter instance
4439
+ *
4440
+ * @param adapter - Supabase adapter implementation
4441
+ */
4442
+ setSupabaseAdapter(adapter) {
4443
+ this.supabaseAdapter = adapter;
4444
+ }
4445
+ /**
4446
+ * Set the Cached adapter instance
4447
+ *
4448
+ * @param adapter - Cached adapter implementation
4449
+ */
4450
+ setCachedAdapter(adapter) {
4451
+ this.cachedAdapter = adapter;
4452
+ }
4453
+ /**
4454
+ * Initialize auto-detection with a detector instance
4455
+ *
4456
+ * @param detector - The auto-detector to use
4457
+ */
4458
+ initializeAutoDetection(detector) {
4459
+ this.autoDetector = detector;
4460
+ detector.addListener((result) => {
4461
+ this.lastDetectionResult = result;
4462
+ this.notifyBackendChange(result.recommendedBackend);
4463
+ });
4464
+ }
4465
+ // ===========================================================================
4466
+ // Adapter Access
4467
+ // ===========================================================================
4468
+ /**
4469
+ * Get the appropriate adapter for a table based on configuration.
4470
+ *
4471
+ * The adapter is selected based on the table's strategy in config.tables:
4472
+ * - "powersync": Returns PowerSyncAdapter
4473
+ * - "supabase": Returns SupabaseAdapter
4474
+ * - "cached": Returns CachedAdapter (wrapping Supabase)
4475
+ * - "hybrid": Returns HybridAdapter (combining PowerSync + Cached)
4476
+ * - "auto": Uses auto-detection to select the best backend
4477
+ *
4478
+ * For tables not in config, defaults to auto-detection if available,
4479
+ * otherwise falls back to SupabaseAdapter.
4480
+ *
4481
+ * Supports schema-qualified table names:
4482
+ * - "core.Profile" looks up config["core.Profile"] first, then config["Profile"]
4483
+ * - Auto-generates PowerSync alias as "CoreProfile" if not explicitly set
4484
+ *
4485
+ * @param table - The table name (may be schema-qualified like "core.Profile")
4486
+ * @param operation - The operation type: 'read' uses fallback logic during init, 'write' always uses PowerSync for powersync tables
4487
+ * @returns The appropriate adapter for the table
4488
+ * @throws Error if adapters are not initialized
4489
+ */
4490
+ getAdapter(table, _operation = "read") {
4491
+ this.ensurePowerSyncAdapter();
4492
+ const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4493
+ const strategy = this.config.tables[table] ?? this.config.tables[tableWithoutSchema];
4494
+ if (!strategy || strategy.strategy === "auto") {
4495
+ if (this.powerSyncAdapter) {
4496
+ const powerSyncTableKeys = this.getPowerSyncTableKeys();
4497
+ const isConfigured = powerSyncTableKeys.some((key) => key === table || key === tableWithoutSchema || key.includes(".") && key.split(".")[1] === tableWithoutSchema);
4498
+ if (!isConfigured) {
4499
+ if (this.supabaseAdapter) {
4500
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
4501
+ console.warn(`[AdapterRegistry] Table "${table}" is not configured for PowerSync sync. Using Supabase fallback.`);
4502
+ }
4503
+ return this.supabaseAdapter;
4504
+ }
4505
+ throw new Error(`Table "${table}" is not configured for PowerSync sync and Supabase adapter is not available. Either add this table to the PowerSync schema or initialize the Supabase adapter.`);
4506
+ }
4507
+ }
4508
+ return this.getAutoAdapter(strategy, table);
4509
+ }
4510
+ if (strategy.strategy === "powersync") {
4511
+ if (this.powerSyncAdapter) {
4512
+ return this.syncTrackingAdapter ?? this.powerSyncAdapter;
4513
+ }
4514
+ if (this.supabaseAdapter) {
4515
+ const isOnline = this.autoDetector?.getSyncStatus()?.isOnline ?? (typeof navigator !== "undefined" ? navigator.onLine : false);
4516
+ if (isOnline) {
4517
+ return this.supabaseAdapter;
4518
+ }
4519
+ throw new Error(`Offline and PowerSync not ready for table "${table}". Wait for initialization.`);
4520
+ }
4521
+ }
4522
+ const existing = this.adapters.get(table);
4523
+ if (existing) {
4524
+ return existing;
4525
+ }
4526
+ const adapter = this.createAdapter(strategy);
4527
+ this.adapters.set(table, adapter);
4528
+ return adapter;
4529
+ }
4530
+ /**
4531
+ * Get the PowerSync adapter directly
4532
+ *
4533
+ * @returns PowerSync adapter or null if not initialized
4534
+ */
4535
+ getPowerSyncAdapter() {
4536
+ return this.powerSyncAdapter;
4537
+ }
4538
+ /**
4539
+ * Get the Supabase adapter directly
4540
+ *
4541
+ * @returns Supabase adapter or null if not initialized
4542
+ */
4543
+ getSupabaseAdapter() {
4544
+ return this.supabaseAdapter;
4545
+ }
4546
+ /**
4547
+ * Get the Cached adapter directly
4548
+ *
4549
+ * @returns Cached adapter or null if not initialized
4623
4550
  */
4624
- adapters = /* @__PURE__ */ new Map();
4551
+ getCachedAdapter() {
4552
+ return this.cachedAdapter;
4553
+ }
4625
4554
  /**
4626
- * PowerSync adapter instance (set during initialization or lazily via getter)
4555
+ * Get all configured table names
4556
+ *
4557
+ * @returns Array of table names with explicit strategy configuration
4627
4558
  */
4628
- powerSyncAdapter = null;
4559
+ getConfiguredTables() {
4560
+ return Object.keys(this.config.tables);
4561
+ }
4629
4562
  /**
4630
- * Getter function to retrieve PowerSync database instance.
4631
- * Used for lazy initialization when PowerSync becomes available after registry creation.
4563
+ * Get the strategy for a specific table
4564
+ *
4565
+ * @param table - The table name (may include schema prefix like "core.Profile")
4566
+ * @returns The table strategy or undefined if not configured
4632
4567
  */
4633
- powerSyncGetter = null;
4568
+ getTableStrategy(table) {
4569
+ const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4570
+ return this.config.tables[table] ?? this.config.tables[tableWithoutSchema];
4571
+ }
4634
4572
  /**
4635
- * Factory function to create PowerSync adapter from database instance.
4636
- * Set via setPowerSyncGetter along with the getter.
4573
+ * Check if a table uses PowerSync strategy
4574
+ *
4575
+ * @param table - The table name (may include schema prefix like "core.Profile")
4576
+ * @returns True if table uses PowerSync, Hybrid, or Auto strategy
4637
4577
  */
4638
- powerSyncAdapterFactory = null;
4578
+ usesPowerSync(table) {
4579
+ const strategy = this.getTableStrategy(table);
4580
+ return strategy?.strategy === "powersync" || strategy?.strategy === "hybrid" || strategy?.strategy === "auto";
4581
+ }
4639
4582
  /**
4640
- * Supabase adapter instance (set during initialization)
4583
+ * Get all table config keys that use PowerSync (may be schema-qualified)
4584
+ *
4585
+ * @returns Array of config keys using PowerSync, Hybrid, or Auto strategy
4641
4586
  */
4642
- supabaseAdapter = null;
4587
+ getPowerSyncTableKeys() {
4588
+ return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key]) => key);
4589
+ }
4643
4590
  /**
4644
- * Cached adapter instance (wraps Supabase with TanStack Query)
4591
+ * Get all tables that use PowerSync (returns aliases for PowerSync schema)
4592
+ *
4593
+ * @returns Array of PowerSync table aliases
4645
4594
  */
4646
- cachedAdapter = null;
4595
+ getPowerSyncTables() {
4596
+ return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key, strategy]) => getPowerSyncAlias(key, strategy));
4597
+ }
4647
4598
  /**
4648
- * Dependencies for creating adapters
4599
+ * Get the PowerSync alias for a table.
4600
+ * Uses explicit alias from config if set, otherwise auto-generates.
4601
+ *
4602
+ * @param table - Table name (may be schema-qualified)
4603
+ * @returns The alias to use in PowerSync schema
4649
4604
  */
4650
- deps = null;
4605
+ getTableAlias(table) {
4606
+ const strategy = this.getTableStrategy(table);
4607
+ const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4608
+ const configKey = this.config.tables[table] ? table : tableWithoutSchema;
4609
+ return getPowerSyncAlias(configKey, strategy);
4610
+ }
4611
+ // ===========================================================================
4612
+ // Auto-Detection Methods
4613
+ // ===========================================================================
4651
4614
  /**
4652
- * Whether the registry has been initialized with adapters
4615
+ * Get adapter using auto-detection.
4616
+ *
4617
+ * Simple logic: PowerSync first, Supabase fallback ONLY when online.
4618
+ * Once PowerSync is initialized, always use it for offline-first behavior.
4653
4619
  */
4654
- _isInitialized = false;
4620
+ getAutoAdapter(_strategy, _table) {
4621
+ if (this.powerSyncAdapter) {
4622
+ return this.syncTrackingAdapter ?? this.powerSyncAdapter;
4623
+ }
4624
+ if (this.supabaseAdapter) {
4625
+ const isOnline = this.autoDetector?.getSyncStatus()?.isOnline ?? (typeof navigator !== "undefined" ? navigator.onLine : false);
4626
+ if (isOnline) {
4627
+ return this.supabaseAdapter;
4628
+ }
4629
+ throw new Error("Offline and PowerSync not ready. Wait for PowerSync initialization.");
4630
+ }
4631
+ throw new Error("No adapters available. Initialize PowerSync or Supabase.");
4632
+ }
4655
4633
  /**
4656
- * Auto-detector instance for automatic backend selection
4634
+ * Subscribe to backend changes
4635
+ *
4636
+ * @param callback - Function called when recommended backend changes
4637
+ * @returns Unsubscribe function
4657
4638
  */
4658
- autoDetector = null;
4639
+ onBackendChange(callback) {
4640
+ this.backendChangeListeners.add(callback);
4641
+ return () => {
4642
+ this.backendChangeListeners.delete(callback);
4643
+ };
4644
+ }
4659
4645
  /**
4660
- * Sync tracker for mutation tracking
4646
+ * Notify listeners of backend change
4647
+ *
4648
+ * @param backend - The new recommended backend
4661
4649
  */
4662
- syncTracker = null;
4650
+ notifyBackendChange(backend) {
4651
+ this.backendChangeListeners.forEach((callback) => {
4652
+ try {
4653
+ callback(backend);
4654
+ } catch (error) {
4655
+ console.error("Error in backend change listener:", error);
4656
+ }
4657
+ });
4658
+ }
4663
4659
  /**
4664
- * Sync tracking adapter that wraps PowerSync adapter
4660
+ * Get the last auto-detection result
4661
+ *
4662
+ * @returns Last detection result or null if never detected
4665
4663
  */
4666
- syncTrackingAdapter = null;
4664
+ getLastDetectionResult() {
4665
+ return this.lastDetectionResult;
4666
+ }
4667
4667
  /**
4668
- * Listeners for backend change events
4668
+ * Get the auto-detector instance
4669
+ *
4670
+ * @returns Auto-detector instance or null if not initialized
4669
4671
  */
4670
- backendChangeListeners = /* @__PURE__ */ new Set();
4672
+ getAutoDetector() {
4673
+ return this.autoDetector;
4674
+ }
4675
+ // ===========================================================================
4676
+ // Private Methods
4677
+ // ===========================================================================
4671
4678
  /**
4672
- * Last auto-detection result for debugging and status
4679
+ * Create an adapter based on the strategy type
4680
+ *
4681
+ * @param strategy - The table strategy configuration
4682
+ * @returns The created adapter
4683
+ * @throws Error if the required base adapter is not initialized
4673
4684
  */
4674
- lastDetectionResult = null;
4685
+ createAdapter(strategy) {
4686
+ switch (strategy.strategy) {
4687
+ case "powersync":
4688
+ if (!this.powerSyncAdapter) {
4689
+ if (this.supabaseAdapter) {
4690
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
4691
+ console.warn(`[AdapterRegistry] PowerSync not ready, falling back to Supabase`);
4692
+ }
4693
+ return this.supabaseAdapter;
4694
+ }
4695
+ throw new Error("PowerSync adapter not initialized and Supabase not available. Ensure PowerSyncAdapter is set before accessing PowerSync tables.");
4696
+ }
4697
+ return this.syncTrackingAdapter ?? this.powerSyncAdapter;
4698
+ case "supabase":
4699
+ if (!this.supabaseAdapter) {
4700
+ throw new Error("Supabase adapter not initialized. Ensure SupabaseAdapter is set before accessing Supabase tables.");
4701
+ }
4702
+ return this.supabaseAdapter;
4703
+ case "cached":
4704
+ if (this.cachedAdapter) {
4705
+ return this.cachedAdapter;
4706
+ }
4707
+ throw new Error("CachedAdapter not yet implemented. This feature will be available in Wave 2. For now, use 'supabase' strategy as a fallback.");
4708
+ case "hybrid":
4709
+ throw new Error("HybridAdapter not yet implemented. This feature will be available in Wave 2. For now, use 'powersync' or 'supabase' strategy as a fallback.");
4710
+ default:
4711
+ if (!this.supabaseAdapter) {
4712
+ throw new Error("Supabase adapter not initialized. Ensure SupabaseAdapter is set before accessing tables.");
4713
+ }
4714
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
4715
+ console.warn(`[AdapterRegistry] Unknown strategy "${strategy.strategy}". Using Supabase fallback.`);
4716
+ }
4717
+ return this.supabaseAdapter;
4718
+ }
4719
+ }
4675
4720
  // ===========================================================================
4676
- // Initialization
4721
+ // Utility Methods
4677
4722
  // ===========================================================================
4678
4723
  /**
4679
- * Check if the registry has been initialized
4724
+ * Clear all cached adapters.
4725
+ * Useful when configuration changes and adapters need to be recreated.
4680
4726
  */
4681
- get isInitialized() {
4682
- return this._isInitialized;
4727
+ clearCache() {
4728
+ this.adapters.clear();
4683
4729
  }
4684
4730
  /**
4685
- * Initialize the registry with dependencies.
4686
- * Called by DataLayerProvider when PowerSync and Supabase are ready.
4687
- *
4688
- * @param deps - Dependencies needed to create adapters
4731
+ * Reset the registry to uninitialized state.
4732
+ * Used during cleanup or testing.
4689
4733
  */
4690
- initialize(deps) {
4691
- this.deps = deps;
4692
- this._isInitialized = true;
4734
+ reset() {
4735
+ this.adapters.clear();
4736
+ this.powerSyncAdapter = null;
4737
+ this.powerSyncGetter = null;
4738
+ this.powerSyncAdapterFactory = null;
4739
+ this.supabaseAdapter = null;
4740
+ this.cachedAdapter = null;
4741
+ this.deps = null;
4742
+ this._isInitialized = false;
4743
+ this.autoDetector = null;
4744
+ this.backendChangeListeners.clear();
4745
+ this.lastDetectionResult = null;
4746
+ this.syncTracker = null;
4747
+ this.syncTrackingAdapter = null;
4693
4748
  }
4694
4749
  /**
4695
- * Set the PowerSync adapter instance
4696
- *
4697
- * @param adapter - PowerSync adapter implementation
4750
+ * Dispose all adapters and clean up resources.
4751
+ * Called when the DataLayerProvider unmounts.
4698
4752
  */
4699
- setPowerSyncAdapter(adapter) {
4700
- this.powerSyncAdapter = adapter;
4701
- if (this.syncTracker) {
4702
- this.syncTrackingAdapter = new SyncTrackingAdapter(adapter, this.syncTracker);
4703
- } else {
4704
- this.syncTrackingAdapter = null;
4705
- }
4706
- this.adapters.clear();
4753
+ dispose() {
4754
+ this.reset();
4707
4755
  }
4708
4756
  /**
4709
- * Set a getter function for lazy PowerSync adapter initialization.
4710
- *
4711
- * This allows the registry to lazily create the PowerSync adapter when
4712
- * PowerSync becomes available, rather than requiring it at initialization time.
4713
- * Solves race conditions where queries/mutations run before PowerSync is ready.
4714
- *
4715
- * @param getter - Function that returns the PowerSync database instance (or null if not ready)
4716
- * @param factory - Function that creates a TableDataAdapter from the database instance
4757
+ * Get debug information about the registry state
4717
4758
  */
4718
- setPowerSyncGetter(getter, factory) {
4719
- this.powerSyncGetter = getter;
4720
- this.powerSyncAdapterFactory = factory;
4759
+ getDebugInfo() {
4760
+ return {
4761
+ isInitialized: this._isInitialized,
4762
+ hasPowerSync: this.powerSyncAdapter !== null,
4763
+ hasSupabase: this.supabaseAdapter !== null,
4764
+ hasCached: this.cachedAdapter !== null,
4765
+ cachedAdapterCount: this.adapters.size,
4766
+ configuredTableCount: Object.keys(this.config.tables).length,
4767
+ powerSyncTables: this.getPowerSyncTables(),
4768
+ hasAutoDetector: this.autoDetector !== null,
4769
+ lastDetectionResult: this.lastDetectionResult
4770
+ };
4771
+ }
4772
+ };
4773
+ function createAdapterRegistry(config) {
4774
+ return new AdapterRegistry(config);
4775
+ }
4776
+
4777
+ // src/adapters/auto-detector.ts
4778
+ var BackendStatus = /* @__PURE__ */ ((BackendStatus2) => {
4779
+ BackendStatus2["AVAILABLE"] = "available";
4780
+ BackendStatus2["INITIALIZING"] = "initializing";
4781
+ BackendStatus2["UNAVAILABLE"] = "unavailable";
4782
+ return BackendStatus2;
4783
+ })(BackendStatus || {});
4784
+ var AdapterAutoDetector = class {
4785
+ constructor(powerSyncDb, supabase, options = {}) {
4786
+ this.powerSyncDb = powerSyncDb;
4787
+ this.supabase = supabase;
4788
+ this.options = {
4789
+ preferPowerSync: options.preferPowerSync ?? true,
4790
+ statusCheckTimeout: options.statusCheckTimeout ?? 1e3,
4791
+ useOnlineUntilSynced: options.useOnlineUntilSynced ?? true
4792
+ };
4721
4793
  }
4794
+ options;
4795
+ listeners = /* @__PURE__ */ new Set();
4796
+ lastResult = null;
4797
+ syncStatus = null;
4722
4798
  /**
4723
- * Ensure PowerSync adapter is initialized if available.
4724
- *
4725
- * Checks the getter (if set) and lazily creates the adapter if PowerSync
4726
- * is now available. Also updates the auto-detector with the new reference.
4799
+ * Update the PowerSync database reference.
4800
+ * Called when PowerSync becomes available after initial construction.
4727
4801
  *
4728
- * @returns true if PowerSync adapter is available, false otherwise
4802
+ * @param db - PowerSync database instance or null
4729
4803
  */
4730
- ensurePowerSyncAdapter() {
4731
- if (this.powerSyncAdapter) {
4732
- return true;
4733
- }
4734
- if (!this.powerSyncGetter || !this.powerSyncAdapterFactory) {
4735
- return false;
4736
- }
4737
- const db = this.powerSyncGetter();
4738
- if (!db) {
4739
- return false;
4740
- }
4741
- const adapter = this.powerSyncAdapterFactory(db);
4742
- this.setPowerSyncAdapter(adapter);
4743
- if (this.autoDetector) {
4744
- this.autoDetector.setPowerSyncDb(db);
4804
+ setPowerSyncDb(db) {
4805
+ this.powerSyncDb = db;
4806
+ if (db) {
4807
+ this.detect();
4745
4808
  }
4746
- return true;
4747
4809
  }
4748
4810
  /**
4749
- * Set the sync tracker for mutation tracking.
4750
- * When set, write operations on PowerSync tables will
4751
- * automatically track pending mutations.
4811
+ * Update the sync status from PowerSync.
4812
+ * Called when sync status changes to re-evaluate backend recommendation.
4752
4813
  *
4753
- * NOTE: This also clears the adapter cache to ensure any
4754
- * previously cached adapters (which may have been created
4755
- * before the sync tracker was available) are recreated
4756
- * with the new sync tracker on next access.
4814
+ * @param status - Current sync status or null if not available
4757
4815
  */
4758
- setSyncTracker(tracker) {
4759
- this.syncTracker = tracker;
4760
- if (this.powerSyncAdapter) {
4761
- this.syncTrackingAdapter = new SyncTrackingAdapter(this.powerSyncAdapter, tracker);
4816
+ updateSyncStatus(status) {
4817
+ const hadSynced = this.syncStatus?.hasSynced;
4818
+ const hasSyncedNow = status?.hasSynced;
4819
+ const changed = hadSynced !== hasSyncedNow;
4820
+ this.syncStatus = status;
4821
+ if (changed) {
4822
+ this.detect();
4762
4823
  }
4763
- this.adapters.clear();
4764
- }
4765
- /**
4766
- * Set the Supabase adapter instance
4767
- *
4768
- * @param adapter - Supabase adapter implementation
4769
- */
4770
- setSupabaseAdapter(adapter) {
4771
- this.supabaseAdapter = adapter;
4772
- }
4773
- /**
4774
- * Set the Cached adapter instance
4775
- *
4776
- * @param adapter - Cached adapter implementation
4777
- */
4778
- setCachedAdapter(adapter) {
4779
- this.cachedAdapter = adapter;
4780
4824
  }
4781
4825
  /**
4782
- * Initialize auto-detection with a detector instance
4783
- *
4784
- * @param detector - The auto-detector to use
4826
+ * Get current sync status.
4827
+ * @returns Current sync status or null
4785
4828
  */
4786
- initializeAutoDetection(detector) {
4787
- this.autoDetector = detector;
4788
- detector.addListener((result) => {
4789
- this.lastDetectionResult = result;
4790
- this.notifyBackendChange(result.recommendedBackend);
4791
- });
4829
+ getSyncStatus() {
4830
+ return this.syncStatus;
4792
4831
  }
4793
4832
  // ===========================================================================
4794
- // Adapter Access
4833
+ // Main Detection Methods
4795
4834
  // ===========================================================================
4796
4835
  /**
4797
- * Get the appropriate adapter for a table based on configuration.
4798
- *
4799
- * The adapter is selected based on the table's strategy in config.tables:
4800
- * - "powersync": Returns PowerSyncAdapter
4801
- * - "supabase": Returns SupabaseAdapter
4802
- * - "cached": Returns CachedAdapter (wrapping Supabase)
4803
- * - "hybrid": Returns HybridAdapter (combining PowerSync + Cached)
4804
- * - "auto": Uses auto-detection to select the best backend
4805
- *
4806
- * For tables not in config, defaults to auto-detection if available,
4807
- * otherwise falls back to SupabaseAdapter.
4836
+ * Detect backend availability and recommend best option.
4808
4837
  *
4809
- * Supports schema-qualified table names:
4810
- * - "core.Profile" looks up config["core.Profile"] first, then config["Profile"]
4811
- * - Auto-generates PowerSync alias as "CoreProfile" if not explicitly set
4838
+ * The detection logic follows this priority:
4839
+ * 1. If preferPowerSync is true and PowerSync is available, use PowerSync
4840
+ * 2. If PowerSync is initializing and online with Supabase available, use Supabase temporarily
4841
+ * 3. If online with Supabase available, use Supabase
4842
+ * 4. If offline but PowerSync available, use PowerSync (offline mode)
4843
+ * 5. If offline and PowerSync exists (even if initializing), use PowerSync local data
4844
+ * 6. Default to Supabase as fallback
4812
4845
  *
4813
- * @param table - The table name (may be schema-qualified like "core.Profile")
4814
- * @param operation - The operation type: 'read' uses fallback logic during init, 'write' always uses PowerSync for powersync tables
4815
- * @returns The appropriate adapter for the table
4816
- * @throws Error if adapters are not initialized
4846
+ * @returns Detection result with recommendation and reasoning
4817
4847
  */
4818
- getAdapter(table, _operation = "read") {
4819
- this.ensurePowerSyncAdapter();
4820
- const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4821
- const strategy = this.config.tables[table] ?? this.config.tables[tableWithoutSchema];
4822
- if (!strategy || strategy.strategy === "auto") {
4823
- if (this.powerSyncAdapter) {
4824
- const powerSyncTableKeys = this.getPowerSyncTableKeys();
4825
- const isConfigured = powerSyncTableKeys.some((key) => key === table || key === tableWithoutSchema || key.includes(".") && key.split(".")[1] === tableWithoutSchema);
4826
- if (!isConfigured) {
4827
- if (this.supabaseAdapter) {
4828
- if (typeof __DEV__ !== "undefined" && __DEV__) {
4829
- console.warn(`[AdapterRegistry] Table "${table}" is not configured for PowerSync sync. Using Supabase fallback.`);
4830
- }
4831
- return this.supabaseAdapter;
4832
- }
4833
- throw new Error(`Table "${table}" is not configured for PowerSync sync and Supabase adapter is not available. Either add this table to the PowerSync schema or initialize the Supabase adapter.`);
4834
- }
4835
- }
4836
- return this.getAutoAdapter(strategy, table);
4837
- }
4838
- if (strategy.strategy === "powersync" && this.autoDetector && this.supabaseAdapter) {
4839
- const detection = this.autoDetector.detectSilent();
4840
- if (detection.powerSyncStatus === "initializing" /* INITIALIZING */ && detection.isOnline) {
4841
- if (typeof __DEV__ !== "undefined" && __DEV__) {
4842
- console.warn(`[AdapterRegistry] Table "${table}" configured for PowerSync but using Supabase. PowerSync status: ${detection.powerSyncStatus}, Online: ${detection.isOnline}`);
4843
- }
4844
- return this.supabaseAdapter;
4845
- }
4846
- }
4847
- const existing = this.adapters.get(table);
4848
- if (existing) {
4849
- return existing;
4848
+ detect() {
4849
+ const result = this.performDetection();
4850
+ if (this.hasResultChanged(result)) {
4851
+ this.lastResult = result;
4852
+ this.notifyListeners(result);
4850
4853
  }
4851
- const adapter = this.createAdapter(strategy);
4852
- this.adapters.set(table, adapter);
4853
- return adapter;
4854
+ return result;
4854
4855
  }
4855
4856
  /**
4856
- * Get the PowerSync adapter directly
4857
+ * Detect backend availability WITHOUT notifying listeners.
4857
4858
  *
4858
- * @returns PowerSync adapter or null if not initialized
4859
+ * This method is safe to call during React's render phase because it does not
4860
+ * trigger state updates in other components. Use this method when you need to
4861
+ * check backend status synchronously during render (e.g., in getAdapter()).
4862
+ *
4863
+ * The regular detect() method with listener notifications should only be called
4864
+ * from useEffect or event handlers to avoid the React warning:
4865
+ * "Cannot update a component while rendering a different component"
4866
+ *
4867
+ * @returns Detection result with recommendation and reasoning
4859
4868
  */
4860
- getPowerSyncAdapter() {
4861
- return this.powerSyncAdapter;
4869
+ detectSilent() {
4870
+ const result = this.performDetection();
4871
+ this.lastResult = result;
4872
+ return result;
4862
4873
  }
4863
4874
  /**
4864
- * Get the Supabase adapter directly
4865
- *
4866
- * @returns Supabase adapter or null if not initialized
4875
+ * Internal detection logic shared by detect() and detectSilent().
4876
+ * @private
4867
4877
  */
4868
- getSupabaseAdapter() {
4869
- return this.supabaseAdapter;
4878
+ performDetection() {
4879
+ const powerSyncStatus = this.detectPowerSyncStatus();
4880
+ const supabaseStatus = this.detectSupabaseStatus();
4881
+ const isOnline = this.checkOnlineStatus();
4882
+ let recommendedBackend;
4883
+ let reason;
4884
+ if (this.options.preferPowerSync && powerSyncStatus === "available" /* AVAILABLE */) {
4885
+ recommendedBackend = "powersync";
4886
+ reason = "PowerSync is available and preferred for offline-first experience";
4887
+ } else if (powerSyncStatus === "initializing" /* INITIALIZING */ && isOnline && supabaseStatus === "available" /* AVAILABLE */) {
4888
+ recommendedBackend = "supabase";
4889
+ reason = "PowerSync initial sync in progress; using Supabase for fresh data";
4890
+ } else if (supabaseStatus === "available" /* AVAILABLE */ && isOnline) {
4891
+ recommendedBackend = "supabase";
4892
+ reason = "Using Supabase direct connection";
4893
+ } else if (powerSyncStatus === "available" /* AVAILABLE */) {
4894
+ recommendedBackend = "powersync";
4895
+ reason = "Offline mode using PowerSync local data";
4896
+ } else if (!isOnline && this.powerSyncDb) {
4897
+ recommendedBackend = "powersync";
4898
+ reason = "Offline mode - using PowerSync local data (initial sync may be incomplete)";
4899
+ } else {
4900
+ recommendedBackend = "supabase";
4901
+ reason = "No confirmed available backend; defaulting to Supabase";
4902
+ }
4903
+ return {
4904
+ powerSyncStatus,
4905
+ supabaseStatus,
4906
+ recommendedBackend,
4907
+ isOnline,
4908
+ reason
4909
+ };
4870
4910
  }
4871
4911
  /**
4872
- * Get the Cached adapter directly
4912
+ * Check if PowerSync is available.
4873
4913
  *
4874
- * @returns Cached adapter or null if not initialized
4914
+ * PowerSync status depends on:
4915
+ * 1. Database instance exists
4916
+ * 2. If useOnlineUntilSynced is true, also checks if initial sync completed
4917
+ *
4918
+ * @returns PowerSync backend status
4875
4919
  */
4876
- getCachedAdapter() {
4877
- return this.cachedAdapter;
4920
+ detectPowerSyncStatus() {
4921
+ if (!this.powerSyncDb) {
4922
+ return "unavailable" /* UNAVAILABLE */;
4923
+ }
4924
+ try {
4925
+ if (typeof this.powerSyncDb.getAll !== "function") {
4926
+ return "initializing" /* INITIALIZING */;
4927
+ }
4928
+ if (this.options.useOnlineUntilSynced) {
4929
+ if (!this.syncStatus || !this.syncStatus.hasSynced) {
4930
+ return "initializing" /* INITIALIZING */;
4931
+ }
4932
+ }
4933
+ return "available" /* AVAILABLE */;
4934
+ } catch {
4935
+ return "initializing" /* INITIALIZING */;
4936
+ }
4878
4937
  }
4879
4938
  /**
4880
- * Get all configured table names
4939
+ * Check if Supabase is available.
4881
4940
  *
4882
- * @returns Array of table names with explicit strategy configuration
4941
+ * Supabase is considered available if we have a client instance.
4942
+ * The actual network connectivity is checked separately via checkOnlineStatus().
4943
+ *
4944
+ * @returns Supabase backend status
4883
4945
  */
4884
- getConfiguredTables() {
4885
- return Object.keys(this.config.tables);
4946
+ detectSupabaseStatus() {
4947
+ if (!this.supabase) {
4948
+ return "unavailable" /* UNAVAILABLE */;
4949
+ }
4950
+ try {
4951
+ if (typeof this.supabase.from === "function") {
4952
+ return "available" /* AVAILABLE */;
4953
+ }
4954
+ return "unavailable" /* UNAVAILABLE */;
4955
+ } catch {
4956
+ return "unavailable" /* UNAVAILABLE */;
4957
+ }
4886
4958
  }
4887
4959
  /**
4888
- * Get the strategy for a specific table
4960
+ * Check if device is online.
4889
4961
  *
4890
- * @param table - The table name (may include schema prefix like "core.Profile")
4891
- * @returns The table strategy or undefined if not configured
4962
+ * Prefers the sync status's isOnline property if available (from React Native NetInfo).
4963
+ * Falls back to navigator.onLine in browser environments.
4964
+ * Returns false by default for non-browser environments (React Native, Node.js, etc.)
4965
+ * to ensure offline-first behavior works correctly.
4966
+ *
4967
+ * @returns Whether the device has network connectivity
4892
4968
  */
4893
- getTableStrategy(table) {
4894
- const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4895
- return this.config.tables[table] ?? this.config.tables[tableWithoutSchema];
4969
+ checkOnlineStatus() {
4970
+ if (this.syncStatus?.isOnline !== void 0) {
4971
+ return this.syncStatus.isOnline;
4972
+ }
4973
+ if (typeof window !== "undefined" && typeof navigator !== "undefined") {
4974
+ return navigator.onLine;
4975
+ }
4976
+ return false;
4896
4977
  }
4978
+ // ===========================================================================
4979
+ // Instance Management Methods
4980
+ // ===========================================================================
4897
4981
  /**
4898
- * Check if a table uses PowerSync strategy
4982
+ * Update PowerSync instance (e.g., when it becomes available).
4899
4983
  *
4900
- * @param table - The table name (may include schema prefix like "core.Profile")
4901
- * @returns True if table uses PowerSync, Hybrid, or Auto strategy
4984
+ * @param db - New PowerSync database instance or null
4902
4985
  */
4903
- usesPowerSync(table) {
4904
- const strategy = this.getTableStrategy(table);
4905
- return strategy?.strategy === "powersync" || strategy?.strategy === "hybrid" || strategy?.strategy === "auto";
4986
+ setPowerSync(db) {
4987
+ this.powerSyncDb = db;
4906
4988
  }
4907
4989
  /**
4908
- * Get all table config keys that use PowerSync (may be schema-qualified)
4990
+ * Update Supabase instance.
4909
4991
  *
4910
- * @returns Array of config keys using PowerSync, Hybrid, or Auto strategy
4992
+ * @param supabase - New Supabase client instance or null
4911
4993
  */
4912
- getPowerSyncTableKeys() {
4913
- return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key]) => key);
4994
+ setSupabase(supabase) {
4995
+ this.supabase = supabase;
4914
4996
  }
4915
4997
  /**
4916
- * Get all tables that use PowerSync (returns aliases for PowerSync schema)
4998
+ * Get current PowerSync instance.
4917
4999
  *
4918
- * @returns Array of PowerSync table aliases
5000
+ * @returns Current PowerSync database instance or null
4919
5001
  */
4920
- getPowerSyncTables() {
4921
- return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key, strategy]) => getPowerSyncAlias(key, strategy));
5002
+ getPowerSync() {
5003
+ return this.powerSyncDb;
4922
5004
  }
4923
5005
  /**
4924
- * Get the PowerSync alias for a table.
4925
- * Uses explicit alias from config if set, otherwise auto-generates.
5006
+ * Get current Supabase instance.
4926
5007
  *
4927
- * @param table - Table name (may be schema-qualified)
4928
- * @returns The alias to use in PowerSync schema
5008
+ * @returns Current Supabase client instance or null
4929
5009
  */
4930
- getTableAlias(table) {
4931
- const strategy = this.getTableStrategy(table);
4932
- const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4933
- const configKey = this.config.tables[table] ? table : tableWithoutSchema;
4934
- return getPowerSyncAlias(configKey, strategy);
5010
+ getSupabase() {
5011
+ return this.supabase;
4935
5012
  }
4936
5013
  // ===========================================================================
4937
- // Auto-Detection Methods
5014
+ // Options Management
4938
5015
  // ===========================================================================
4939
5016
  /**
4940
- * Get adapter using auto-detection
5017
+ * Update detector options.
4941
5018
  *
4942
- * @param strategy - Optional auto strategy configuration
4943
- * @returns The automatically selected adapter
5019
+ * @param options - New options to merge with existing
4944
5020
  */
4945
- getAutoAdapter(_strategy, table) {
4946
- if (!this.autoDetector) {
4947
- if (!this.supabaseAdapter) {
4948
- throw new Error("No auto-detector configured and Supabase adapter not available. Either initialize auto-detection or set adapters explicitly.");
4949
- }
4950
- if (typeof __DEV__ !== "undefined" && __DEV__) {
4951
- console.warn(`[AdapterRegistry] No auto-detector configured${table ? ` for table "${table}"` : ""}. Using Supabase fallback.`);
4952
- }
4953
- return this.supabaseAdapter;
4954
- }
4955
- const detection = this.autoDetector.detectSilent();
4956
- this.lastDetectionResult = detection;
4957
- if (detection.recommendedBackend === "powersync") {
4958
- if (!this.powerSyncAdapter) {
4959
- if (!this.supabaseAdapter) {
4960
- throw new Error("Neither PowerSync nor Supabase adapters are available.");
4961
- }
4962
- if (typeof __DEV__ !== "undefined" && __DEV__) {
4963
- console.warn(`[AdapterRegistry] PowerSync recommended${table ? ` for table "${table}"` : ""} but adapter not available. Falling back to Supabase. Reason: ${detection.reason}`);
4964
- }
4965
- return this.supabaseAdapter;
4966
- }
4967
- return this.powerSyncAdapter;
4968
- }
4969
- if (!this.supabaseAdapter) {
4970
- throw new Error("Supabase adapter not available and PowerSync not recommended.");
4971
- }
4972
- return this.supabaseAdapter;
5021
+ setOptions(options) {
5022
+ this.options = {
5023
+ ...this.options,
5024
+ ...options
5025
+ };
4973
5026
  }
4974
5027
  /**
4975
- * Subscribe to backend changes
5028
+ * Get current detector options.
4976
5029
  *
4977
- * @param callback - Function called when recommended backend changes
4978
- * @returns Unsubscribe function
5030
+ * @returns Current detector options
4979
5031
  */
4980
- onBackendChange(callback) {
4981
- this.backendChangeListeners.add(callback);
4982
- return () => {
4983
- this.backendChangeListeners.delete(callback);
5032
+ getOptions() {
5033
+ return {
5034
+ ...this.options
4984
5035
  };
4985
5036
  }
5037
+ // ===========================================================================
5038
+ // Listener Management
5039
+ // ===========================================================================
4986
5040
  /**
4987
- * Notify listeners of backend change
5041
+ * Add a listener for backend change events.
4988
5042
  *
4989
- * @param backend - The new recommended backend
5043
+ * @param listener - Callback to invoke when detection result changes
5044
+ * @returns Function to remove the listener
4990
5045
  */
4991
- notifyBackendChange(backend) {
4992
- this.backendChangeListeners.forEach((callback) => {
4993
- try {
4994
- callback(backend);
4995
- } catch (error) {
4996
- console.error("Error in backend change listener:", error);
4997
- }
4998
- });
5046
+ addListener(listener) {
5047
+ this.listeners.add(listener);
5048
+ return () => this.listeners.delete(listener);
4999
5049
  }
5000
5050
  /**
5001
- * Get the last auto-detection result
5051
+ * Remove a listener for backend change events.
5002
5052
  *
5003
- * @returns Last detection result or null if never detected
5053
+ * @param listener - Listener to remove
5004
5054
  */
5005
- getLastDetectionResult() {
5006
- return this.lastDetectionResult;
5055
+ removeListener(listener) {
5056
+ this.listeners.delete(listener);
5007
5057
  }
5008
5058
  /**
5009
- * Get the auto-detector instance
5059
+ * Get the last detection result.
5010
5060
  *
5011
- * @returns Auto-detector instance or null if not initialized
5061
+ * @returns Last detection result or null if never detected
5012
5062
  */
5013
- getAutoDetector() {
5014
- return this.autoDetector;
5063
+ getLastResult() {
5064
+ return this.lastResult;
5015
5065
  }
5016
5066
  // ===========================================================================
5017
- // Private Methods
5067
+ // Private Helper Methods
5018
5068
  // ===========================================================================
5019
5069
  /**
5020
- * Create an adapter based on the strategy type
5021
- *
5022
- * @param strategy - The table strategy configuration
5023
- * @returns The created adapter
5024
- * @throws Error if the required base adapter is not initialized
5070
+ * Check if the detection result has changed from the last result.
5025
5071
  */
5026
- createAdapter(strategy) {
5027
- switch (strategy.strategy) {
5028
- case "powersync":
5029
- if (!this.powerSyncAdapter) {
5030
- if (this.supabaseAdapter) {
5031
- if (typeof __DEV__ !== "undefined" && __DEV__) {
5032
- console.warn(`[AdapterRegistry] PowerSync not ready, falling back to Supabase`);
5033
- }
5034
- return this.supabaseAdapter;
5035
- }
5036
- throw new Error("PowerSync adapter not initialized and Supabase not available. Ensure PowerSyncAdapter is set before accessing PowerSync tables.");
5037
- }
5038
- return this.syncTrackingAdapter ?? this.powerSyncAdapter;
5039
- case "supabase":
5040
- if (!this.supabaseAdapter) {
5041
- throw new Error("Supabase adapter not initialized. Ensure SupabaseAdapter is set before accessing Supabase tables.");
5042
- }
5043
- return this.supabaseAdapter;
5044
- case "cached":
5045
- if (this.cachedAdapter) {
5046
- return this.cachedAdapter;
5047
- }
5048
- throw new Error("CachedAdapter not yet implemented. This feature will be available in Wave 2. For now, use 'supabase' strategy as a fallback.");
5049
- case "hybrid":
5050
- throw new Error("HybridAdapter not yet implemented. This feature will be available in Wave 2. For now, use 'powersync' or 'supabase' strategy as a fallback.");
5051
- default:
5052
- if (!this.supabaseAdapter) {
5053
- throw new Error("Supabase adapter not initialized. Ensure SupabaseAdapter is set before accessing tables.");
5054
- }
5055
- if (typeof __DEV__ !== "undefined" && __DEV__) {
5056
- console.warn(`[AdapterRegistry] Unknown strategy "${strategy.strategy}". Using Supabase fallback.`);
5057
- }
5058
- return this.supabaseAdapter;
5072
+ hasResultChanged(result) {
5073
+ if (!this.lastResult) {
5074
+ return true;
5059
5075
  }
5060
- }
5061
- // ===========================================================================
5062
- // Utility Methods
5063
- // ===========================================================================
5064
- /**
5065
- * Clear all cached adapters.
5066
- * Useful when configuration changes and adapters need to be recreated.
5067
- */
5068
- clearCache() {
5069
- this.adapters.clear();
5070
- }
5071
- /**
5072
- * Reset the registry to uninitialized state.
5073
- * Used during cleanup or testing.
5074
- */
5075
- reset() {
5076
- this.adapters.clear();
5077
- this.powerSyncAdapter = null;
5078
- this.powerSyncGetter = null;
5079
- this.powerSyncAdapterFactory = null;
5080
- this.supabaseAdapter = null;
5081
- this.cachedAdapter = null;
5082
- this.deps = null;
5083
- this._isInitialized = false;
5084
- this.autoDetector = null;
5085
- this.backendChangeListeners.clear();
5086
- this.lastDetectionResult = null;
5087
- this.syncTracker = null;
5088
- this.syncTrackingAdapter = null;
5089
- }
5090
- /**
5091
- * Dispose all adapters and clean up resources.
5092
- * Called when the DataLayerProvider unmounts.
5093
- */
5094
- dispose() {
5095
- this.reset();
5076
+ return this.lastResult.powerSyncStatus !== result.powerSyncStatus || this.lastResult.supabaseStatus !== result.supabaseStatus || this.lastResult.recommendedBackend !== result.recommendedBackend || this.lastResult.isOnline !== result.isOnline;
5096
5077
  }
5097
5078
  /**
5098
- * Get debug information about the registry state
5079
+ * Notify all listeners of a detection result change.
5099
5080
  */
5100
- getDebugInfo() {
5101
- return {
5102
- isInitialized: this._isInitialized,
5103
- hasPowerSync: this.powerSyncAdapter !== null,
5104
- hasSupabase: this.supabaseAdapter !== null,
5105
- hasCached: this.cachedAdapter !== null,
5106
- cachedAdapterCount: this.adapters.size,
5107
- configuredTableCount: Object.keys(this.config.tables).length,
5108
- powerSyncTables: this.getPowerSyncTables(),
5109
- hasAutoDetector: this.autoDetector !== null,
5110
- lastDetectionResult: this.lastDetectionResult
5111
- };
5081
+ notifyListeners(result) {
5082
+ const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : process.env.NODE_ENV !== "production";
5083
+ if (isDev) {
5084
+ const prevBackend = this.lastResult?.recommendedBackend;
5085
+ if (prevBackend && prevBackend !== result.recommendedBackend) {
5086
+ console.log(`[DataLayer] Backend switched: ${prevBackend} \u2192 ${result.recommendedBackend}`, `| Reason: ${result.reason}`);
5087
+ }
5088
+ if (result.recommendedBackend === "supabase" && this.options.preferPowerSync) {
5089
+ console.log(`[DataLayer] Using online fallback (Supabase)`, `| PowerSync: ${result.powerSyncStatus}`, `| Online: ${result.isOnline}`, `| Reason: ${result.reason}`);
5090
+ }
5091
+ }
5092
+ Array.from(this.listeners).forEach((listener) => {
5093
+ try {
5094
+ listener(result);
5095
+ } catch (error) {
5096
+ console.error("Error in backend change listener:", error);
5097
+ }
5098
+ });
5112
5099
  }
5113
5100
  };
5114
- function createAdapterRegistry(config) {
5115
- return new AdapterRegistry(config);
5101
+ function createAdapterAutoDetector(powerSyncDb, supabase, options) {
5102
+ return new AdapterAutoDetector(powerSyncDb, supabase, options);
5116
5103
  }
5117
5104
 
5118
5105
  // src/adapters/supabase-adapter.ts
@@ -7942,13 +7929,13 @@ export {
7942
7929
  useMutationSuccess,
7943
7930
  useMutationSuccessRN,
7944
7931
  ADAPTER_STRATEGIES,
7945
- BackendStatus,
7946
- AdapterAutoDetector,
7947
- createAdapterAutoDetector,
7948
7932
  SyncTrackingAdapter,
7949
7933
  stripSchemaPrefix,
7950
7934
  AdapterRegistry,
7951
7935
  createAdapterRegistry,
7936
+ BackendStatus,
7937
+ AdapterAutoDetector,
7938
+ createAdapterAutoDetector,
7952
7939
  SupabaseAdapter,
7953
7940
  createSupabaseAdapter,
7954
7941
  getErrorBody,
@@ -8005,4 +7992,4 @@ moment/moment.js:
8005
7992
  (*! license : MIT *)
8006
7993
  (*! momentjs.com *)
8007
7994
  */
8008
- //# sourceMappingURL=chunk-PGTRDT5K.js.map
7995
+ //# sourceMappingURL=chunk-FZJX2FZS.js.map