@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 +346 -49
- package/dist/index.d.mts +215 -20
- package/dist/index.d.ts +215 -20
- package/dist/index.js +2705 -668
- package/dist/index.mjs +2693 -668
- package/package.json +2 -2
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
|
-
│
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
│
|
|
30
|
-
|
|
31
|
-
│ - products
|
|
32
|
-
│ -
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
│
|
|
37
|
-
|
|
38
|
-
│
|
|
39
|
-
│
|
|
40
|
-
│
|
|
41
|
-
│
|
|
42
|
-
│
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
│
|
|
47
|
-
│
|
|
48
|
-
│
|
|
49
|
-
│
|
|
50
|
-
│
|
|
51
|
-
│
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
│
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
│
|
|
60
|
-
│
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
│ (
|
|
66
|
-
│
|
|
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
|
|