@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.
- package/LICENSE +21 -0
- package/README.md +1197 -0
- package/dist/client-DeXa-R9w.d.ts +680 -0
- package/dist/client-VT7NckyI.d.cts +680 -0
- package/dist/index.cjs +4567 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1788 -0
- package/dist/index.d.ts +1788 -0
- package/dist/index.js +4543 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/auth.cjs +650 -0
- package/dist/modules/auth.cjs.map +1 -0
- package/dist/modules/auth.d.cts +384 -0
- package/dist/modules/auth.d.ts +384 -0
- package/dist/modules/auth.js +648 -0
- package/dist/modules/auth.js.map +1 -0
- package/dist/modules/files.cjs +269 -0
- package/dist/modules/files.cjs.map +1 -0
- package/dist/modules/files.d.cts +187 -0
- package/dist/modules/files.d.ts +187 -0
- package/dist/modules/files.js +267 -0
- package/dist/modules/files.js.map +1 -0
- package/dist/modules/items.cjs +640 -0
- package/dist/modules/items.cjs.map +1 -0
- package/dist/modules/items.d.cts +465 -0
- package/dist/modules/items.d.ts +465 -0
- package/dist/modules/items.js +637 -0
- package/dist/modules/items.js.map +1 -0
- package/dist/modules/schemas.cjs +322 -0
- package/dist/modules/schemas.cjs.map +1 -0
- package/dist/modules/schemas.d.cts +260 -0
- package/dist/modules/schemas.d.ts +260 -0
- package/dist/modules/schemas.js +320 -0
- package/dist/modules/schemas.js.map +1 -0
- package/dist/storage/index.cjs +162 -0
- package/dist/storage/index.cjs.map +1 -0
- package/dist/storage/index.d.cts +96 -0
- package/dist/storage/index.d.ts +96 -0
- package/dist/storage/index.js +157 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types-BdjsGANq.d.cts +40 -0
- package/dist/types-BdjsGANq.d.ts +40 -0
- 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
|
+
[](https://www.npmjs.com/package/@baasix/sdk)
|
|
6
|
+
[](https://www.npmjs.com/package/@baasix/sdk)
|
|
7
|
+
[](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)
|