@pol-studios/db 1.0.51 → 1.0.52

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) {
@@ -4623,496 +4295,803 @@ var AdapterRegistry = class {
4623
4295
  */
4624
4296
  adapters = /* @__PURE__ */ new Map();
4625
4297
  /**
4626
- * PowerSync adapter instance (set during initialization or lazily via getter)
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
+ return this.supabaseAdapter;
4516
+ }
4517
+ }
4518
+ const existing = this.adapters.get(table);
4519
+ if (existing) {
4520
+ return existing;
4521
+ }
4522
+ const adapter = this.createAdapter(strategy);
4523
+ this.adapters.set(table, adapter);
4524
+ return adapter;
4525
+ }
4526
+ /**
4527
+ * Get the PowerSync adapter directly
4528
+ *
4529
+ * @returns PowerSync adapter or null if not initialized
4530
+ */
4531
+ getPowerSyncAdapter() {
4532
+ return this.powerSyncAdapter;
4533
+ }
4534
+ /**
4535
+ * Get the Supabase adapter directly
4536
+ *
4537
+ * @returns Supabase adapter or null if not initialized
4538
+ */
4539
+ getSupabaseAdapter() {
4540
+ return this.supabaseAdapter;
4541
+ }
4542
+ /**
4543
+ * Get the Cached adapter directly
4544
+ *
4545
+ * @returns Cached adapter or null if not initialized
4627
4546
  */
4628
- powerSyncAdapter = null;
4547
+ getCachedAdapter() {
4548
+ return this.cachedAdapter;
4549
+ }
4629
4550
  /**
4630
- * Getter function to retrieve PowerSync database instance.
4631
- * Used for lazy initialization when PowerSync becomes available after registry creation.
4551
+ * Get all configured table names
4552
+ *
4553
+ * @returns Array of table names with explicit strategy configuration
4632
4554
  */
4633
- powerSyncGetter = null;
4555
+ getConfiguredTables() {
4556
+ return Object.keys(this.config.tables);
4557
+ }
4634
4558
  /**
4635
- * Factory function to create PowerSync adapter from database instance.
4636
- * Set via setPowerSyncGetter along with the getter.
4559
+ * Get the strategy for a specific table
4560
+ *
4561
+ * @param table - The table name (may include schema prefix like "core.Profile")
4562
+ * @returns The table strategy or undefined if not configured
4637
4563
  */
4638
- powerSyncAdapterFactory = null;
4564
+ getTableStrategy(table) {
4565
+ const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4566
+ return this.config.tables[table] ?? this.config.tables[tableWithoutSchema];
4567
+ }
4639
4568
  /**
4640
- * Supabase adapter instance (set during initialization)
4569
+ * Check if a table uses PowerSync strategy
4570
+ *
4571
+ * @param table - The table name (may include schema prefix like "core.Profile")
4572
+ * @returns True if table uses PowerSync, Hybrid, or Auto strategy
4641
4573
  */
4642
- supabaseAdapter = null;
4574
+ usesPowerSync(table) {
4575
+ const strategy = this.getTableStrategy(table);
4576
+ return strategy?.strategy === "powersync" || strategy?.strategy === "hybrid" || strategy?.strategy === "auto";
4577
+ }
4643
4578
  /**
4644
- * Cached adapter instance (wraps Supabase with TanStack Query)
4579
+ * Get all table config keys that use PowerSync (may be schema-qualified)
4580
+ *
4581
+ * @returns Array of config keys using PowerSync, Hybrid, or Auto strategy
4645
4582
  */
4646
- cachedAdapter = null;
4583
+ getPowerSyncTableKeys() {
4584
+ return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key]) => key);
4585
+ }
4647
4586
  /**
4648
- * Dependencies for creating adapters
4587
+ * Get all tables that use PowerSync (returns aliases for PowerSync schema)
4588
+ *
4589
+ * @returns Array of PowerSync table aliases
4649
4590
  */
4650
- deps = null;
4591
+ getPowerSyncTables() {
4592
+ return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key, strategy]) => getPowerSyncAlias(key, strategy));
4593
+ }
4651
4594
  /**
4652
- * Whether the registry has been initialized with adapters
4595
+ * Get the PowerSync alias for a table.
4596
+ * Uses explicit alias from config if set, otherwise auto-generates.
4597
+ *
4598
+ * @param table - Table name (may be schema-qualified)
4599
+ * @returns The alias to use in PowerSync schema
4653
4600
  */
4654
- _isInitialized = false;
4601
+ getTableAlias(table) {
4602
+ const strategy = this.getTableStrategy(table);
4603
+ const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4604
+ const configKey = this.config.tables[table] ? table : tableWithoutSchema;
4605
+ return getPowerSyncAlias(configKey, strategy);
4606
+ }
4607
+ // ===========================================================================
4608
+ // Auto-Detection Methods
4609
+ // ===========================================================================
4655
4610
  /**
4656
- * Auto-detector instance for automatic backend selection
4611
+ * Get adapter using auto-detection.
4612
+ *
4613
+ * Simple logic: PowerSync first, Supabase fallback.
4614
+ * Once PowerSync is initialized, always use it for offline-first behavior.
4657
4615
  */
4658
- autoDetector = null;
4616
+ getAutoAdapter(_strategy, _table) {
4617
+ if (this.powerSyncAdapter) {
4618
+ return this.powerSyncAdapter;
4619
+ }
4620
+ if (this.supabaseAdapter) {
4621
+ return this.supabaseAdapter;
4622
+ }
4623
+ throw new Error("No adapters available. Initialize PowerSync or Supabase.");
4624
+ }
4659
4625
  /**
4660
- * Sync tracker for mutation tracking
4626
+ * Subscribe to backend changes
4627
+ *
4628
+ * @param callback - Function called when recommended backend changes
4629
+ * @returns Unsubscribe function
4661
4630
  */
4662
- syncTracker = null;
4631
+ onBackendChange(callback) {
4632
+ this.backendChangeListeners.add(callback);
4633
+ return () => {
4634
+ this.backendChangeListeners.delete(callback);
4635
+ };
4636
+ }
4663
4637
  /**
4664
- * Sync tracking adapter that wraps PowerSync adapter
4638
+ * Notify listeners of backend change
4639
+ *
4640
+ * @param backend - The new recommended backend
4665
4641
  */
4666
- syncTrackingAdapter = null;
4642
+ notifyBackendChange(backend) {
4643
+ this.backendChangeListeners.forEach((callback) => {
4644
+ try {
4645
+ callback(backend);
4646
+ } catch (error) {
4647
+ console.error("Error in backend change listener:", error);
4648
+ }
4649
+ });
4650
+ }
4667
4651
  /**
4668
- * Listeners for backend change events
4652
+ * Get the last auto-detection result
4653
+ *
4654
+ * @returns Last detection result or null if never detected
4669
4655
  */
4670
- backendChangeListeners = /* @__PURE__ */ new Set();
4656
+ getLastDetectionResult() {
4657
+ return this.lastDetectionResult;
4658
+ }
4671
4659
  /**
4672
- * Last auto-detection result for debugging and status
4660
+ * Get the auto-detector instance
4661
+ *
4662
+ * @returns Auto-detector instance or null if not initialized
4673
4663
  */
4674
- lastDetectionResult = null;
4664
+ getAutoDetector() {
4665
+ return this.autoDetector;
4666
+ }
4675
4667
  // ===========================================================================
4676
- // Initialization
4668
+ // Private Methods
4677
4669
  // ===========================================================================
4678
4670
  /**
4679
- * Check if the registry has been initialized
4671
+ * Create an adapter based on the strategy type
4672
+ *
4673
+ * @param strategy - The table strategy configuration
4674
+ * @returns The created adapter
4675
+ * @throws Error if the required base adapter is not initialized
4680
4676
  */
4681
- get isInitialized() {
4682
- return this._isInitialized;
4677
+ createAdapter(strategy) {
4678
+ switch (strategy.strategy) {
4679
+ case "powersync":
4680
+ if (!this.powerSyncAdapter) {
4681
+ if (this.supabaseAdapter) {
4682
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
4683
+ console.warn(`[AdapterRegistry] PowerSync not ready, falling back to Supabase`);
4684
+ }
4685
+ return this.supabaseAdapter;
4686
+ }
4687
+ throw new Error("PowerSync adapter not initialized and Supabase not available. Ensure PowerSyncAdapter is set before accessing PowerSync tables.");
4688
+ }
4689
+ return this.syncTrackingAdapter ?? this.powerSyncAdapter;
4690
+ case "supabase":
4691
+ if (!this.supabaseAdapter) {
4692
+ throw new Error("Supabase adapter not initialized. Ensure SupabaseAdapter is set before accessing Supabase tables.");
4693
+ }
4694
+ return this.supabaseAdapter;
4695
+ case "cached":
4696
+ if (this.cachedAdapter) {
4697
+ return this.cachedAdapter;
4698
+ }
4699
+ throw new Error("CachedAdapter not yet implemented. This feature will be available in Wave 2. For now, use 'supabase' strategy as a fallback.");
4700
+ case "hybrid":
4701
+ 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.");
4702
+ default:
4703
+ if (!this.supabaseAdapter) {
4704
+ throw new Error("Supabase adapter not initialized. Ensure SupabaseAdapter is set before accessing tables.");
4705
+ }
4706
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
4707
+ console.warn(`[AdapterRegistry] Unknown strategy "${strategy.strategy}". Using Supabase fallback.`);
4708
+ }
4709
+ return this.supabaseAdapter;
4710
+ }
4683
4711
  }
4712
+ // ===========================================================================
4713
+ // Utility Methods
4714
+ // ===========================================================================
4684
4715
  /**
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
4716
+ * Clear all cached adapters.
4717
+ * Useful when configuration changes and adapters need to be recreated.
4689
4718
  */
4690
- initialize(deps) {
4691
- this.deps = deps;
4692
- this._isInitialized = true;
4719
+ clearCache() {
4720
+ this.adapters.clear();
4693
4721
  }
4694
4722
  /**
4695
- * Set the PowerSync adapter instance
4696
- *
4697
- * @param adapter - PowerSync adapter implementation
4723
+ * Reset the registry to uninitialized state.
4724
+ * Used during cleanup or testing.
4698
4725
  */
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
- }
4726
+ reset() {
4706
4727
  this.adapters.clear();
4728
+ this.powerSyncAdapter = null;
4729
+ this.powerSyncGetter = null;
4730
+ this.powerSyncAdapterFactory = null;
4731
+ this.supabaseAdapter = null;
4732
+ this.cachedAdapter = null;
4733
+ this.deps = null;
4734
+ this._isInitialized = false;
4735
+ this.autoDetector = null;
4736
+ this.backendChangeListeners.clear();
4737
+ this.lastDetectionResult = null;
4738
+ this.syncTracker = null;
4739
+ this.syncTrackingAdapter = null;
4707
4740
  }
4708
4741
  /**
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
4742
+ * Dispose all adapters and clean up resources.
4743
+ * Called when the DataLayerProvider unmounts.
4717
4744
  */
4718
- setPowerSyncGetter(getter, factory) {
4719
- this.powerSyncGetter = getter;
4720
- this.powerSyncAdapterFactory = factory;
4745
+ dispose() {
4746
+ this.reset();
4747
+ }
4748
+ /**
4749
+ * Get debug information about the registry state
4750
+ */
4751
+ getDebugInfo() {
4752
+ return {
4753
+ isInitialized: this._isInitialized,
4754
+ hasPowerSync: this.powerSyncAdapter !== null,
4755
+ hasSupabase: this.supabaseAdapter !== null,
4756
+ hasCached: this.cachedAdapter !== null,
4757
+ cachedAdapterCount: this.adapters.size,
4758
+ configuredTableCount: Object.keys(this.config.tables).length,
4759
+ powerSyncTables: this.getPowerSyncTables(),
4760
+ hasAutoDetector: this.autoDetector !== null,
4761
+ lastDetectionResult: this.lastDetectionResult
4762
+ };
4763
+ }
4764
+ };
4765
+ function createAdapterRegistry(config) {
4766
+ return new AdapterRegistry(config);
4767
+ }
4768
+
4769
+ // src/adapters/auto-detector.ts
4770
+ var BackendStatus = /* @__PURE__ */ ((BackendStatus2) => {
4771
+ BackendStatus2["AVAILABLE"] = "available";
4772
+ BackendStatus2["INITIALIZING"] = "initializing";
4773
+ BackendStatus2["UNAVAILABLE"] = "unavailable";
4774
+ return BackendStatus2;
4775
+ })(BackendStatus || {});
4776
+ var AdapterAutoDetector = class {
4777
+ constructor(powerSyncDb, supabase, options = {}) {
4778
+ this.powerSyncDb = powerSyncDb;
4779
+ this.supabase = supabase;
4780
+ this.options = {
4781
+ preferPowerSync: options.preferPowerSync ?? true,
4782
+ statusCheckTimeout: options.statusCheckTimeout ?? 1e3,
4783
+ useOnlineUntilSynced: options.useOnlineUntilSynced ?? true
4784
+ };
4721
4785
  }
4786
+ options;
4787
+ listeners = /* @__PURE__ */ new Set();
4788
+ lastResult = null;
4789
+ syncStatus = null;
4722
4790
  /**
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.
4791
+ * Update the PowerSync database reference.
4792
+ * Called when PowerSync becomes available after initial construction.
4727
4793
  *
4728
- * @returns true if PowerSync adapter is available, false otherwise
4794
+ * @param db - PowerSync database instance or null
4729
4795
  */
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);
4796
+ setPowerSyncDb(db) {
4797
+ this.powerSyncDb = db;
4798
+ if (db) {
4799
+ this.detect();
4745
4800
  }
4746
- return true;
4747
4801
  }
4748
4802
  /**
4749
- * Set the sync tracker for mutation tracking.
4750
- * When set, write operations on PowerSync tables will
4751
- * automatically track pending mutations.
4803
+ * Update the sync status from PowerSync.
4804
+ * Called when sync status changes to re-evaluate backend recommendation.
4752
4805
  *
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.
4806
+ * @param status - Current sync status or null if not available
4757
4807
  */
4758
- setSyncTracker(tracker) {
4759
- this.syncTracker = tracker;
4760
- if (this.powerSyncAdapter) {
4761
- this.syncTrackingAdapter = new SyncTrackingAdapter(this.powerSyncAdapter, tracker);
4808
+ updateSyncStatus(status) {
4809
+ const hadSynced = this.syncStatus?.hasSynced;
4810
+ const hasSyncedNow = status?.hasSynced;
4811
+ const changed = hadSynced !== hasSyncedNow;
4812
+ this.syncStatus = status;
4813
+ if (changed) {
4814
+ this.detect();
4762
4815
  }
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
4816
  }
4773
4817
  /**
4774
- * Set the Cached adapter instance
4775
- *
4776
- * @param adapter - Cached adapter implementation
4777
- */
4778
- setCachedAdapter(adapter) {
4779
- this.cachedAdapter = adapter;
4780
- }
4781
- /**
4782
- * Initialize auto-detection with a detector instance
4783
- *
4784
- * @param detector - The auto-detector to use
4818
+ * Get current sync status.
4819
+ * @returns Current sync status or null
4785
4820
  */
4786
- initializeAutoDetection(detector) {
4787
- this.autoDetector = detector;
4788
- detector.addListener((result) => {
4789
- this.lastDetectionResult = result;
4790
- this.notifyBackendChange(result.recommendedBackend);
4791
- });
4821
+ getSyncStatus() {
4822
+ return this.syncStatus;
4792
4823
  }
4793
4824
  // ===========================================================================
4794
- // Adapter Access
4825
+ // Main Detection Methods
4795
4826
  // ===========================================================================
4796
4827
  /**
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.
4828
+ * Detect backend availability and recommend best option.
4808
4829
  *
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
4830
+ * The detection logic follows this priority:
4831
+ * 1. If preferPowerSync is true and PowerSync is available, use PowerSync
4832
+ * 2. If PowerSync is initializing and online with Supabase available, use Supabase temporarily
4833
+ * 3. If online with Supabase available, use Supabase
4834
+ * 4. If offline but PowerSync available, use PowerSync (offline mode)
4835
+ * 5. If offline and PowerSync exists (even if initializing), use PowerSync local data
4836
+ * 6. Default to Supabase as fallback
4812
4837
  *
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
4838
+ * @returns Detection result with recommendation and reasoning
4817
4839
  */
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;
4840
+ detect() {
4841
+ const result = this.performDetection();
4842
+ if (this.hasResultChanged(result)) {
4843
+ this.lastResult = result;
4844
+ this.notifyListeners(result);
4850
4845
  }
4851
- const adapter = this.createAdapter(strategy);
4852
- this.adapters.set(table, adapter);
4853
- return adapter;
4846
+ return result;
4854
4847
  }
4855
4848
  /**
4856
- * Get the PowerSync adapter directly
4849
+ * Detect backend availability WITHOUT notifying listeners.
4857
4850
  *
4858
- * @returns PowerSync adapter or null if not initialized
4851
+ * This method is safe to call during React's render phase because it does not
4852
+ * trigger state updates in other components. Use this method when you need to
4853
+ * check backend status synchronously during render (e.g., in getAdapter()).
4854
+ *
4855
+ * The regular detect() method with listener notifications should only be called
4856
+ * from useEffect or event handlers to avoid the React warning:
4857
+ * "Cannot update a component while rendering a different component"
4858
+ *
4859
+ * @returns Detection result with recommendation and reasoning
4859
4860
  */
4860
- getPowerSyncAdapter() {
4861
- return this.powerSyncAdapter;
4861
+ detectSilent() {
4862
+ const result = this.performDetection();
4863
+ this.lastResult = result;
4864
+ return result;
4862
4865
  }
4863
4866
  /**
4864
- * Get the Supabase adapter directly
4865
- *
4866
- * @returns Supabase adapter or null if not initialized
4867
+ * Internal detection logic shared by detect() and detectSilent().
4868
+ * @private
4867
4869
  */
4868
- getSupabaseAdapter() {
4869
- return this.supabaseAdapter;
4870
+ performDetection() {
4871
+ const powerSyncStatus = this.detectPowerSyncStatus();
4872
+ const supabaseStatus = this.detectSupabaseStatus();
4873
+ const isOnline = this.checkOnlineStatus();
4874
+ let recommendedBackend;
4875
+ let reason;
4876
+ if (this.options.preferPowerSync && powerSyncStatus === "available" /* AVAILABLE */) {
4877
+ recommendedBackend = "powersync";
4878
+ reason = "PowerSync is available and preferred for offline-first experience";
4879
+ } else if (powerSyncStatus === "initializing" /* INITIALIZING */ && isOnline && supabaseStatus === "available" /* AVAILABLE */) {
4880
+ recommendedBackend = "supabase";
4881
+ reason = "PowerSync initial sync in progress; using Supabase for fresh data";
4882
+ } else if (supabaseStatus === "available" /* AVAILABLE */ && isOnline) {
4883
+ recommendedBackend = "supabase";
4884
+ reason = "Using Supabase direct connection";
4885
+ } else if (powerSyncStatus === "available" /* AVAILABLE */) {
4886
+ recommendedBackend = "powersync";
4887
+ reason = "Offline mode using PowerSync local data";
4888
+ } else if (!isOnline && this.powerSyncDb) {
4889
+ recommendedBackend = "powersync";
4890
+ reason = "Offline mode - using PowerSync local data (initial sync may be incomplete)";
4891
+ } else {
4892
+ recommendedBackend = "supabase";
4893
+ reason = "No confirmed available backend; defaulting to Supabase";
4894
+ }
4895
+ return {
4896
+ powerSyncStatus,
4897
+ supabaseStatus,
4898
+ recommendedBackend,
4899
+ isOnline,
4900
+ reason
4901
+ };
4870
4902
  }
4871
4903
  /**
4872
- * Get the Cached adapter directly
4904
+ * Check if PowerSync is available.
4873
4905
  *
4874
- * @returns Cached adapter or null if not initialized
4906
+ * PowerSync status depends on:
4907
+ * 1. Database instance exists
4908
+ * 2. If useOnlineUntilSynced is true, also checks if initial sync completed
4909
+ *
4910
+ * @returns PowerSync backend status
4875
4911
  */
4876
- getCachedAdapter() {
4877
- return this.cachedAdapter;
4912
+ detectPowerSyncStatus() {
4913
+ if (!this.powerSyncDb) {
4914
+ return "unavailable" /* UNAVAILABLE */;
4915
+ }
4916
+ try {
4917
+ if (typeof this.powerSyncDb.getAll !== "function") {
4918
+ return "initializing" /* INITIALIZING */;
4919
+ }
4920
+ if (this.options.useOnlineUntilSynced) {
4921
+ if (!this.syncStatus || !this.syncStatus.hasSynced) {
4922
+ return "initializing" /* INITIALIZING */;
4923
+ }
4924
+ }
4925
+ return "available" /* AVAILABLE */;
4926
+ } catch {
4927
+ return "initializing" /* INITIALIZING */;
4928
+ }
4878
4929
  }
4879
4930
  /**
4880
- * Get all configured table names
4931
+ * Check if Supabase is available.
4881
4932
  *
4882
- * @returns Array of table names with explicit strategy configuration
4933
+ * Supabase is considered available if we have a client instance.
4934
+ * The actual network connectivity is checked separately via checkOnlineStatus().
4935
+ *
4936
+ * @returns Supabase backend status
4883
4937
  */
4884
- getConfiguredTables() {
4885
- return Object.keys(this.config.tables);
4938
+ detectSupabaseStatus() {
4939
+ if (!this.supabase) {
4940
+ return "unavailable" /* UNAVAILABLE */;
4941
+ }
4942
+ try {
4943
+ if (typeof this.supabase.from === "function") {
4944
+ return "available" /* AVAILABLE */;
4945
+ }
4946
+ return "unavailable" /* UNAVAILABLE */;
4947
+ } catch {
4948
+ return "unavailable" /* UNAVAILABLE */;
4949
+ }
4886
4950
  }
4887
4951
  /**
4888
- * Get the strategy for a specific table
4952
+ * Check if device is online.
4889
4953
  *
4890
- * @param table - The table name (may include schema prefix like "core.Profile")
4891
- * @returns The table strategy or undefined if not configured
4954
+ * Prefers the sync status's isOnline property if available (from React Native NetInfo).
4955
+ * Falls back to navigator.onLine in browser environments.
4956
+ * Returns false by default for non-browser environments (React Native, Node.js, etc.)
4957
+ * to ensure offline-first behavior works correctly.
4958
+ *
4959
+ * @returns Whether the device has network connectivity
4892
4960
  */
4893
- getTableStrategy(table) {
4894
- const tableWithoutSchema = table.includes(".") ? table.split(".")[1] : table;
4895
- return this.config.tables[table] ?? this.config.tables[tableWithoutSchema];
4961
+ checkOnlineStatus() {
4962
+ if (this.syncStatus?.isOnline !== void 0) {
4963
+ return this.syncStatus.isOnline;
4964
+ }
4965
+ if (typeof window !== "undefined" && typeof navigator !== "undefined") {
4966
+ return navigator.onLine;
4967
+ }
4968
+ return false;
4896
4969
  }
4970
+ // ===========================================================================
4971
+ // Instance Management Methods
4972
+ // ===========================================================================
4897
4973
  /**
4898
- * Check if a table uses PowerSync strategy
4974
+ * Update PowerSync instance (e.g., when it becomes available).
4899
4975
  *
4900
- * @param table - The table name (may include schema prefix like "core.Profile")
4901
- * @returns True if table uses PowerSync, Hybrid, or Auto strategy
4976
+ * @param db - New PowerSync database instance or null
4902
4977
  */
4903
- usesPowerSync(table) {
4904
- const strategy = this.getTableStrategy(table);
4905
- return strategy?.strategy === "powersync" || strategy?.strategy === "hybrid" || strategy?.strategy === "auto";
4978
+ setPowerSync(db) {
4979
+ this.powerSyncDb = db;
4906
4980
  }
4907
4981
  /**
4908
- * Get all table config keys that use PowerSync (may be schema-qualified)
4982
+ * Update Supabase instance.
4909
4983
  *
4910
- * @returns Array of config keys using PowerSync, Hybrid, or Auto strategy
4984
+ * @param supabase - New Supabase client instance or null
4911
4985
  */
4912
- getPowerSyncTableKeys() {
4913
- return Object.entries(this.config.tables).filter(([_, strategy]) => strategy.strategy === "powersync" || strategy.strategy === "hybrid" || strategy.strategy === "auto").map(([key]) => key);
4986
+ setSupabase(supabase) {
4987
+ this.supabase = supabase;
4914
4988
  }
4915
4989
  /**
4916
- * Get all tables that use PowerSync (returns aliases for PowerSync schema)
4990
+ * Get current PowerSync instance.
4917
4991
  *
4918
- * @returns Array of PowerSync table aliases
4992
+ * @returns Current PowerSync database instance or null
4919
4993
  */
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));
4994
+ getPowerSync() {
4995
+ return this.powerSyncDb;
4922
4996
  }
4923
4997
  /**
4924
- * Get the PowerSync alias for a table.
4925
- * Uses explicit alias from config if set, otherwise auto-generates.
4998
+ * Get current Supabase instance.
4926
4999
  *
4927
- * @param table - Table name (may be schema-qualified)
4928
- * @returns The alias to use in PowerSync schema
5000
+ * @returns Current Supabase client instance or null
4929
5001
  */
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);
5002
+ getSupabase() {
5003
+ return this.supabase;
4935
5004
  }
4936
5005
  // ===========================================================================
4937
- // Auto-Detection Methods
5006
+ // Options Management
4938
5007
  // ===========================================================================
4939
5008
  /**
4940
- * Get adapter using auto-detection
5009
+ * Update detector options.
4941
5010
  *
4942
- * @param strategy - Optional auto strategy configuration
4943
- * @returns The automatically selected adapter
5011
+ * @param options - New options to merge with existing
4944
5012
  */
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;
5013
+ setOptions(options) {
5014
+ this.options = {
5015
+ ...this.options,
5016
+ ...options
5017
+ };
4973
5018
  }
4974
5019
  /**
4975
- * Subscribe to backend changes
5020
+ * Get current detector options.
4976
5021
  *
4977
- * @param callback - Function called when recommended backend changes
4978
- * @returns Unsubscribe function
5022
+ * @returns Current detector options
4979
5023
  */
4980
- onBackendChange(callback) {
4981
- this.backendChangeListeners.add(callback);
4982
- return () => {
4983
- this.backendChangeListeners.delete(callback);
5024
+ getOptions() {
5025
+ return {
5026
+ ...this.options
4984
5027
  };
4985
5028
  }
5029
+ // ===========================================================================
5030
+ // Listener Management
5031
+ // ===========================================================================
4986
5032
  /**
4987
- * Notify listeners of backend change
5033
+ * Add a listener for backend change events.
4988
5034
  *
4989
- * @param backend - The new recommended backend
5035
+ * @param listener - Callback to invoke when detection result changes
5036
+ * @returns Function to remove the listener
4990
5037
  */
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
- });
5038
+ addListener(listener) {
5039
+ this.listeners.add(listener);
5040
+ return () => this.listeners.delete(listener);
4999
5041
  }
5000
5042
  /**
5001
- * Get the last auto-detection result
5043
+ * Remove a listener for backend change events.
5002
5044
  *
5003
- * @returns Last detection result or null if never detected
5045
+ * @param listener - Listener to remove
5004
5046
  */
5005
- getLastDetectionResult() {
5006
- return this.lastDetectionResult;
5047
+ removeListener(listener) {
5048
+ this.listeners.delete(listener);
5007
5049
  }
5008
5050
  /**
5009
- * Get the auto-detector instance
5051
+ * Get the last detection result.
5010
5052
  *
5011
- * @returns Auto-detector instance or null if not initialized
5053
+ * @returns Last detection result or null if never detected
5012
5054
  */
5013
- getAutoDetector() {
5014
- return this.autoDetector;
5055
+ getLastResult() {
5056
+ return this.lastResult;
5015
5057
  }
5016
5058
  // ===========================================================================
5017
- // Private Methods
5059
+ // Private Helper Methods
5018
5060
  // ===========================================================================
5019
5061
  /**
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
5062
+ * Check if the detection result has changed from the last result.
5025
5063
  */
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;
5064
+ hasResultChanged(result) {
5065
+ if (!this.lastResult) {
5066
+ return true;
5059
5067
  }
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();
5068
+ return this.lastResult.powerSyncStatus !== result.powerSyncStatus || this.lastResult.supabaseStatus !== result.supabaseStatus || this.lastResult.recommendedBackend !== result.recommendedBackend || this.lastResult.isOnline !== result.isOnline;
5096
5069
  }
5097
5070
  /**
5098
- * Get debug information about the registry state
5071
+ * Notify all listeners of a detection result change.
5099
5072
  */
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
- };
5073
+ notifyListeners(result) {
5074
+ const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : process.env.NODE_ENV !== "production";
5075
+ if (isDev) {
5076
+ const prevBackend = this.lastResult?.recommendedBackend;
5077
+ if (prevBackend && prevBackend !== result.recommendedBackend) {
5078
+ console.log(`[DataLayer] Backend switched: ${prevBackend} \u2192 ${result.recommendedBackend}`, `| Reason: ${result.reason}`);
5079
+ }
5080
+ if (result.recommendedBackend === "supabase" && this.options.preferPowerSync) {
5081
+ console.log(`[DataLayer] Using online fallback (Supabase)`, `| PowerSync: ${result.powerSyncStatus}`, `| Online: ${result.isOnline}`, `| Reason: ${result.reason}`);
5082
+ }
5083
+ }
5084
+ Array.from(this.listeners).forEach((listener) => {
5085
+ try {
5086
+ listener(result);
5087
+ } catch (error) {
5088
+ console.error("Error in backend change listener:", error);
5089
+ }
5090
+ });
5112
5091
  }
5113
5092
  };
5114
- function createAdapterRegistry(config) {
5115
- return new AdapterRegistry(config);
5093
+ function createAdapterAutoDetector(powerSyncDb, supabase, options) {
5094
+ return new AdapterAutoDetector(powerSyncDb, supabase, options);
5116
5095
  }
5117
5096
 
5118
5097
  // src/adapters/supabase-adapter.ts
@@ -7942,13 +7921,13 @@ export {
7942
7921
  useMutationSuccess,
7943
7922
  useMutationSuccessRN,
7944
7923
  ADAPTER_STRATEGIES,
7945
- BackendStatus,
7946
- AdapterAutoDetector,
7947
- createAdapterAutoDetector,
7948
7924
  SyncTrackingAdapter,
7949
7925
  stripSchemaPrefix,
7950
7926
  AdapterRegistry,
7951
7927
  createAdapterRegistry,
7928
+ BackendStatus,
7929
+ AdapterAutoDetector,
7930
+ createAdapterAutoDetector,
7952
7931
  SupabaseAdapter,
7953
7932
  createSupabaseAdapter,
7954
7933
  getErrorBody,
@@ -8005,4 +7984,4 @@ moment/moment.js:
8005
7984
  (*! license : MIT *)
8006
7985
  (*! momentjs.com *)
8007
7986
  */
8008
- //# sourceMappingURL=chunk-PGTRDT5K.js.map
7987
+ //# sourceMappingURL=chunk-EOJGUOJR.js.map