@baasix/sdk 0.1.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1197 -0
  3. package/dist/client-DeXa-R9w.d.ts +680 -0
  4. package/dist/client-VT7NckyI.d.cts +680 -0
  5. package/dist/index.cjs +4567 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +1788 -0
  8. package/dist/index.d.ts +1788 -0
  9. package/dist/index.js +4543 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/modules/auth.cjs +650 -0
  12. package/dist/modules/auth.cjs.map +1 -0
  13. package/dist/modules/auth.d.cts +384 -0
  14. package/dist/modules/auth.d.ts +384 -0
  15. package/dist/modules/auth.js +648 -0
  16. package/dist/modules/auth.js.map +1 -0
  17. package/dist/modules/files.cjs +269 -0
  18. package/dist/modules/files.cjs.map +1 -0
  19. package/dist/modules/files.d.cts +187 -0
  20. package/dist/modules/files.d.ts +187 -0
  21. package/dist/modules/files.js +267 -0
  22. package/dist/modules/files.js.map +1 -0
  23. package/dist/modules/items.cjs +640 -0
  24. package/dist/modules/items.cjs.map +1 -0
  25. package/dist/modules/items.d.cts +465 -0
  26. package/dist/modules/items.d.ts +465 -0
  27. package/dist/modules/items.js +637 -0
  28. package/dist/modules/items.js.map +1 -0
  29. package/dist/modules/schemas.cjs +322 -0
  30. package/dist/modules/schemas.cjs.map +1 -0
  31. package/dist/modules/schemas.d.cts +260 -0
  32. package/dist/modules/schemas.d.ts +260 -0
  33. package/dist/modules/schemas.js +320 -0
  34. package/dist/modules/schemas.js.map +1 -0
  35. package/dist/storage/index.cjs +162 -0
  36. package/dist/storage/index.cjs.map +1 -0
  37. package/dist/storage/index.d.cts +96 -0
  38. package/dist/storage/index.d.ts +96 -0
  39. package/dist/storage/index.js +157 -0
  40. package/dist/storage/index.js.map +1 -0
  41. package/dist/types-BdjsGANq.d.cts +40 -0
  42. package/dist/types-BdjsGANq.d.ts +40 -0
  43. package/package.json +108 -0
package/README.md ADDED
@@ -0,0 +1,1197 @@
1
+ # @baasix/sdk
2
+
3
+ Official JavaScript/TypeScript SDK for [Baasix](https://www.baasix.com) Backend-as-a-Service.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@baasix/sdk.svg)](https://www.npmjs.com/package/@baasix/sdk)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@baasix/sdk.svg)](https://www.npmjs.com/package/@baasix/sdk)
7
+ [![license](https://img.shields.io/npm/l/@baasix/sdk.svg)](https://github.com/baasix/baasix/blob/main/LICENSE)
8
+
9
+ ## Features
10
+
11
+ - 🌐 **Universal** - Works in browsers, Node.js, and React Native
12
+ - 🔐 **Flexible Auth** - JWT tokens, HTTP-only cookies, OAuth (Google, Facebook, Apple, GitHub)
13
+ - 💾 **Customizable Storage** - LocalStorage, AsyncStorage, or custom adapters
14
+ - 📝 **Type-Safe** - Full TypeScript support with generics
15
+ - 🔄 **Auto Token Refresh** - Seamless token management
16
+ - 🏢 **Multi-Tenant** - Built-in tenant switching and invitation support
17
+ - ⚡ **Query Builder** - Fluent API for complex queries with 50+ filter operators
18
+ - 📡 **Realtime** - WebSocket subscriptions for live data updates
19
+ - 📁 **File Management** - Upload, download, and transform assets
20
+ - 🔀 **Workflows** - Execute and monitor workflow executions
21
+ - 👥 **User & Role Management** - Admin operations for users and roles
22
+ - 📊 **Reports** - Generate reports with aggregations
23
+ - 🔔 **Notifications** - User notification system with realtime delivery
24
+ - 🗃️ **Migrations** - Database schema migration management
25
+ - 🔃 **Sort/Reorder** - Drag-and-drop style item reordering
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install @baasix/sdk
31
+ # or
32
+ yarn add @baasix/sdk
33
+ # or
34
+ pnpm add @baasix/sdk
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```typescript
40
+ import { createBaasix } from '@baasix/sdk';
41
+
42
+ // Create client
43
+ const baasix = createBaasix({
44
+ url: 'https://your-baasix-instance.com',
45
+ });
46
+
47
+ // Login
48
+ const { user, token } = await baasix.auth.login({
49
+ email: 'user@example.com',
50
+ password: 'password123',
51
+ });
52
+
53
+ // Query items
54
+ const { data: products } = await baasix.items('products').find({
55
+ filter: { status: { eq: 'active' } },
56
+ sort: { createdAt: 'desc' },
57
+ limit: 10,
58
+ });
59
+
60
+ // Create item
61
+ const productId = await baasix.items('products').create({
62
+ name: 'New Product',
63
+ price: 29.99,
64
+ });
65
+ ```
66
+
67
+ ## Configuration
68
+
69
+ ### Basic Configuration
70
+
71
+ ```typescript
72
+ import { createBaasix } from '@baasix/sdk';
73
+
74
+ const baasix = createBaasix({
75
+ url: 'https://api.example.com', // Required: Your Baasix URL
76
+ authMode: 'jwt', // 'jwt' (default) or 'cookie'
77
+ timeout: 30000, // Request timeout in ms (default: 30000)
78
+ autoRefresh: true, // Auto-refresh tokens (default: true)
79
+ onAuthStateChange: (event, user) => { // Auth state callback
80
+ console.log('Auth changed:', event, user);
81
+ },
82
+ });
83
+ ```
84
+
85
+ ### React Native Setup
86
+
87
+ ```typescript
88
+ import { createBaasix, AsyncStorageAdapter } from '@baasix/sdk';
89
+ import AsyncStorage from '@react-native-async-storage/async-storage';
90
+
91
+ const baasix = createBaasix({
92
+ url: 'https://api.example.com',
93
+ storage: new AsyncStorageAdapter(AsyncStorage),
94
+ });
95
+ ```
96
+
97
+ ### Cookie Mode (Web with HTTP-only cookies)
98
+
99
+ ```typescript
100
+ const baasix = createBaasix({
101
+ url: 'https://api.example.com',
102
+ authMode: 'cookie',
103
+ credentials: 'include', // Required for cookies
104
+ });
105
+ ```
106
+
107
+ ### Server-Side / Service Account
108
+
109
+ ```typescript
110
+ const baasix = createBaasix({
111
+ url: 'https://api.example.com',
112
+ token: 'your-service-account-token', // Static token
113
+ });
114
+ ```
115
+
116
+ ## Authentication
117
+
118
+ ### Register
119
+
120
+ ```typescript
121
+ const { user, token } = await baasix.auth.register({
122
+ email: 'newuser@example.com',
123
+ password: 'securepassword',
124
+ firstName: 'John',
125
+ lastName: 'Doe',
126
+ });
127
+ ```
128
+
129
+ ### Login
130
+
131
+ ```typescript
132
+ const { user, token } = await baasix.auth.login({
133
+ email: 'user@example.com',
134
+ password: 'password123',
135
+ });
136
+
137
+ // With tenant (multi-tenant mode)
138
+ const result = await baasix.auth.login({
139
+ email: 'user@example.com',
140
+ password: 'password123',
141
+ tenantId: 'tenant-uuid',
142
+ });
143
+ ```
144
+
145
+ ### Get Current User
146
+
147
+ ```typescript
148
+ // From server (makes API call)
149
+ const user = await baasix.auth.getUser();
150
+
151
+ // From cache (no API call)
152
+ const cachedUser = await baasix.auth.getCachedUser();
153
+ ```
154
+
155
+ ### Logout
156
+
157
+ ```typescript
158
+ await baasix.auth.logout();
159
+ ```
160
+
161
+ ### Check Authentication
162
+
163
+ ```typescript
164
+ if (await baasix.auth.isAuthenticated()) {
165
+ // User is logged in
166
+ }
167
+ ```
168
+
169
+ ### Magic Link Login
170
+
171
+ ```typescript
172
+ // Send magic link
173
+ await baasix.auth.sendMagicLink({
174
+ email: 'user@example.com',
175
+ redirectUrl: 'https://myapp.com/auth/callback',
176
+ });
177
+
178
+ // Verify (after user clicks link)
179
+ const { user, token } = await baasix.auth.verifyMagicLink('verification-token');
180
+ ```
181
+
182
+ ### Password Reset
183
+
184
+ ```typescript
185
+ // Request reset
186
+ await baasix.auth.forgotPassword({
187
+ email: 'user@example.com',
188
+ redirectUrl: 'https://myapp.com/reset-password',
189
+ });
190
+
191
+ // Reset password
192
+ await baasix.auth.resetPassword('reset-token', 'newpassword123');
193
+ ```
194
+
195
+ ### Multi-Tenant
196
+
197
+ ```typescript
198
+ // Get available tenants
199
+ const tenants = await baasix.auth.getTenants();
200
+
201
+ // Switch tenant
202
+ const { user, token } = await baasix.auth.switchTenant('tenant-uuid');
203
+ ```
204
+
205
+ ## Items (CRUD Operations)
206
+
207
+ ### Query Items
208
+
209
+ ```typescript
210
+ const items = baasix.items('products');
211
+
212
+ // Basic find
213
+ const { data, totalCount } = await items.find();
214
+
215
+ // With parameters
216
+ const { data: activeProducts } = await items.find({
217
+ filter: { status: { eq: 'active' } },
218
+ sort: { createdAt: 'desc' },
219
+ limit: 20,
220
+ page: 1,
221
+ fields: ['id', 'name', 'price', 'category.*'],
222
+ });
223
+
224
+ // Find one by ID
225
+ const product = await items.findOne('product-uuid');
226
+
227
+ // With related data
228
+ const product = await items.findOne('product-uuid', {
229
+ fields: ['*', 'category.*', 'reviews.*'],
230
+ });
231
+ ```
232
+
233
+ ### Query Builder
234
+
235
+ ```typescript
236
+ const results = await baasix.items('posts')
237
+ .query()
238
+ .select('*', 'author.*', 'comments.*')
239
+ .filter({
240
+ AND: [
241
+ { status: { eq: 'published' } },
242
+ { createdAt: { gte: '$NOW-DAYS_30' } },
243
+ ],
244
+ })
245
+ .sort({ createdAt: 'desc' })
246
+ .limit(10)
247
+ .page(1)
248
+ .get();
249
+
250
+ // First result only
251
+ const post = await baasix.items('posts')
252
+ .query()
253
+ .filter({ slug: { eq: 'my-post' } })
254
+ .first();
255
+
256
+ // Count
257
+ const count = await baasix.items('products')
258
+ .query()
259
+ .filter({ inStock: { eq: true } })
260
+ .count();
261
+ ```
262
+
263
+ ### Create Items
264
+
265
+ ```typescript
266
+ // Single item
267
+ const id = await baasix.items('products').create({
268
+ name: 'New Product',
269
+ price: 29.99,
270
+ status: 'draft',
271
+ });
272
+
273
+ // Multiple items
274
+ const ids = await baasix.items('products').createMany([
275
+ { name: 'Product 1', price: 10 },
276
+ { name: 'Product 2', price: 20 },
277
+ ]);
278
+ ```
279
+
280
+ ### Update Items
281
+
282
+ ```typescript
283
+ // Single item
284
+ await baasix.items('products').update('product-uuid', {
285
+ price: 24.99,
286
+ status: 'published',
287
+ });
288
+
289
+ // Multiple items
290
+ await baasix.items('products').updateMany(
291
+ ['id1', 'id2', 'id3'],
292
+ { status: 'archived' }
293
+ );
294
+
295
+ // Upsert (create or update)
296
+ const id = await baasix.items('products').upsert(
297
+ { sku: { eq: 'SKU-001' } },
298
+ { name: 'Widget', price: 29.99, sku: 'SKU-001' }
299
+ );
300
+ ```
301
+
302
+ ### Delete Items
303
+
304
+ ```typescript
305
+ // Single item
306
+ await baasix.items('products').delete('product-uuid');
307
+
308
+ // Multiple items
309
+ await baasix.items('products').deleteMany(['id1', 'id2', 'id3']);
310
+
311
+ // Soft delete (if paranoid mode enabled)
312
+ await baasix.items('products').softDelete('product-uuid');
313
+
314
+ // Restore soft-deleted
315
+ await baasix.items('products').restore('product-uuid');
316
+ ```
317
+
318
+ ### Aggregation
319
+
320
+ ```typescript
321
+ const results = await baasix.items('orders').aggregate({
322
+ aggregate: {
323
+ totalRevenue: { function: 'sum', field: 'total' },
324
+ orderCount: { function: 'count', field: 'id' },
325
+ avgOrderValue: { function: 'avg', field: 'total' },
326
+ },
327
+ groupBy: ['status', 'category'],
328
+ filter: { createdAt: { gte: '$NOW-DAYS_30' } },
329
+ });
330
+ ```
331
+
332
+ ## Filter Operators
333
+
334
+ Baasix supports 50+ filter operators:
335
+
336
+ ```typescript
337
+ // Comparison
338
+ { field: { eq: value } } // Equal
339
+ { field: { ne: value } } // Not equal
340
+ { field: { gt: value } } // Greater than
341
+ { field: { gte: value } } // Greater than or equal
342
+ { field: { lt: value } } // Less than
343
+ { field: { lte: value } } // Less than or equal
344
+
345
+ // Collection
346
+ { field: { in: [1, 2, 3] } } // In list
347
+ { field: { notIn: [1, 2, 3] } } // Not in list
348
+
349
+ // String
350
+ { field: { like: 'pattern' } } // LIKE (case-sensitive)
351
+ { field: { iLike: 'pattern' } } // ILIKE (case-insensitive)
352
+ { field: { startsWith: 'prefix' } } // Starts with
353
+ { field: { endsWith: 'suffix' } } // Ends with
354
+ { field: { contains: 'substring' } } // Contains
355
+
356
+ // Range
357
+ { field: { between: [10, 100] } } // Between
358
+
359
+ // Null
360
+ { field: { isNull: true } } // Is null
361
+ { field: { isNotNull: true } } // Is not null
362
+
363
+ // Array (PostgreSQL)
364
+ { tags: { arraycontains: ['js', 'api'] } }
365
+
366
+ // JSONB
367
+ { metadata: { jsonbHasKey: 'discount' } }
368
+ { metadata: { jsonbKeyEquals: { key: 'status', value: 'active' } } }
369
+
370
+ // Logical
371
+ { AND: [{ status: { eq: 'active' } }, { price: { gt: 0 } }] }
372
+ { OR: [{ status: { eq: 'featured' } }, { views: { gt: 1000 } }] }
373
+
374
+ // Relation filtering
375
+ { 'author.name': { like: 'John' } }
376
+
377
+ // Dynamic variables
378
+ { author_Id: { eq: '$CURRENT_USER' } }
379
+ { createdAt: { gte: '$NOW-DAYS_30' } }
380
+ ```
381
+
382
+ ## Files
383
+
384
+ ### Upload Files
385
+
386
+ ```typescript
387
+ // Browser
388
+ const fileMetadata = await baasix.files.upload(fileInput.files[0], {
389
+ title: 'Product Image',
390
+ folder: 'products',
391
+ isPublic: true,
392
+ onProgress: (progress) => console.log(`${progress}% uploaded`),
393
+ });
394
+
395
+ // React Native with expo-image-picker
396
+ const metadata = await baasix.files.upload({
397
+ uri: result.uri,
398
+ name: 'photo.jpg',
399
+ type: 'image/jpeg',
400
+ });
401
+ ```
402
+
403
+ ### Get Asset URLs
404
+
405
+ ```typescript
406
+ // Original file
407
+ const url = baasix.files.getAssetUrl('file-uuid');
408
+
409
+ // With transformations
410
+ const thumbnailUrl = baasix.files.getAssetUrl('file-uuid', {
411
+ width: 200,
412
+ height: 200,
413
+ fit: 'cover',
414
+ quality: 80,
415
+ format: 'webp',
416
+ });
417
+ ```
418
+
419
+ ### File Operations
420
+
421
+ ```typescript
422
+ // List files
423
+ const { data: files } = await baasix.files.find({
424
+ filter: { mimeType: { startsWith: 'image/' } },
425
+ });
426
+
427
+ // Get file info
428
+ const file = await baasix.files.findOne('file-uuid');
429
+
430
+ // Download file
431
+ const blob = await baasix.files.download('file-uuid');
432
+
433
+ // Delete file
434
+ await baasix.files.delete('file-uuid');
435
+ ```
436
+
437
+ ## Schemas
438
+
439
+ ### Create Collection
440
+
441
+ ```typescript
442
+ await baasix.schemas.create({
443
+ collectionName: 'products',
444
+ schema: {
445
+ name: 'Product',
446
+ timestamps: true,
447
+ paranoid: true, // Soft deletes
448
+ fields: {
449
+ id: {
450
+ type: 'UUID',
451
+ primaryKey: true,
452
+ defaultValue: { type: 'UUIDV4' },
453
+ },
454
+ sku: {
455
+ type: 'SUID',
456
+ unique: true,
457
+ defaultValue: { type: 'SUID' },
458
+ },
459
+ name: {
460
+ type: 'String',
461
+ allowNull: false,
462
+ values: { length: 255 },
463
+ validate: {
464
+ notEmpty: true,
465
+ len: [3, 255],
466
+ },
467
+ },
468
+ price: {
469
+ type: 'Decimal',
470
+ values: { precision: 10, scale: 2 },
471
+ defaultValue: 0,
472
+ validate: {
473
+ min: 0,
474
+ max: 999999.99,
475
+ },
476
+ },
477
+ quantity: {
478
+ type: 'Integer',
479
+ defaultValue: 0,
480
+ validate: {
481
+ isInt: true,
482
+ min: 0,
483
+ },
484
+ },
485
+ email: {
486
+ type: 'String',
487
+ validate: {
488
+ isEmail: true,
489
+ },
490
+ },
491
+ website: {
492
+ type: 'String',
493
+ validate: {
494
+ isUrl: true,
495
+ },
496
+ },
497
+ slug: {
498
+ type: 'String',
499
+ validate: {
500
+ matches: '^[a-z0-9-]+$',
501
+ },
502
+ },
503
+ tags: {
504
+ type: 'Array',
505
+ values: { type: 'String' },
506
+ defaultValue: [],
507
+ },
508
+ metadata: {
509
+ type: 'JSONB',
510
+ defaultValue: {},
511
+ },
512
+ status: {
513
+ type: 'String',
514
+ defaultValue: 'draft',
515
+ },
516
+ isActive: {
517
+ type: 'Boolean',
518
+ defaultValue: true,
519
+ },
520
+ sortOrder: {
521
+ type: 'Integer',
522
+ defaultValue: { type: 'AUTOINCREMENT' },
523
+ },
524
+ publishedAt: {
525
+ type: 'DateTime',
526
+ defaultValue: { type: 'NOW' },
527
+ },
528
+ },
529
+ },
530
+ });
531
+ ```
532
+
533
+ ### Validation Rules
534
+
535
+ | Rule | Type | Description |
536
+ |------|------|-------------|
537
+ | `min` | number | Minimum value for numeric fields |
538
+ | `max` | number | Maximum value for numeric fields |
539
+ | `isInt` | boolean | Validate as integer |
540
+ | `notEmpty` | boolean | String must not be empty |
541
+ | `isEmail` | boolean | Validate email format |
542
+ | `isUrl` | boolean | Validate URL format |
543
+ | `len` | [min, max] | String length range |
544
+ | `is` / `matches` | string | Pattern matching with regex |
545
+
546
+ ### Default Value Types
547
+
548
+ | Type | Description |
549
+ |------|-------------|
550
+ | `UUIDV4` | Random UUID v4 |
551
+ | `SUID` | Short unique ID (compact, URL-safe) |
552
+ | `NOW` | Current timestamp |
553
+ | `AUTOINCREMENT` | Auto-incrementing integer |
554
+ | `SQL` | Custom SQL expression |
555
+ | Static | Any constant value (`"active"`, `false`, `0`) |
556
+
557
+ ### Relationships
558
+
559
+ ```typescript
560
+ // Many-to-One (BelongsTo)
561
+ // Auto-creates index on foreign key column
562
+ await baasix.schemas.createRelationship('products', {
563
+ type: 'M2O',
564
+ target: 'categories',
565
+ name: 'category',
566
+ alias: 'products',
567
+ });
568
+
569
+ // Many-to-Many
570
+ // Auto-generates junction table: products_tags_tags_junction
571
+ await baasix.schemas.createRelationship('products', {
572
+ type: 'M2M',
573
+ target: 'tags',
574
+ name: 'tags',
575
+ alias: 'products',
576
+ });
577
+
578
+ // Many-to-Many with custom junction table name
579
+ // Useful when auto-generated name exceeds PostgreSQL's 63 char limit
580
+ await baasix.schemas.createRelationship('products', {
581
+ type: 'M2M',
582
+ target: 'tags',
583
+ name: 'tags',
584
+ alias: 'products',
585
+ through: 'product_tags', // Custom junction table name (max 63 chars)
586
+ });
587
+
588
+ // Many-to-Any (Polymorphic)
589
+ await baasix.schemas.createRelationship('comments', {
590
+ type: 'M2A',
591
+ name: 'commentable',
592
+ tables: ['posts', 'products'],
593
+ alias: 'comments',
594
+ through: 'comment_refs', // Optional custom junction table name
595
+ });
596
+ ```
597
+
598
+ #### Junction Tables (M2M/M2A)
599
+ - **Auto-generated name**: `{source}_{target}_{name}_junction`
600
+ - **Custom name**: Use `through` property (max 63 characters for PostgreSQL)
601
+ - **Schema property**: Junction tables have `isJunction: true` in their schema
602
+ - **Auto-indexed**: Foreign key columns are automatically indexed
603
+
604
+ ### Indexes
605
+
606
+ ```typescript
607
+ await baasix.schemas.createIndex('products', {
608
+ name: 'idx_products_sku',
609
+ fields: ['sku'],
610
+ unique: true,
611
+ });
612
+ ```
613
+
614
+ ## Reports & Analytics
615
+
616
+ ### Generate Report (POST)
617
+
618
+ Use `generate()` to create a report with a POST request, sending the query in the request body:
619
+
620
+ ```typescript
621
+ const report = await baasix.reports.generate('orders', {
622
+ aggregate: {
623
+ revenue: { function: 'sum', field: 'total' },
624
+ orders: { function: 'count', field: 'id' },
625
+ },
626
+ groupBy: ['category'],
627
+ filter: { status: { eq: 'completed' } },
628
+ dateRange: {
629
+ start: '2025-01-01',
630
+ end: '2025-12-31',
631
+ },
632
+ });
633
+ ```
634
+
635
+ ### Query Report (GET)
636
+
637
+ Use `query()` to fetch a report with a GET request, sending parameters as query strings:
638
+
639
+ ```typescript
640
+ const report = await baasix.reports.query('orders', {
641
+ aggregate: {
642
+ total: { function: 'sum', field: 'amount' },
643
+ },
644
+ groupBy: ['status'],
645
+ filter: { createdAt: { gte: '$NOW-DAYS_30' } },
646
+ });
647
+ ```
648
+
649
+ ### Multi-Collection Stats
650
+
651
+ Get statistics for multiple collections in a single request:
652
+
653
+ ```typescript
654
+ const stats = await baasix.reports.getStats([
655
+ {
656
+ name: 'total_products',
657
+ collection: 'products',
658
+ query: {
659
+ aggregate: { count: { function: 'count', field: '*' } },
660
+ },
661
+ },
662
+ {
663
+ name: 'total_orders',
664
+ collection: 'orders',
665
+ query: {
666
+ aggregate: {
667
+ count: { function: 'count', field: '*' },
668
+ total_amount: { function: 'sum', field: 'amount' },
669
+ },
670
+ },
671
+ },
672
+ {
673
+ name: 'products_by_category',
674
+ collection: 'products',
675
+ query: {
676
+ groupBy: ['categoryId'],
677
+ aggregate: {
678
+ count: { function: 'count', field: 'id' },
679
+ avg_price: { function: 'avg', field: 'price' },
680
+ },
681
+ fields: ['categoryId', 'category.name'],
682
+ },
683
+ },
684
+ ]);
685
+ // Returns: [{ name: 'total_products', collection: 'products', data: [...] }, ...]
686
+ ```
687
+
688
+ ### Aggregation Query
689
+
690
+ Run aggregation queries directly on a collection (uses items endpoint):
691
+
692
+ ```typescript
693
+ const results = await baasix.reports.aggregate('orders', {
694
+ aggregate: {
695
+ total: { function: 'sum', field: 'amount' },
696
+ count: { function: 'count', field: 'id' },
697
+ min: { function: 'min', field: 'amount' },
698
+ max: { function: 'max', field: 'amount' },
699
+ avg: { function: 'avg', field: 'amount' },
700
+ },
701
+ groupBy: ['status', 'paymentMethod'],
702
+ filter: { createdAt: { gte: '$NOW-DAYS_30' } },
703
+ });
704
+ ```
705
+
706
+ ### Quick Count
707
+
708
+ ```typescript
709
+ const activeUsers = await baasix.reports.count('users', {
710
+ status: { eq: 'active' },
711
+ });
712
+ ```
713
+
714
+ ### Distinct Values
715
+
716
+ ```typescript
717
+ const categories = await baasix.reports.distinct('products', 'category');
718
+ // Returns: ['Electronics', 'Clothing', 'Books', ...]
719
+ ```
720
+
721
+ ## Workflows
722
+
723
+ ```typescript
724
+ // Execute workflow
725
+ const result = await baasix.workflows.execute('workflow-uuid', {
726
+ orderId: 'order-123',
727
+ });
728
+
729
+ // Get execution history
730
+ const { data: executions } = await baasix.workflows.getExecutions('workflow-uuid');
731
+
732
+ // Subscribe to execution updates (requires realtime)
733
+ const unsubscribe = baasix.realtime.subscribeToExecution(executionId, (update) => {
734
+ console.log('Execution progress:', update.progress, '%');
735
+ if (update.status === 'complete') {
736
+ console.log('Workflow finished!', update.result);
737
+ }
738
+ });
739
+ ```
740
+
741
+ ## Realtime Subscriptions
742
+
743
+ The SDK supports real-time data updates via WebSocket connections.
744
+
745
+ ### Setup
746
+
747
+ ```typescript
748
+ // Install socket.io-client separately
749
+ npm install socket.io-client
750
+
751
+ // Initialize realtime
752
+ import { io } from 'socket.io-client';
753
+
754
+ // Set the socket client
755
+ baasix.realtime.setSocketClient(io);
756
+
757
+ // Connect to realtime server
758
+ await baasix.realtime.connect();
759
+ ```
760
+
761
+ ### Subscribe to Collections
762
+
763
+ ```typescript
764
+ // Subscribe to all changes on a collection
765
+ const unsubscribe = baasix.realtime.subscribe('products', (payload) => {
766
+ console.log(`Product ${payload.action}:`, payload.data);
767
+ // payload.action: 'create' | 'update' | 'delete'
768
+ // payload.data: the created/updated/deleted item
769
+ // payload.timestamp: ISO timestamp
770
+ });
771
+
772
+ // Subscribe to specific events only
773
+ const unsubscribe = baasix.realtime.on('orders', 'create', (data) => {
774
+ console.log('New order received:', data);
775
+ });
776
+
777
+ // Unsubscribe when done
778
+ unsubscribe();
779
+ ```
780
+
781
+ ### Supabase-style Channel API
782
+
783
+ ```typescript
784
+ const channel = baasix.realtime
785
+ .channel('products')
786
+ .on('INSERT', (payload) => console.log('New:', payload))
787
+ .on('UPDATE', (payload) => console.log('Updated:', payload))
788
+ .on('DELETE', (payload) => console.log('Deleted:', payload))
789
+ .subscribe();
790
+
791
+ // Later
792
+ channel.unsubscribe();
793
+ ```
794
+
795
+ ### Connection Management
796
+
797
+ ```typescript
798
+ // Check connection status
799
+ if (baasix.realtime.isConnected) {
800
+ console.log('Connected to realtime server');
801
+ }
802
+
803
+ // Listen for connection changes
804
+ baasix.realtime.onConnectionChange((connected) => {
805
+ console.log('Realtime:', connected ? 'online' : 'offline');
806
+ });
807
+
808
+ // Disconnect
809
+ baasix.realtime.disconnect();
810
+ ```
811
+
812
+ ## OAuth / Social Login
813
+
814
+ ```typescript
815
+ // Redirect to OAuth provider
816
+ const url = baasix.auth.getOAuthUrl({
817
+ provider: 'google', // 'google' | 'facebook' | 'apple' | 'github'
818
+ redirectUrl: 'https://myapp.com/auth/callback',
819
+ });
820
+ window.location.href = url;
821
+
822
+ // In your callback page
823
+ const params = new URLSearchParams(window.location.search);
824
+ const token = params.get('token');
825
+
826
+ if (token) {
827
+ const { user } = await baasix.auth.handleOAuthCallback(token);
828
+ console.log('Logged in as:', user.email);
829
+ }
830
+ ```
831
+
832
+ ## Invitation System (Multi-tenant)
833
+
834
+ ```typescript
835
+ // Send invitation
836
+ await baasix.auth.sendInvite({
837
+ email: 'newuser@example.com',
838
+ roleId: 'editor-role-uuid',
839
+ tenantId: 'tenant-uuid',
840
+ redirectUrl: 'https://myapp.com/accept-invite',
841
+ });
842
+
843
+ // Verify invitation token (in callback page)
844
+ const result = await baasix.auth.verifyInvite(token);
845
+ if (result.valid) {
846
+ // Show registration form with pre-filled email
847
+ console.log('Invite for:', result.email);
848
+ }
849
+
850
+ // Register with invitation
851
+ const { user } = await baasix.auth.registerWithInvite({
852
+ email: 'newuser@example.com',
853
+ password: 'password123',
854
+ firstName: 'John',
855
+ lastName: 'Doe',
856
+ inviteToken: token,
857
+ });
858
+ ```
859
+
860
+ ## Users Management (Admin)
861
+
862
+ ```typescript
863
+ // List users
864
+ const { data: users } = await baasix.users.find({
865
+ filter: { status: { eq: 'active' } },
866
+ limit: 20,
867
+ });
868
+
869
+ // Create user
870
+ const userId = await baasix.users.create({
871
+ email: 'user@example.com',
872
+ password: 'password123',
873
+ firstName: 'John',
874
+ role_Id: 'role-uuid',
875
+ });
876
+
877
+ // Update user
878
+ await baasix.users.update(userId, { firstName: 'Jane' });
879
+
880
+ // Admin password change
881
+ await baasix.users.changePassword(userId, 'newPassword123');
882
+
883
+ // Suspend/Activate user
884
+ await baasix.users.suspend(userId);
885
+ await baasix.users.activate(userId);
886
+ ```
887
+
888
+ ## Roles Management
889
+
890
+ ```typescript
891
+ // List roles
892
+ const { data: roles } = await baasix.roles.find();
893
+
894
+ // Find by name
895
+ const adminRole = await baasix.roles.findByName('Administrator');
896
+
897
+ // Create role
898
+ const roleId = await baasix.roles.create({
899
+ name: 'Editor',
900
+ description: 'Content editors',
901
+ appAccess: true,
902
+ });
903
+
904
+ // Update role
905
+ await baasix.roles.update(roleId, { description: 'Updated description' });
906
+ ```
907
+
908
+ ## Bulk Operations
909
+
910
+ ```typescript
911
+ // Bulk create
912
+ const ids = await baasix.items('products').createMany([
913
+ { name: 'Product 1', price: 29.99 },
914
+ { name: 'Product 2', price: 39.99 },
915
+ ]);
916
+
917
+ // Bulk update - apply same data to multiple items
918
+ await baasix.items('products').updateMany(
919
+ ['uuid-1', 'uuid-2', 'uuid-3'],
920
+ { status: 'archived' }
921
+ );
922
+
923
+ // Bulk delete
924
+ await baasix.items('products').deleteMany(['uuid-1', 'uuid-2']);
925
+ ```
926
+
927
+ ## CSV/JSON Import
928
+
929
+ ```typescript
930
+ // Import from CSV file
931
+ const result = await baasix.items('products').importCSV(csvFile);
932
+ console.log(`Imported: ${result.imported}, Failed: ${result.failed}`);
933
+
934
+ // Import from JSON file
935
+ const result = await baasix.items('products').importJSON(jsonFile);
936
+ ```
937
+
938
+ ## Sort / Reorder Items
939
+
940
+ ```typescript
941
+ // Move item1 before item2
942
+ await baasix.items('products').sortItem('item1-uuid', 'item2-uuid');
943
+
944
+ // Move item1 after item2
945
+ await baasix.items('products').sortItem('item1-uuid', 'item2-uuid', 'after');
946
+
947
+ // Reorder multiple items (set explicit order)
948
+ await baasix.items('products').reorder([
949
+ 'item3-uuid',
950
+ 'item1-uuid',
951
+ 'item2-uuid'
952
+ ]);
953
+ ```
954
+
955
+ ## Migrations (Admin)
956
+
957
+ ```typescript
958
+ // Check migration status
959
+ const status = await baasix.migrations.status();
960
+ console.log(`Pending: ${status.pendingCount}`);
961
+
962
+ // Get pending migrations
963
+ const pending = await baasix.migrations.pending();
964
+
965
+ // Run pending migrations
966
+ const result = await baasix.migrations.run();
967
+ console.log(`Completed: ${result.summary.completed}`);
968
+
969
+ // Run with options
970
+ const result = await baasix.migrations.run({
971
+ step: 1, // Run only 1 migration
972
+ dryRun: true, // Preview without executing
973
+ });
974
+
975
+ // Rollback a specific migration
976
+ await baasix.migrations.rollback('20231201000000');
977
+
978
+ // Rollback last batch
979
+ await baasix.migrations.rollbackBatch();
980
+
981
+ // Create new migration file
982
+ const { filepath } = await baasix.migrations.create('add_status_column', {
983
+ type: 'schema',
984
+ description: 'Add status column to orders',
985
+ });
986
+
987
+ // Mark migrations as completed (without running)
988
+ await baasix.migrations.markCompleted('20231201000000');
989
+ await baasix.migrations.markAllCompleted();
990
+ ```
991
+
992
+ ## Notifications
993
+
994
+ ```typescript
995
+ // Get user notifications
996
+ const { data } = await baasix.notifications.find({
997
+ limit: 20,
998
+ filter: { seen: { eq: false } },
999
+ });
1000
+
1001
+ // Get unread count
1002
+ const count = await baasix.notifications.getUnreadCount();
1003
+
1004
+ // Mark notifications as seen
1005
+ await baasix.notifications.markAsSeen(['id1', 'id2']);
1006
+ // Or mark all as seen
1007
+ await baasix.notifications.markAsSeen();
1008
+
1009
+ // Delete notifications
1010
+ await baasix.notifications.delete(['id1', 'id2']);
1011
+
1012
+ // Send notification (admin only)
1013
+ await baasix.notifications.send({
1014
+ type: 'alert',
1015
+ title: 'System Update',
1016
+ message: 'Maintenance scheduled for tonight',
1017
+ userIds: ['user1-uuid', 'user2-uuid'],
1018
+ });
1019
+
1020
+ // Cleanup old notifications (admin only)
1021
+ await baasix.notifications.cleanup(30); // older than 30 days
1022
+ ```
1023
+
1024
+ ## Custom Storage Adapter
1025
+
1026
+ Create a custom storage adapter for any environment:
1027
+
1028
+ ```typescript
1029
+ import { StorageAdapter } from '@baasix/sdk';
1030
+
1031
+ class MyCustomStorage implements StorageAdapter {
1032
+ async get(key: string): Promise<string | null> {
1033
+ // Your implementation
1034
+ }
1035
+
1036
+ async set(key: string, value: string): Promise<void> {
1037
+ // Your implementation
1038
+ }
1039
+
1040
+ async remove(key: string): Promise<void> {
1041
+ // Your implementation
1042
+ }
1043
+
1044
+ async clear(): Promise<void> {
1045
+ // Your implementation
1046
+ }
1047
+ }
1048
+
1049
+ const baasix = createBaasix({
1050
+ url: 'https://api.example.com',
1051
+ storage: new MyCustomStorage(),
1052
+ });
1053
+ ```
1054
+
1055
+ ## Error Handling
1056
+
1057
+ ```typescript
1058
+ import { BaasixError } from '@baasix/sdk';
1059
+
1060
+ try {
1061
+ await baasix.items('products').create({ name: 'Product' });
1062
+ } catch (error) {
1063
+ if (error instanceof BaasixError) {
1064
+ console.error('Status:', error.status);
1065
+ console.error('Code:', error.code);
1066
+ console.error('Message:', error.message);
1067
+ console.error('Details:', error.details);
1068
+
1069
+ if (error.status === 401) {
1070
+ // Handle unauthorized
1071
+ }
1072
+ if (error.status === 403) {
1073
+ // Handle forbidden
1074
+ }
1075
+ if (error.isRetryable) {
1076
+ // Can retry request
1077
+ }
1078
+ }
1079
+ }
1080
+ ```
1081
+
1082
+ ## TypeScript Support
1083
+
1084
+ Use generics for type-safe operations:
1085
+
1086
+ ```typescript
1087
+ interface Product {
1088
+ id: string;
1089
+ name: string;
1090
+ price: number;
1091
+ category_Id: string;
1092
+ createdAt: string;
1093
+ }
1094
+
1095
+ // Typed items module
1096
+ const products = baasix.items<Product>('products');
1097
+
1098
+ // Type inference
1099
+ const { data } = await products.find();
1100
+ // data is Product[]
1101
+
1102
+ const product = await products.findOne('uuid');
1103
+ // product is Product
1104
+
1105
+ await products.create({
1106
+ name: 'Widget',
1107
+ price: 29.99,
1108
+ category_Id: 'cat-uuid',
1109
+ }); // Type-checked
1110
+ ```
1111
+
1112
+ ## React Example
1113
+
1114
+ ```tsx
1115
+ import { createBaasix } from '@baasix/sdk';
1116
+ import { useEffect, useState } from 'react';
1117
+
1118
+ const baasix = createBaasix({
1119
+ url: process.env.REACT_APP_BAASIX_URL!,
1120
+ onAuthStateChange: (event, user) => {
1121
+ console.log('Auth:', event, user);
1122
+ },
1123
+ });
1124
+
1125
+ function App() {
1126
+ const [user, setUser] = useState(null);
1127
+ const [products, setProducts] = useState([]);
1128
+
1129
+ useEffect(() => {
1130
+ // Initialize auth on mount
1131
+ baasix.auth.initialize().then((state) => {
1132
+ setUser(state.user);
1133
+ });
1134
+ }, []);
1135
+
1136
+ useEffect(() => {
1137
+ // Fetch products
1138
+ baasix.items('products')
1139
+ .find({ filter: { status: { eq: 'active' } } })
1140
+ .then(({ data }) => setProducts(data));
1141
+ }, []);
1142
+
1143
+ const handleLogin = async (email, password) => {
1144
+ const { user } = await baasix.auth.login({ email, password });
1145
+ setUser(user);
1146
+ };
1147
+
1148
+ const handleLogout = async () => {
1149
+ await baasix.auth.logout();
1150
+ setUser(null);
1151
+ };
1152
+
1153
+ return (
1154
+ <div>
1155
+ {user ? (
1156
+ <>
1157
+ <p>Welcome, {user.email}</p>
1158
+ <button onClick={handleLogout}>Logout</button>
1159
+ </>
1160
+ ) : (
1161
+ <LoginForm onSubmit={handleLogin} />
1162
+ )}
1163
+ <ProductList products={products} />
1164
+ </div>
1165
+ );
1166
+ }
1167
+ ```
1168
+
1169
+ ## API Reference
1170
+
1171
+ ### `createBaasix(config)`
1172
+
1173
+ Creates a new Baasix SDK instance.
1174
+
1175
+ | Option | Type | Default | Description |
1176
+ |--------|------|---------|-------------|
1177
+ | `url` | `string` | Required | Your Baasix server URL |
1178
+ | `authMode` | `'jwt' \| 'cookie'` | `'jwt'` | Authentication mode |
1179
+ | `storage` | `StorageAdapter` | Auto-detected | Token storage adapter |
1180
+ | `token` | `string` | - | Static auth token |
1181
+ | `timeout` | `number` | `30000` | Request timeout (ms) |
1182
+ | `autoRefresh` | `boolean` | `true` | Auto-refresh tokens |
1183
+ | `headers` | `object` | `{}` | Custom headers |
1184
+ | `tenantId` | `string` | - | Multi-tenant ID |
1185
+ | `credentials` | `RequestCredentials` | Based on authMode | Fetch credentials |
1186
+ | `onAuthStateChange` | `function` | - | Auth state callback |
1187
+ | `onError` | `function` | - | Global error handler |
1188
+
1189
+ ### Storage Adapters
1190
+
1191
+ - `LocalStorageAdapter` - Browser localStorage (default for web)
1192
+ - `MemoryStorageAdapter` - In-memory (default for SSR/Node.js)
1193
+ - `AsyncStorageAdapter` - React Native AsyncStorage
1194
+
1195
+ ## License
1196
+
1197
+ MIT © [Vivek Palanisamy](https://www.baasix.com)