@use-stall/core 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,59 +12,76 @@ The Stall Core SDK provides an adapter-based architecture that allows you to:
12
12
  - **Implement CRUD + bulk operations** with a consistent API
13
13
  - **Handle errors gracefully** with try-catch wrapped operations
14
14
  - **Cache adapter modules** in IndexedDB with TTL-based invalidation
15
+ - **Work offline-first** with automatic local data caching and intelligent sync queuing
16
+ - **Sync intelligently** with dependency-aware batch processing and automatic ID management
15
17
 
16
18
  ## Architecture
17
19
 
18
20
  ```
19
- ┌─────────────────────────────────────┐
20
- Your Application
21
- └──────────────┬──────────────────────┘
22
-
23
- ┌───────▼──────────────────┐
24
- │ initializeStallCore()
25
- │ Returns: CoreConfig
26
- └───────┬──────────────────┘
27
-
28
- ┌──────────▼──────────┐
29
- Service Layer
30
- ├─────────────────────┤
31
- │ - products
32
- │ - orders
33
- │ - customers │
34
- - variants │
35
- │ - inventory_levels │
36
- - promotions
37
- │ - refunds │
38
- - payments
39
- - tax_regions
40
- - locations
41
- - fulfillments
42
- - ... and more
43
- └──────────┬──────────┘
44
-
45
- ┌──────────▼──────────────────┐
46
- Adapter Module (Cached)
47
- ┌───────────────────────┐
48
- - Products module │ │
49
- - Orders module
50
- - Customers module │ │
51
- - All modules
52
- │ └───────────────────────┘ │
53
-
54
- │ Storage: IndexedDB + Dexie │
55
- TTL: 4 hours (configurable)
56
- └──────────┬──────────────────┘
57
-
58
- ┌──────────▼──────────────────┐
59
- Connector Loader
60
- (Dynamic Module Import)
61
- └──────────┬──────────────────┘
62
-
63
- ┌──────────▼──────────────────┐
64
- │ Commerce Platform API │
65
- │ (WooCommerce, Shopify,
66
- Medusa,Google Sheets etc.)
67
- └─────────────────────────────┘
21
+ ┌──────────────────────────────────────────┐
22
+ Your Application
23
+ └────────────────┬─────────────────────────┘
24
+
25
+ ┌───────▼──────────────────┐
26
+ │ initializeStallCore()
27
+ │ Returns: CoreConfig
28
+ └───────┬──────────────────┘
29
+
30
+ ┌────────────▼──────────────┐
31
+ Service Layer
32
+ ├──────────────────────────┤
33
+ │ - products, orders, etc.
34
+ │ - All 16+ services
35
+ └────────────┬─────────────┘
36
+
37
+ ┌────────────▼────────────────────────┐
38
+ Offline-First Processing
39
+ ├─────────────────────────────────────┤
40
+ Read Operations:
41
+ 1. Fetch from Connector
42
+ 2. Cache in Local DB (Dexie)
43
+ 3. Return cached data
44
+
45
+ │ Write Operations: │
46
+ 1. Generate temporary offline ID │
47
+ │ 2. Save locally immediately │
48
+ 3. Queue for sync
49
+ 4. Return local data instantly
50
+
51
+ Storage: IndexedDB + Dexie
52
+ Tables: products, orders, sync_queue,
53
+ sync_logs, customers, etc.
54
+ └────────────┬────────────────────────┘
55
+
56
+ ┌────────────▼──────────────────┐
57
+ Sync Queue & Smart Sync
58
+ ├───────────────────────────────┤
59
+ - Dependency-aware processing │
60
+ │ - 5-layer sync strategy │
61
+ - Automatic ID replacement
62
+ - Batch sync operations
63
+ │ - Sync status logging │
64
+ └────────────┬──────────────────┘
65
+
66
+ ┌────────────▼──────────────────────┐
67
+ Adapter Module (Cached)
68
+ ┌─────────────────────────────┐
69
+ │ │ - Products, Orders modules │ │
70
+ │ │ - All connector modules │ │
71
+ │ └─────────────────────────────┘ │
72
+ │ Caching: TTL 4 hours (default) │
73
+ └────────────┬──────────────────────┘
74
+
75
+ ┌────────────▼──────────────────┐
76
+ │ Connector Loader │
77
+ │ (Dynamic Module Import) │
78
+ └────────────┬──────────────────┘
79
+
80
+ ┌────────────▼───────────────────────┐
81
+ │ Commerce Platform API │
82
+ │ (WooCommerce, Shopify, Medusa, │
83
+ │ Shopware, Google Sheets, etc.) │
84
+ └────────────────────────────────────┘
68
85
  ```
69
86
 
70
87
  ## Installation
@@ -264,6 +281,113 @@ const fulfillment = await fulfillments.retrieve({ sdk: stall, id: 'ful_123' });
264
281
  const newFulfillment = await fulfillments.create({ sdk: stall, data: { order_id: 'order_123' } });
265
282
  ```
266
283
 
284
+ ## Offline-First Caching
285
+
286
+ ### How It Works
287
+
288
+ All service operations use a local-first, cloud-sync strategy:
289
+
290
+ **Read Operations:**
291
+ 1. Service method calls connector adapter via `sdk.adapter()`
292
+ 2. Data is fetched from the commerce platform API
293
+ 3. Results are automatically cached in the local Dexie database
294
+ 4. Cached data is returned to your application
295
+ 5. Subsequent calls return local cached data instantly
296
+
297
+ **Write Operations:**
298
+ 1. Data is saved immediately to the local database with a temporary offline ID
299
+ 2. Operation is queued for synchronization (added to sync_queue table)
300
+ 3. Local data is returned instantly for better user experience
301
+ 4. When connectivity returns, the sync engine processes the queue
302
+ 5. Temporary ID is replaced with the real connector ID
303
+ 6. Sync status is logged in the sync_logs table
304
+
305
+ ### Local Storage Structure
306
+
307
+ The SDK uses the following Dexie tables for offline functionality:
308
+
309
+ ```typescript
310
+ // Data tables (cached from connector)
311
+ - products
312
+ - orders
313
+ - customers
314
+ - variants
315
+ - categories
316
+ - collections
317
+ - inventory_levels
318
+ - promotions
319
+ - refunds
320
+ - payments
321
+ - payment_providers
322
+ - tax_regions
323
+ - tax_rates
324
+ - locations
325
+ - fulfillments
326
+ - order_notes
327
+
328
+ // Offline-specific tables
329
+ - sync_queue: Tracks pending create/update/delete operations
330
+ - sync_logs: Records sync status and timestamps
331
+ ```
332
+
333
+ ### Offline ID Management
334
+
335
+ When creating records locally, the SDK generates temporary offline IDs:
336
+
337
+ ```typescript
338
+ // Temporary ID format
339
+ stall_product_<uuid>_<timestamp>
340
+
341
+ // Example
342
+ const newProduct = await products.create({
343
+ sdk: stall,
344
+ data: { name: 'Offline Product', price: 29.99 }
345
+ });
346
+
347
+ // Returns immediately with temporary ID
348
+ // Product ID: stall_product_a1b2c3d4_1699564800000
349
+
350
+ // After sync completes, ID is replaced with real connector ID
351
+ // Product ID: prod_12345 (from WooCommerce, Shopify, etc.)
352
+ ```
353
+
354
+ ### Sync Queue Structure
355
+
356
+ Each queued operation tracks:
357
+
358
+ ```typescript
359
+ interface SyncQueueItem {
360
+ id: string; // Unique queue item ID
361
+ action: 'create' | 'update' | 'delete';
362
+ table: string; // Which table (products, orders, etc.)
363
+ document_id: string; // Local or connector ID
364
+ stall_offline_id: string; // Persistent offline reference
365
+ data: any; // Operation payload
366
+ status: 'pending' | 'syncing' | 'synced' | 'failed';
367
+ error?: string; // Error message if failed
368
+ created_at: string; // When queued
369
+ synced_at?: string; // When successfully synced
370
+ }
371
+ ```
372
+
373
+ ### Sync Logs
374
+
375
+ Each operation is logged for audit and debugging:
376
+
377
+ ```typescript
378
+ interface SyncLog {
379
+ id: string;
380
+ batch_id: string; // Groups related operations
381
+ action: string;
382
+ table: string;
383
+ document_id: string;
384
+ status: 'success' | 'failed' | 'pending';
385
+ message: string;
386
+ created_at: string;
387
+ completed_at?: string;
388
+ }
389
+ ```
390
+
267
391
  ## Adapter Architecture
268
392
 
269
393
  ### How It Works
@@ -291,6 +415,18 @@ const products3 = await products.list({ sdk: stall, query: 'hats' });
291
415
  await stall.refreshAdapter(); // Clears cache, fetches fresh module
292
416
  ```
293
417
 
418
+ ### Dependency-Aware Sync
419
+
420
+ The sync engine processes operations in dependency order to maintain data integrity:
421
+
422
+ **Layer 1 (Foundation):** Categories, Collections, Tax Regions
423
+ **Layer 2 (Products):** Products and their core properties
424
+ **Layer 3 (Variants):** Product Variants and Inventory
425
+ **Layer 4 (Orders):** Orders, Notes, and basic fulfillment
426
+ **Layer 5 (Payments):** Payment Providers, Payments, Refunds, Fulfillments
427
+
428
+ This ensures that referenced entities are created before dependent records.
429
+
294
430
  ## Error Handling
295
431
 
296
432
  All service methods are wrapped in try-catch blocks and propagate errors:
@@ -303,6 +439,118 @@ try {
303
439
  }
304
440
  ```
305
441
 
442
+ ## Offline Handling
443
+
444
+ The SDK handles offline scenarios gracefully by queuing operations locally and syncing when connectivity returns:
445
+
446
+ ### Write Operations While Offline
447
+
448
+ ```typescript
449
+ // This operation succeeds even without connectivity
450
+ const product = await products.create({
451
+ sdk: stall,
452
+ data: {
453
+ name: 'Offline Created Product',
454
+ price: 79.99
455
+ }
456
+ });
457
+
458
+ // Returns immediately with temporary offline ID
459
+ console.log(product.id); // stall_product_xyz_1699564800000
460
+
461
+ // Operation is queued and will sync automatically when online
462
+ ```
463
+
464
+ ### Monitoring Sync Queue
465
+
466
+ Check for pending operations before taking critical actions:
467
+
468
+ ```typescript
469
+ import { local_db } from '@use-stall/core/db/db';
470
+
471
+ // Get all pending sync operations
472
+ const pending = await local_db.sync_queue
473
+ .where('status')
474
+ .equals('pending')
475
+ .toArray();
476
+
477
+ console.log(`Pending operations: ${pending.length}`);
478
+
479
+ // Check for failed operations
480
+ const failed = await local_db.sync_queue
481
+ .where('status')
482
+ .equals('failed')
483
+ .toArray();
484
+
485
+ if (failed.length > 0) {
486
+ console.warn(`${failed.length} sync operations failed:`, failed);
487
+ }
488
+
489
+ // Check sync logs for history
490
+ const recentLogs = await local_db.sync_logs
491
+ .orderBy('created_at')
492
+ .reverse()
493
+ .limit(10)
494
+ .toArray();
495
+
496
+ console.log('Recent sync activity:', recentLogs);
497
+ ```
498
+
499
+ ### Handling Offline State in Your App
500
+
501
+ ```typescript
502
+ // Detect if there are pending operations
503
+ async function hasPendingSync() {
504
+ const pending = await local_db.sync_queue
505
+ .where('status')
506
+ .equals('pending')
507
+ .count();
508
+
509
+ return pending > 0;
510
+ }
511
+
512
+ // Show indicator to user
513
+ const hasSync = await hasPendingSync();
514
+ if (hasSync) {
515
+ showSyncIndicator('Syncing changes...');
516
+ }
517
+
518
+ // Disable certain actions while syncing
519
+ const isSyncing = await local_db.sync_queue
520
+ .where('status')
521
+ .equals('syncing')
522
+ .count();
523
+
524
+ if (isSyncing > 0) {
525
+ disableCheckout(); // Don't checkout while syncing
526
+ }
527
+ ```
528
+
529
+ ### Retrying Failed Operations
530
+
531
+ ```typescript
532
+ // Get failed operations
533
+ const failedOps = await local_db.sync_queue
534
+ .where('status')
535
+ .equals('failed')
536
+ .toArray();
537
+
538
+ // Manually retry a failed operation
539
+ for (const operation of failedOps) {
540
+ try {
541
+ // Update status to pending for retry
542
+ await local_db.sync_queue.update(operation.id, {
543
+ status: 'pending',
544
+ error: null
545
+ });
546
+
547
+ console.log(`Queued retry for ${operation.action} on ${operation.table}`);
548
+ } catch (error) {
549
+ console.error('Failed to queue retry:', error);
550
+ }
551
+ }
552
+ ```
553
+
306
554
  ## Type Safety
307
555
 
308
556
  All services use TypeScript types from `@use-stall/types`:
@@ -398,6 +646,55 @@ const items = await products.bulk_create({
398
646
 
399
647
  5. **Type your data**: Use TypeScript types for safety
400
648
 
649
+ 6. **Embrace offline-first**: Write operations return instantly with local data and sync automatically
650
+
651
+ ```typescript
652
+ // Create returns immediately with temporary ID
653
+ const product = await products.create({
654
+ sdk: stall,
655
+ data: { name: 'New Product', price: 49.99 }
656
+ });
657
+
658
+ // Use the product immediately in your UI
659
+ // It will sync in the background when connectivity returns
660
+ console.log(product.id); // stall_product_abc123_1699564800000
661
+ ```
662
+
663
+ 7. **Monitor sync status**: Check pending operations before critical actions
664
+
665
+ ```typescript
666
+ // Get pending sync operations (implementation depends on your sync service)
667
+ const pending = await local_db.sync_queue
668
+ .where('status')
669
+ .equals('pending')
670
+ .toArray();
671
+
672
+ if (pending.length > 0) {
673
+ console.log(`${pending.length} operations pending sync`);
674
+ }
675
+ ```
676
+
677
+ 8. **Batch related operations**: Group dependent operations for better sync efficiency
678
+
679
+ ```typescript
680
+ // Create product, then variants together
681
+ const product = await products.create({
682
+ sdk: stall,
683
+ data: { name: 'New Product' }
684
+ });
685
+
686
+ const variants = await variants.bulk_create({
687
+ sdk: stall,
688
+ data: [
689
+ { product_id: product.id, size: 'S' },
690
+ { product_id: product.id, size: 'M' },
691
+ { product_id: product.id, size: 'L' },
692
+ ]
693
+ });
694
+
695
+ // Sync engine will create product first, then variants
696
+ ```
697
+
401
698
  ```typescript
402
699
  import type { UnifiedProductType } from '@use-stall/types';
403
700