@tspvivek/baasix-sdk 0.1.0-alpha.1
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 +942 -0
- package/dist/client-CzF9B60b.d.ts +614 -0
- package/dist/client-aXK_gEyr.d.cts +614 -0
- package/dist/index.cjs +4159 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1498 -0
- package/dist/index.d.ts +1498 -0
- package/dist/index.js +4135 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/auth.cjs +651 -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 +649 -0
- package/dist/modules/auth.js.map +1 -0
- package/dist/modules/files.cjs +266 -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 +264 -0
- package/dist/modules/files.js.map +1 -0
- package/dist/modules/items.cjs +654 -0
- package/dist/modules/items.cjs.map +1 -0
- package/dist/modules/items.d.cts +472 -0
- package/dist/modules/items.d.ts +472 -0
- package/dist/modules/items.js +651 -0
- package/dist/modules/items.js.map +1 -0
- package/dist/modules/schemas.cjs +269 -0
- package/dist/modules/schemas.cjs.map +1 -0
- package/dist/modules/schemas.d.cts +239 -0
- package/dist/modules/schemas.d.ts +239 -0
- package/dist/modules/schemas.js +267 -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 +107 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4135 @@
|
|
|
1
|
+
// src/storage/types.ts
|
|
2
|
+
var STORAGE_KEYS = {
|
|
3
|
+
ACCESS_TOKEN: "baasix_access_token",
|
|
4
|
+
REFRESH_TOKEN: "baasix_refresh_token",
|
|
5
|
+
TOKEN_EXPIRY: "baasix_token_expiry",
|
|
6
|
+
USER: "baasix_user",
|
|
7
|
+
TENANT: "baasix_tenant"
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/types.ts
|
|
11
|
+
var BaasixError = class _BaasixError extends Error {
|
|
12
|
+
status;
|
|
13
|
+
code;
|
|
14
|
+
details;
|
|
15
|
+
isRetryable;
|
|
16
|
+
constructor(message, status = 500, code, details) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "BaasixError";
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.details = details;
|
|
22
|
+
this.isRetryable = status >= 500 || status === 429;
|
|
23
|
+
if (Error.captureStackTrace) {
|
|
24
|
+
Error.captureStackTrace(this, _BaasixError);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
toJSON() {
|
|
28
|
+
return {
|
|
29
|
+
name: this.name,
|
|
30
|
+
message: this.message,
|
|
31
|
+
status: this.status,
|
|
32
|
+
code: this.code,
|
|
33
|
+
details: this.details
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/client.ts
|
|
39
|
+
var HttpClient = class {
|
|
40
|
+
config;
|
|
41
|
+
refreshPromise = null;
|
|
42
|
+
constructor(config) {
|
|
43
|
+
this.config = config;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Update client configuration
|
|
47
|
+
*/
|
|
48
|
+
updateConfig(config) {
|
|
49
|
+
this.config = { ...this.config, ...config };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the current base URL
|
|
53
|
+
*/
|
|
54
|
+
getBaseUrl() {
|
|
55
|
+
return this.config.baseUrl;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the full URL with query parameters
|
|
59
|
+
*/
|
|
60
|
+
buildUrl(path, params) {
|
|
61
|
+
const url = new URL(path, this.config.baseUrl);
|
|
62
|
+
if (params) {
|
|
63
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
64
|
+
if (value !== void 0 && value !== null) {
|
|
65
|
+
if (typeof value === "object") {
|
|
66
|
+
url.searchParams.set(key, JSON.stringify(value));
|
|
67
|
+
} else {
|
|
68
|
+
url.searchParams.set(key, String(value));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return url.toString();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the current access token
|
|
77
|
+
*/
|
|
78
|
+
async getAccessToken() {
|
|
79
|
+
if (this.config.token) {
|
|
80
|
+
return this.config.token;
|
|
81
|
+
}
|
|
82
|
+
if (this.config.authMode === "cookie") {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const token = await this.config.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
|
|
86
|
+
return token;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if token is expired or about to expire (within 60 seconds)
|
|
90
|
+
*/
|
|
91
|
+
async isTokenExpired() {
|
|
92
|
+
const expiry = await this.config.storage.get(STORAGE_KEYS.TOKEN_EXPIRY);
|
|
93
|
+
if (!expiry) return false;
|
|
94
|
+
const expiryTime = parseInt(expiry, 10);
|
|
95
|
+
const bufferTime = 60 * 1e3;
|
|
96
|
+
return Date.now() >= expiryTime - bufferTime;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Refresh the access token
|
|
100
|
+
*/
|
|
101
|
+
async refreshToken() {
|
|
102
|
+
if (this.refreshPromise) {
|
|
103
|
+
return this.refreshPromise;
|
|
104
|
+
}
|
|
105
|
+
this.refreshPromise = (async () => {
|
|
106
|
+
try {
|
|
107
|
+
const refreshToken = await this.config.storage.get(
|
|
108
|
+
STORAGE_KEYS.REFRESH_TOKEN
|
|
109
|
+
);
|
|
110
|
+
if (!refreshToken && this.config.authMode === "jwt") {
|
|
111
|
+
throw new BaasixError("No refresh token available", 401, "NO_REFRESH_TOKEN");
|
|
112
|
+
}
|
|
113
|
+
const response = await fetch(
|
|
114
|
+
this.buildUrl("/auth/refresh"),
|
|
115
|
+
{
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
...this.config.headers
|
|
120
|
+
},
|
|
121
|
+
body: this.config.authMode === "jwt" ? JSON.stringify({ refreshToken }) : void 0,
|
|
122
|
+
credentials: this.config.credentials
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new BaasixError("Token refresh failed", response.status, "REFRESH_FAILED");
|
|
127
|
+
}
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
const tokens = {
|
|
130
|
+
accessToken: data.token,
|
|
131
|
+
refreshToken: data.refreshToken,
|
|
132
|
+
expiresIn: data.expiresIn,
|
|
133
|
+
expiresAt: data.expiresIn ? Date.now() + data.expiresIn * 1e3 : void 0
|
|
134
|
+
};
|
|
135
|
+
await this.config.storage.set(STORAGE_KEYS.ACCESS_TOKEN, tokens.accessToken);
|
|
136
|
+
if (tokens.refreshToken) {
|
|
137
|
+
await this.config.storage.set(STORAGE_KEYS.REFRESH_TOKEN, tokens.refreshToken);
|
|
138
|
+
}
|
|
139
|
+
if (tokens.expiresAt) {
|
|
140
|
+
await this.config.storage.set(
|
|
141
|
+
STORAGE_KEYS.TOKEN_EXPIRY,
|
|
142
|
+
tokens.expiresAt.toString()
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
this.config.onTokenRefresh?.(tokens);
|
|
146
|
+
return tokens;
|
|
147
|
+
} finally {
|
|
148
|
+
this.refreshPromise = null;
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
return this.refreshPromise;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Build request headers
|
|
155
|
+
*/
|
|
156
|
+
async buildHeaders(options = {}) {
|
|
157
|
+
const headers = {
|
|
158
|
+
"Content-Type": "application/json",
|
|
159
|
+
...this.config.headers
|
|
160
|
+
};
|
|
161
|
+
if (this.config.tenantId) {
|
|
162
|
+
headers["X-Tenant-Id"] = this.config.tenantId;
|
|
163
|
+
}
|
|
164
|
+
if (options.skipAuth) {
|
|
165
|
+
return headers;
|
|
166
|
+
}
|
|
167
|
+
if (this.config.authMode === "jwt") {
|
|
168
|
+
if (this.config.autoRefresh && await this.isTokenExpired()) {
|
|
169
|
+
try {
|
|
170
|
+
await this.refreshToken();
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const token = await this.getAccessToken();
|
|
175
|
+
if (token) {
|
|
176
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return headers;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Parse error response
|
|
183
|
+
*/
|
|
184
|
+
async parseError(response) {
|
|
185
|
+
let errorData = {};
|
|
186
|
+
try {
|
|
187
|
+
errorData = await response.json();
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
const message = errorData.error?.message || errorData.message || response.statusText || "Request failed";
|
|
191
|
+
const code = errorData.error?.code;
|
|
192
|
+
const details = errorData.details;
|
|
193
|
+
return new BaasixError(message, response.status, code, details);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Make an HTTP request
|
|
197
|
+
*/
|
|
198
|
+
async request(method, path, options = {}) {
|
|
199
|
+
const { params, timeout, skipAuth, rawResponse, ...fetchOptions } = options;
|
|
200
|
+
const url = this.buildUrl(path, params);
|
|
201
|
+
const headers = await this.buildHeaders({ skipAuth });
|
|
202
|
+
const requestTimeout = timeout || this.config.timeout;
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
205
|
+
try {
|
|
206
|
+
const response = await fetch(url, {
|
|
207
|
+
method,
|
|
208
|
+
headers: {
|
|
209
|
+
...headers,
|
|
210
|
+
...fetchOptions.headers
|
|
211
|
+
},
|
|
212
|
+
credentials: this.config.credentials,
|
|
213
|
+
signal: controller.signal,
|
|
214
|
+
...fetchOptions
|
|
215
|
+
});
|
|
216
|
+
if (response.status === 401 && !skipAuth && this.config.autoRefresh) {
|
|
217
|
+
try {
|
|
218
|
+
await this.refreshToken();
|
|
219
|
+
const retryHeaders = await this.buildHeaders({ skipAuth: false });
|
|
220
|
+
const retryResponse = await fetch(url, {
|
|
221
|
+
method,
|
|
222
|
+
headers: {
|
|
223
|
+
...retryHeaders,
|
|
224
|
+
...fetchOptions.headers
|
|
225
|
+
},
|
|
226
|
+
credentials: this.config.credentials,
|
|
227
|
+
...fetchOptions
|
|
228
|
+
});
|
|
229
|
+
if (!retryResponse.ok) {
|
|
230
|
+
throw await this.parseError(retryResponse);
|
|
231
|
+
}
|
|
232
|
+
if (rawResponse) {
|
|
233
|
+
return retryResponse;
|
|
234
|
+
}
|
|
235
|
+
if (retryResponse.status === 204) {
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
return await retryResponse.json();
|
|
239
|
+
} catch (refreshError) {
|
|
240
|
+
this.config.onAuthError?.();
|
|
241
|
+
throw refreshError;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw await this.parseError(response);
|
|
246
|
+
}
|
|
247
|
+
if (rawResponse) {
|
|
248
|
+
return response;
|
|
249
|
+
}
|
|
250
|
+
if (response.status === 204) {
|
|
251
|
+
return {};
|
|
252
|
+
}
|
|
253
|
+
return await response.json();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error instanceof BaasixError) {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
if (error instanceof Error) {
|
|
259
|
+
if (error.name === "AbortError") {
|
|
260
|
+
throw new BaasixError("Request timeout", 408, "TIMEOUT");
|
|
261
|
+
}
|
|
262
|
+
throw new BaasixError(error.message, 0, "NETWORK_ERROR");
|
|
263
|
+
}
|
|
264
|
+
throw new BaasixError("Unknown error occurred", 500, "UNKNOWN");
|
|
265
|
+
} finally {
|
|
266
|
+
clearTimeout(timeoutId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* GET request
|
|
271
|
+
*/
|
|
272
|
+
get(path, options) {
|
|
273
|
+
return this.request("GET", path, options);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* POST request
|
|
277
|
+
*/
|
|
278
|
+
post(path, data, options) {
|
|
279
|
+
return this.request("POST", path, {
|
|
280
|
+
...options,
|
|
281
|
+
body: data ? JSON.stringify(data) : void 0
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* PATCH request
|
|
286
|
+
*/
|
|
287
|
+
patch(path, data, options) {
|
|
288
|
+
return this.request("PATCH", path, {
|
|
289
|
+
...options,
|
|
290
|
+
body: data ? JSON.stringify(data) : void 0
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* PUT request
|
|
295
|
+
*/
|
|
296
|
+
put(path, data, options) {
|
|
297
|
+
return this.request("PUT", path, {
|
|
298
|
+
...options,
|
|
299
|
+
body: data ? JSON.stringify(data) : void 0
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* DELETE request
|
|
304
|
+
*/
|
|
305
|
+
delete(path, options) {
|
|
306
|
+
return this.request("DELETE", path, options);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Upload file with multipart/form-data
|
|
310
|
+
*/
|
|
311
|
+
async upload(path, formData, options) {
|
|
312
|
+
const { params, timeout, skipAuth, onProgress } = options || {};
|
|
313
|
+
const url = this.buildUrl(path, params);
|
|
314
|
+
const headers = await this.buildHeaders({ skipAuth });
|
|
315
|
+
const requestTimeout = timeout || this.config.timeout;
|
|
316
|
+
delete headers["Content-Type"];
|
|
317
|
+
if (onProgress && typeof XMLHttpRequest !== "undefined") {
|
|
318
|
+
return new Promise((resolve, reject) => {
|
|
319
|
+
const xhr = new XMLHttpRequest();
|
|
320
|
+
xhr.open("POST", url);
|
|
321
|
+
Object.entries(headers).forEach(
|
|
322
|
+
([key, value]) => {
|
|
323
|
+
xhr.setRequestHeader(key, value);
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
xhr.withCredentials = this.config.credentials === "include";
|
|
327
|
+
xhr.upload.onprogress = (event) => {
|
|
328
|
+
if (event.lengthComputable) {
|
|
329
|
+
const progress = Math.round(event.loaded / event.total * 100);
|
|
330
|
+
onProgress(progress);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
xhr.onload = () => {
|
|
334
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
335
|
+
try {
|
|
336
|
+
resolve(JSON.parse(xhr.responseText));
|
|
337
|
+
} catch {
|
|
338
|
+
resolve({});
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
reject(
|
|
342
|
+
new BaasixError(
|
|
343
|
+
xhr.statusText || "Upload failed",
|
|
344
|
+
xhr.status,
|
|
345
|
+
"UPLOAD_ERROR"
|
|
346
|
+
)
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
xhr.onerror = () => {
|
|
351
|
+
reject(new BaasixError("Network error during upload", 0, "NETWORK_ERROR"));
|
|
352
|
+
};
|
|
353
|
+
xhr.ontimeout = () => {
|
|
354
|
+
reject(new BaasixError("Upload timeout", 408, "TIMEOUT"));
|
|
355
|
+
};
|
|
356
|
+
xhr.timeout = requestTimeout;
|
|
357
|
+
xhr.send(formData);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const controller = new AbortController();
|
|
361
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
362
|
+
try {
|
|
363
|
+
const response = await fetch(url, {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers,
|
|
366
|
+
body: formData,
|
|
367
|
+
credentials: this.config.credentials,
|
|
368
|
+
signal: controller.signal
|
|
369
|
+
});
|
|
370
|
+
if (!response.ok) {
|
|
371
|
+
throw await this.parseError(response);
|
|
372
|
+
}
|
|
373
|
+
return await response.json();
|
|
374
|
+
} finally {
|
|
375
|
+
clearTimeout(timeoutId);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/storage/localStorage.ts
|
|
381
|
+
var LocalStorageAdapter = class {
|
|
382
|
+
prefix;
|
|
383
|
+
constructor(prefix = "baasix_") {
|
|
384
|
+
this.prefix = prefix;
|
|
385
|
+
}
|
|
386
|
+
getKey(key) {
|
|
387
|
+
if (key.startsWith(this.prefix)) {
|
|
388
|
+
return key;
|
|
389
|
+
}
|
|
390
|
+
return `${this.prefix}${key}`;
|
|
391
|
+
}
|
|
392
|
+
get(key) {
|
|
393
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
return localStorage.getItem(this.getKey(key));
|
|
398
|
+
} catch {
|
|
399
|
+
console.warn(`[Baasix SDK] Failed to get item from localStorage: ${key}`);
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
set(key, value) {
|
|
404
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
localStorage.setItem(this.getKey(key), value);
|
|
409
|
+
} catch {
|
|
410
|
+
console.warn(`[Baasix SDK] Failed to set item in localStorage: ${key}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
remove(key) {
|
|
414
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
localStorage.removeItem(this.getKey(key));
|
|
419
|
+
} catch {
|
|
420
|
+
console.warn(`[Baasix SDK] Failed to remove item from localStorage: ${key}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
clear() {
|
|
424
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const keysToRemove = [];
|
|
429
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
430
|
+
const key = localStorage.key(i);
|
|
431
|
+
if (key?.startsWith(this.prefix)) {
|
|
432
|
+
keysToRemove.push(key);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
436
|
+
} catch {
|
|
437
|
+
console.warn("[Baasix SDK] Failed to clear localStorage");
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// src/storage/memoryStorage.ts
|
|
443
|
+
var MemoryStorageAdapter = class {
|
|
444
|
+
store;
|
|
445
|
+
constructor() {
|
|
446
|
+
this.store = /* @__PURE__ */ new Map();
|
|
447
|
+
}
|
|
448
|
+
get(key) {
|
|
449
|
+
return this.store.get(key) ?? null;
|
|
450
|
+
}
|
|
451
|
+
set(key, value) {
|
|
452
|
+
this.store.set(key, value);
|
|
453
|
+
}
|
|
454
|
+
remove(key) {
|
|
455
|
+
this.store.delete(key);
|
|
456
|
+
}
|
|
457
|
+
clear() {
|
|
458
|
+
this.store.clear();
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Get all stored keys (useful for debugging)
|
|
462
|
+
*/
|
|
463
|
+
keys() {
|
|
464
|
+
return Array.from(this.store.keys());
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Get the number of stored items
|
|
468
|
+
*/
|
|
469
|
+
size() {
|
|
470
|
+
return this.store.size;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// src/modules/auth.ts
|
|
475
|
+
var AuthModule = class {
|
|
476
|
+
client;
|
|
477
|
+
storage;
|
|
478
|
+
authMode;
|
|
479
|
+
onAuthStateChange;
|
|
480
|
+
currentUser = null;
|
|
481
|
+
constructor(config) {
|
|
482
|
+
this.client = config.client;
|
|
483
|
+
this.storage = config.storage;
|
|
484
|
+
this.authMode = config.authMode;
|
|
485
|
+
this.onAuthStateChange = config.onAuthStateChange;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Emit an authentication state change event
|
|
489
|
+
*/
|
|
490
|
+
emitAuthStateChange(event, user) {
|
|
491
|
+
this.currentUser = user;
|
|
492
|
+
this.onAuthStateChange?.(event, user);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Store authentication tokens
|
|
496
|
+
*/
|
|
497
|
+
async storeTokens(response) {
|
|
498
|
+
if (this.authMode === "jwt") {
|
|
499
|
+
await this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, response.token);
|
|
500
|
+
if (response.refreshToken) {
|
|
501
|
+
await this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, response.refreshToken);
|
|
502
|
+
}
|
|
503
|
+
if (response.expiresIn) {
|
|
504
|
+
const expiresAt = Date.now() + response.expiresIn * 1e3;
|
|
505
|
+
await this.storage.set(STORAGE_KEYS.TOKEN_EXPIRY, expiresAt.toString());
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (response.user) {
|
|
509
|
+
await this.storage.set(STORAGE_KEYS.USER, JSON.stringify(response.user));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Clear stored authentication data
|
|
514
|
+
*/
|
|
515
|
+
async clearAuth() {
|
|
516
|
+
await this.storage.remove(STORAGE_KEYS.ACCESS_TOKEN);
|
|
517
|
+
await this.storage.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
518
|
+
await this.storage.remove(STORAGE_KEYS.TOKEN_EXPIRY);
|
|
519
|
+
await this.storage.remove(STORAGE_KEYS.USER);
|
|
520
|
+
await this.storage.remove(STORAGE_KEYS.TENANT);
|
|
521
|
+
this.currentUser = null;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Register a new user
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```typescript
|
|
528
|
+
* const { user, token } = await baasix.auth.register({
|
|
529
|
+
* email: 'newuser@example.com',
|
|
530
|
+
* password: 'securepassword',
|
|
531
|
+
* firstName: 'John',
|
|
532
|
+
* lastName: 'Doe'
|
|
533
|
+
* });
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
async register(data) {
|
|
537
|
+
const response = await this.client.post("/auth/register", data, {
|
|
538
|
+
skipAuth: true
|
|
539
|
+
});
|
|
540
|
+
await this.storeTokens(response);
|
|
541
|
+
this.emitAuthStateChange("SIGNED_IN", response.user);
|
|
542
|
+
return response;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Login with email and password
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```typescript
|
|
549
|
+
* const { user, token } = await baasix.auth.login({
|
|
550
|
+
* email: 'user@example.com',
|
|
551
|
+
* password: 'password123'
|
|
552
|
+
* });
|
|
553
|
+
*
|
|
554
|
+
* // Login with tenant (multi-tenant mode)
|
|
555
|
+
* const result = await baasix.auth.login({
|
|
556
|
+
* email: 'user@example.com',
|
|
557
|
+
* password: 'password123',
|
|
558
|
+
* tenantId: 'tenant-uuid'
|
|
559
|
+
* });
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
async login(credentials) {
|
|
563
|
+
const response = await this.client.post(
|
|
564
|
+
"/auth/login",
|
|
565
|
+
{
|
|
566
|
+
email: credentials.email,
|
|
567
|
+
password: credentials.password,
|
|
568
|
+
tenant_Id: credentials.tenantId
|
|
569
|
+
},
|
|
570
|
+
{ skipAuth: true }
|
|
571
|
+
);
|
|
572
|
+
await this.storeTokens(response);
|
|
573
|
+
this.emitAuthStateChange("SIGNED_IN", response.user);
|
|
574
|
+
return response;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Logout the current user
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```typescript
|
|
581
|
+
* await baasix.auth.logout();
|
|
582
|
+
* ```
|
|
583
|
+
*/
|
|
584
|
+
async logout() {
|
|
585
|
+
try {
|
|
586
|
+
await this.client.get("/auth/logout");
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
await this.clearAuth();
|
|
590
|
+
this.emitAuthStateChange("SIGNED_OUT", null);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Get the current authenticated user from the server
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```typescript
|
|
597
|
+
* const user = await baasix.auth.getUser();
|
|
598
|
+
* console.log(user?.email);
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
async getUser() {
|
|
602
|
+
try {
|
|
603
|
+
const response = await this.client.get("/auth/me");
|
|
604
|
+
this.currentUser = response.data;
|
|
605
|
+
await this.storage.set(STORAGE_KEYS.USER, JSON.stringify(response.data));
|
|
606
|
+
return response.data;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (error instanceof BaasixError && error.status === 401) {
|
|
609
|
+
await this.clearAuth();
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Get the cached current user (does not make an API call)
|
|
617
|
+
*
|
|
618
|
+
* @example
|
|
619
|
+
* ```typescript
|
|
620
|
+
* const user = await baasix.auth.getCachedUser();
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
async getCachedUser() {
|
|
624
|
+
if (this.currentUser) {
|
|
625
|
+
return this.currentUser;
|
|
626
|
+
}
|
|
627
|
+
const userJson = await this.storage.get(STORAGE_KEYS.USER);
|
|
628
|
+
if (userJson) {
|
|
629
|
+
try {
|
|
630
|
+
this.currentUser = JSON.parse(userJson);
|
|
631
|
+
return this.currentUser;
|
|
632
|
+
} catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Check if user is authenticated (has valid token)
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```typescript
|
|
643
|
+
* if (await baasix.auth.isAuthenticated()) {
|
|
644
|
+
* // User is logged in
|
|
645
|
+
* }
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
async isAuthenticated() {
|
|
649
|
+
if (this.authMode === "cookie") {
|
|
650
|
+
const user = await this.getCachedUser();
|
|
651
|
+
return user !== null;
|
|
652
|
+
}
|
|
653
|
+
const token = await this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
|
|
654
|
+
if (!token) return false;
|
|
655
|
+
const expiry = await this.storage.get(STORAGE_KEYS.TOKEN_EXPIRY);
|
|
656
|
+
if (expiry && Date.now() >= parseInt(expiry, 10)) {
|
|
657
|
+
const refreshToken = await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
658
|
+
return !!refreshToken;
|
|
659
|
+
}
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get the current access token
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```typescript
|
|
667
|
+
* const token = await baasix.auth.getToken();
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
async getToken() {
|
|
671
|
+
if (this.authMode === "cookie") {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
return await this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Set a static token (useful for server-side or service accounts)
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```typescript
|
|
681
|
+
* baasix.auth.setToken('your-api-token');
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
684
|
+
async setToken(token) {
|
|
685
|
+
await this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Refresh the current token
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* ```typescript
|
|
692
|
+
* const tokens = await baasix.auth.refreshToken();
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
async refreshToken() {
|
|
696
|
+
const refreshToken = await this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
697
|
+
const response = await this.client.post(
|
|
698
|
+
"/auth/refresh",
|
|
699
|
+
this.authMode === "jwt" ? { refreshToken } : void 0
|
|
700
|
+
);
|
|
701
|
+
await this.storeTokens(response);
|
|
702
|
+
const tokens = {
|
|
703
|
+
accessToken: response.token,
|
|
704
|
+
refreshToken: response.refreshToken,
|
|
705
|
+
expiresIn: response.expiresIn,
|
|
706
|
+
expiresAt: response.expiresIn ? Date.now() + response.expiresIn * 1e3 : void 0
|
|
707
|
+
};
|
|
708
|
+
this.emitAuthStateChange("TOKEN_REFRESHED", response.user);
|
|
709
|
+
return tokens;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Request a magic link for passwordless login
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* ```typescript
|
|
716
|
+
* await baasix.auth.sendMagicLink({
|
|
717
|
+
* email: 'user@example.com',
|
|
718
|
+
* redirectUrl: 'https://myapp.com/auth/callback'
|
|
719
|
+
* });
|
|
720
|
+
* ```
|
|
721
|
+
*/
|
|
722
|
+
async sendMagicLink(options) {
|
|
723
|
+
await this.client.post(
|
|
724
|
+
"/auth/magiclink",
|
|
725
|
+
{
|
|
726
|
+
email: options.email,
|
|
727
|
+
link: options.redirectUrl,
|
|
728
|
+
mode: options.mode || "link"
|
|
729
|
+
},
|
|
730
|
+
{ skipAuth: true }
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Verify magic link/code and complete login
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* const { user, token } = await baasix.auth.verifyMagicLink('verification-token');
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
async verifyMagicLink(token) {
|
|
742
|
+
const response = await this.client.post(
|
|
743
|
+
"/auth/magiclink/verify",
|
|
744
|
+
{ token },
|
|
745
|
+
{ skipAuth: true }
|
|
746
|
+
);
|
|
747
|
+
await this.storeTokens(response);
|
|
748
|
+
this.emitAuthStateChange("SIGNED_IN", response.user);
|
|
749
|
+
return response;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Request a password reset
|
|
753
|
+
*
|
|
754
|
+
* @example
|
|
755
|
+
* ```typescript
|
|
756
|
+
* await baasix.auth.forgotPassword({
|
|
757
|
+
* email: 'user@example.com',
|
|
758
|
+
* redirectUrl: 'https://myapp.com/reset-password'
|
|
759
|
+
* });
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
async forgotPassword(options) {
|
|
763
|
+
await this.client.post(
|
|
764
|
+
"/auth/forgot-password",
|
|
765
|
+
{
|
|
766
|
+
email: options.email,
|
|
767
|
+
link: options.redirectUrl
|
|
768
|
+
},
|
|
769
|
+
{ skipAuth: true }
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Reset password using a reset token
|
|
774
|
+
*
|
|
775
|
+
* @example
|
|
776
|
+
* ```typescript
|
|
777
|
+
* await baasix.auth.resetPassword('reset-token', 'newpassword123');
|
|
778
|
+
* ```
|
|
779
|
+
*/
|
|
780
|
+
async resetPassword(token, newPassword) {
|
|
781
|
+
await this.client.post(
|
|
782
|
+
"/auth/reset-password",
|
|
783
|
+
{ token, password: newPassword },
|
|
784
|
+
{ skipAuth: true }
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Change the current user's password
|
|
789
|
+
*
|
|
790
|
+
* @example
|
|
791
|
+
* ```typescript
|
|
792
|
+
* await baasix.auth.changePassword('currentPassword', 'newPassword');
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
795
|
+
async changePassword(currentPassword, newPassword) {
|
|
796
|
+
await this.client.post("/auth/change-password", {
|
|
797
|
+
currentPassword,
|
|
798
|
+
newPassword
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Update the current user's profile
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```typescript
|
|
806
|
+
* const updatedUser = await baasix.auth.updateProfile({
|
|
807
|
+
* firstName: 'Jane',
|
|
808
|
+
* lastName: 'Doe'
|
|
809
|
+
* });
|
|
810
|
+
* ```
|
|
811
|
+
*/
|
|
812
|
+
async updateProfile(data) {
|
|
813
|
+
const response = await this.client.patch("/auth/me", data);
|
|
814
|
+
await this.storage.set(STORAGE_KEYS.USER, JSON.stringify(response.data));
|
|
815
|
+
this.emitAuthStateChange("USER_UPDATED", response.data);
|
|
816
|
+
return response.data;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Get available tenants for the current user (multi-tenant mode)
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* ```typescript
|
|
823
|
+
* const tenants = await baasix.auth.getTenants();
|
|
824
|
+
* ```
|
|
825
|
+
*/
|
|
826
|
+
async getTenants() {
|
|
827
|
+
const response = await this.client.get("/auth/tenants");
|
|
828
|
+
return response.data;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Switch to a different tenant (multi-tenant mode)
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```typescript
|
|
835
|
+
* const { user, token } = await baasix.auth.switchTenant('tenant-uuid');
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
838
|
+
async switchTenant(tenantId) {
|
|
839
|
+
const response = await this.client.post("/auth/switch-tenant", {
|
|
840
|
+
tenant_Id: tenantId
|
|
841
|
+
});
|
|
842
|
+
await this.storeTokens(response);
|
|
843
|
+
await this.storage.set(STORAGE_KEYS.TENANT, tenantId);
|
|
844
|
+
this.emitAuthStateChange("TENANT_SWITCHED", response.user);
|
|
845
|
+
return response;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Get the current authentication state
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* ```typescript
|
|
852
|
+
* const state = await baasix.auth.getState();
|
|
853
|
+
* console.log(state.isAuthenticated, state.user);
|
|
854
|
+
* ```
|
|
855
|
+
*/
|
|
856
|
+
async getState() {
|
|
857
|
+
const isAuthenticated = await this.isAuthenticated();
|
|
858
|
+
const user = await this.getCachedUser();
|
|
859
|
+
return {
|
|
860
|
+
user,
|
|
861
|
+
isAuthenticated,
|
|
862
|
+
isLoading: false,
|
|
863
|
+
error: null
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Initialize authentication state from storage
|
|
868
|
+
* Call this on app startup to restore previous session
|
|
869
|
+
*
|
|
870
|
+
* @example
|
|
871
|
+
* ```typescript
|
|
872
|
+
* await baasix.auth.initialize();
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
async initialize() {
|
|
876
|
+
const state = await this.getState();
|
|
877
|
+
if (state.isAuthenticated && state.user) {
|
|
878
|
+
this.emitAuthStateChange("SIGNED_IN", state.user);
|
|
879
|
+
}
|
|
880
|
+
return state;
|
|
881
|
+
}
|
|
882
|
+
// ===================
|
|
883
|
+
// OAuth / Social Login
|
|
884
|
+
// ===================
|
|
885
|
+
/**
|
|
886
|
+
* Get the OAuth authorization URL for a provider
|
|
887
|
+
* Redirect the user to this URL to start the OAuth flow
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```typescript
|
|
891
|
+
* const url = baasix.auth.getOAuthUrl({
|
|
892
|
+
* provider: 'google',
|
|
893
|
+
* redirectUrl: 'https://myapp.com/auth/callback'
|
|
894
|
+
* });
|
|
895
|
+
* window.location.href = url;
|
|
896
|
+
* ```
|
|
897
|
+
*/
|
|
898
|
+
getOAuthUrl(options) {
|
|
899
|
+
const baseUrl = this.client.getBaseUrl();
|
|
900
|
+
const params = new URLSearchParams({
|
|
901
|
+
redirect_url: options.redirectUrl
|
|
902
|
+
});
|
|
903
|
+
if (options.scopes?.length) {
|
|
904
|
+
params.set("scopes", options.scopes.join(","));
|
|
905
|
+
}
|
|
906
|
+
if (options.state) {
|
|
907
|
+
params.set("state", options.state);
|
|
908
|
+
}
|
|
909
|
+
return `${baseUrl}/auth/signin/${options.provider}?${params.toString()}`;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Handle OAuth callback and complete login
|
|
913
|
+
* Call this from your callback page with the token from URL
|
|
914
|
+
*
|
|
915
|
+
* @example
|
|
916
|
+
* ```typescript
|
|
917
|
+
* // In your callback page
|
|
918
|
+
* const params = new URLSearchParams(window.location.search);
|
|
919
|
+
* const token = params.get('token');
|
|
920
|
+
*
|
|
921
|
+
* if (token) {
|
|
922
|
+
* await baasix.auth.handleOAuthCallback(token);
|
|
923
|
+
* }
|
|
924
|
+
* ```
|
|
925
|
+
*/
|
|
926
|
+
async handleOAuthCallback(token) {
|
|
927
|
+
await this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
928
|
+
const user = await this.getUser();
|
|
929
|
+
const response = {
|
|
930
|
+
token,
|
|
931
|
+
user
|
|
932
|
+
};
|
|
933
|
+
this.emitAuthStateChange("SIGNED_IN", user);
|
|
934
|
+
return response;
|
|
935
|
+
}
|
|
936
|
+
// ===================
|
|
937
|
+
// Email Verification
|
|
938
|
+
// ===================
|
|
939
|
+
/**
|
|
940
|
+
* Request email verification
|
|
941
|
+
* Sends a verification email to the current user
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* ```typescript
|
|
945
|
+
* await baasix.auth.requestEmailVerification('https://myapp.com/verify-email');
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
948
|
+
async requestEmailVerification(redirectUrl) {
|
|
949
|
+
await this.client.post("/auth/request-verify-email", {
|
|
950
|
+
link: redirectUrl
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Verify email with token
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* ```typescript
|
|
958
|
+
* const params = new URLSearchParams(window.location.search);
|
|
959
|
+
* const token = params.get('token');
|
|
960
|
+
*
|
|
961
|
+
* await baasix.auth.verifyEmail(token);
|
|
962
|
+
* ```
|
|
963
|
+
*/
|
|
964
|
+
async verifyEmail(token) {
|
|
965
|
+
await this.client.get("/auth/verify-email", {
|
|
966
|
+
params: { token },
|
|
967
|
+
skipAuth: true
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Check if current session/token is valid
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* ```typescript
|
|
975
|
+
* const isValid = await baasix.auth.checkSession();
|
|
976
|
+
* ```
|
|
977
|
+
*/
|
|
978
|
+
async checkSession() {
|
|
979
|
+
try {
|
|
980
|
+
const response = await this.client.get("/auth/check");
|
|
981
|
+
return response.data.valid;
|
|
982
|
+
} catch {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
// ===================
|
|
987
|
+
// Invitation System
|
|
988
|
+
// ===================
|
|
989
|
+
/**
|
|
990
|
+
* Send an invitation to a user (multi-tenant mode)
|
|
991
|
+
*
|
|
992
|
+
* @example
|
|
993
|
+
* ```typescript
|
|
994
|
+
* await baasix.auth.sendInvite({
|
|
995
|
+
* email: 'newuser@example.com',
|
|
996
|
+
* roleId: 'role-uuid',
|
|
997
|
+
* tenantId: 'tenant-uuid',
|
|
998
|
+
* redirectUrl: 'https://myapp.com/accept-invite'
|
|
999
|
+
* });
|
|
1000
|
+
* ```
|
|
1001
|
+
*/
|
|
1002
|
+
async sendInvite(options) {
|
|
1003
|
+
await this.client.post("/auth/invite", {
|
|
1004
|
+
email: options.email,
|
|
1005
|
+
role_Id: options.roleId,
|
|
1006
|
+
tenant_Id: options.tenantId,
|
|
1007
|
+
link: options.redirectUrl
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Verify an invitation token
|
|
1012
|
+
*
|
|
1013
|
+
* @example
|
|
1014
|
+
* ```typescript
|
|
1015
|
+
* const params = new URLSearchParams(window.location.search);
|
|
1016
|
+
* const token = params.get('token');
|
|
1017
|
+
*
|
|
1018
|
+
* const result = await baasix.auth.verifyInvite(token);
|
|
1019
|
+
* if (result.valid) {
|
|
1020
|
+
* // Show registration form with pre-filled email
|
|
1021
|
+
* }
|
|
1022
|
+
* ```
|
|
1023
|
+
*/
|
|
1024
|
+
async verifyInvite(token, redirectUrl) {
|
|
1025
|
+
const response = await this.client.get(
|
|
1026
|
+
"/auth/verify-invite",
|
|
1027
|
+
{
|
|
1028
|
+
params: {
|
|
1029
|
+
token,
|
|
1030
|
+
link: redirectUrl
|
|
1031
|
+
},
|
|
1032
|
+
skipAuth: true
|
|
1033
|
+
}
|
|
1034
|
+
);
|
|
1035
|
+
return response.data;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Accept an invitation (for existing users)
|
|
1039
|
+
*
|
|
1040
|
+
* @example
|
|
1041
|
+
* ```typescript
|
|
1042
|
+
* await baasix.auth.acceptInvite(token);
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
async acceptInvite(token) {
|
|
1046
|
+
const response = await this.client.post(
|
|
1047
|
+
"/auth/accept-invite",
|
|
1048
|
+
{ token }
|
|
1049
|
+
);
|
|
1050
|
+
await this.storeTokens(response);
|
|
1051
|
+
this.emitAuthStateChange("SIGNED_IN", response.user);
|
|
1052
|
+
return response;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Register with an invitation token
|
|
1056
|
+
*
|
|
1057
|
+
* @example
|
|
1058
|
+
* ```typescript
|
|
1059
|
+
* const { user, token } = await baasix.auth.registerWithInvite({
|
|
1060
|
+
* email: 'user@example.com',
|
|
1061
|
+
* password: 'password',
|
|
1062
|
+
* firstName: 'John',
|
|
1063
|
+
* lastName: 'Doe',
|
|
1064
|
+
* inviteToken: 'invite-token'
|
|
1065
|
+
* });
|
|
1066
|
+
* ```
|
|
1067
|
+
*/
|
|
1068
|
+
async registerWithInvite(data) {
|
|
1069
|
+
const response = await this.client.post(
|
|
1070
|
+
"/auth/register",
|
|
1071
|
+
{
|
|
1072
|
+
...data,
|
|
1073
|
+
inviteToken: data.inviteToken
|
|
1074
|
+
},
|
|
1075
|
+
{ skipAuth: true }
|
|
1076
|
+
);
|
|
1077
|
+
await this.storeTokens(response);
|
|
1078
|
+
this.emitAuthStateChange("SIGNED_IN", response.user);
|
|
1079
|
+
return response;
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
// src/modules/items.ts
|
|
1084
|
+
var QueryBuilder = class {
|
|
1085
|
+
collection;
|
|
1086
|
+
client;
|
|
1087
|
+
queryParams = {};
|
|
1088
|
+
constructor(collection, client) {
|
|
1089
|
+
this.collection = collection;
|
|
1090
|
+
this.client = client;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Select specific fields to return
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```typescript
|
|
1097
|
+
* items.select(['id', 'name', 'author.*'])
|
|
1098
|
+
* items.select('*', 'category.name')
|
|
1099
|
+
* ```
|
|
1100
|
+
*/
|
|
1101
|
+
select(...fields) {
|
|
1102
|
+
const flatFields = fields.length === 1 && Array.isArray(fields[0]) ? fields[0] : fields;
|
|
1103
|
+
this.queryParams.fields = flatFields;
|
|
1104
|
+
return this;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Alias for select()
|
|
1108
|
+
*/
|
|
1109
|
+
fields(...fields) {
|
|
1110
|
+
return this.select(...fields);
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Add filter conditions
|
|
1114
|
+
*
|
|
1115
|
+
* @example
|
|
1116
|
+
* ```typescript
|
|
1117
|
+
* // Simple equality
|
|
1118
|
+
* items.filter({ status: { eq: 'active' } })
|
|
1119
|
+
*
|
|
1120
|
+
* // Multiple conditions
|
|
1121
|
+
* items.filter({
|
|
1122
|
+
* AND: [
|
|
1123
|
+
* { status: { eq: 'active' } },
|
|
1124
|
+
* { price: { gte: 100 } }
|
|
1125
|
+
* ]
|
|
1126
|
+
* })
|
|
1127
|
+
*
|
|
1128
|
+
* // Relation filtering
|
|
1129
|
+
* items.filter({ 'author.name': { like: 'John' } })
|
|
1130
|
+
* ```
|
|
1131
|
+
*/
|
|
1132
|
+
filter(filter) {
|
|
1133
|
+
this.queryParams.filter = filter;
|
|
1134
|
+
return this;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Alias for filter()
|
|
1138
|
+
*/
|
|
1139
|
+
where(filter) {
|
|
1140
|
+
return this.filter(filter);
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Sort results
|
|
1144
|
+
*
|
|
1145
|
+
* @example
|
|
1146
|
+
* ```typescript
|
|
1147
|
+
* // Object notation
|
|
1148
|
+
* items.sort({ createdAt: 'desc', name: 'asc' })
|
|
1149
|
+
*
|
|
1150
|
+
* // Array notation with prefix
|
|
1151
|
+
* items.sort(['-createdAt', 'name'])
|
|
1152
|
+
*
|
|
1153
|
+
* // String shorthand
|
|
1154
|
+
* items.sort('createdAt:desc')
|
|
1155
|
+
* ```
|
|
1156
|
+
*/
|
|
1157
|
+
sort(sort) {
|
|
1158
|
+
this.queryParams.sort = sort;
|
|
1159
|
+
return this;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Alias for sort()
|
|
1163
|
+
*/
|
|
1164
|
+
orderBy(sort) {
|
|
1165
|
+
return this.sort(sort);
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Limit number of results
|
|
1169
|
+
*
|
|
1170
|
+
* @example
|
|
1171
|
+
* ```typescript
|
|
1172
|
+
* items.limit(20)
|
|
1173
|
+
* items.limit(-1) // All results
|
|
1174
|
+
* ```
|
|
1175
|
+
*/
|
|
1176
|
+
limit(limit) {
|
|
1177
|
+
this.queryParams.limit = limit;
|
|
1178
|
+
return this;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Set page number (1-indexed)
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```typescript
|
|
1185
|
+
* items.page(2).limit(20)
|
|
1186
|
+
* ```
|
|
1187
|
+
*/
|
|
1188
|
+
page(page) {
|
|
1189
|
+
this.queryParams.page = page;
|
|
1190
|
+
return this;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Skip a number of results
|
|
1194
|
+
*
|
|
1195
|
+
* @example
|
|
1196
|
+
* ```typescript
|
|
1197
|
+
* items.offset(20)
|
|
1198
|
+
* ```
|
|
1199
|
+
*/
|
|
1200
|
+
offset(offset) {
|
|
1201
|
+
this.queryParams.offset = offset;
|
|
1202
|
+
return this;
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Full-text search
|
|
1206
|
+
*
|
|
1207
|
+
* @example
|
|
1208
|
+
* ```typescript
|
|
1209
|
+
* items.search('keyword', ['title', 'description'])
|
|
1210
|
+
* ```
|
|
1211
|
+
*/
|
|
1212
|
+
search(query, fields) {
|
|
1213
|
+
this.queryParams.search = query;
|
|
1214
|
+
if (fields) {
|
|
1215
|
+
this.queryParams.searchFields = fields;
|
|
1216
|
+
}
|
|
1217
|
+
return this;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Include soft-deleted items
|
|
1221
|
+
*
|
|
1222
|
+
* @example
|
|
1223
|
+
* ```typescript
|
|
1224
|
+
* items.withDeleted()
|
|
1225
|
+
* ```
|
|
1226
|
+
*/
|
|
1227
|
+
withDeleted() {
|
|
1228
|
+
this.queryParams.paranoid = false;
|
|
1229
|
+
return this;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Filter related items in O2M/M2M relations
|
|
1233
|
+
*
|
|
1234
|
+
* @example
|
|
1235
|
+
* ```typescript
|
|
1236
|
+
* // Only show approved comments
|
|
1237
|
+
* items.relFilter({
|
|
1238
|
+
* comments: { approved: { eq: true } }
|
|
1239
|
+
* })
|
|
1240
|
+
* ```
|
|
1241
|
+
*/
|
|
1242
|
+
relFilter(conditions) {
|
|
1243
|
+
this.queryParams.relConditions = conditions;
|
|
1244
|
+
return this;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Get the built query parameters
|
|
1248
|
+
*/
|
|
1249
|
+
getQuery() {
|
|
1250
|
+
return { ...this.queryParams };
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Execute the query and return results
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```typescript
|
|
1257
|
+
* const { data, totalCount } = await items
|
|
1258
|
+
* .filter({ status: { eq: 'active' } })
|
|
1259
|
+
* .sort({ createdAt: 'desc' })
|
|
1260
|
+
* .limit(10)
|
|
1261
|
+
* .get();
|
|
1262
|
+
* ```
|
|
1263
|
+
*/
|
|
1264
|
+
async get() {
|
|
1265
|
+
return this.client.get(`/items/${this.collection}`, {
|
|
1266
|
+
params: this.buildParams()
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Execute the query and return the first result
|
|
1271
|
+
*
|
|
1272
|
+
* @example
|
|
1273
|
+
* ```typescript
|
|
1274
|
+
* const item = await items
|
|
1275
|
+
* .filter({ slug: { eq: 'my-post' } })
|
|
1276
|
+
* .first();
|
|
1277
|
+
* ```
|
|
1278
|
+
*/
|
|
1279
|
+
async first() {
|
|
1280
|
+
const result = await this.limit(1).get();
|
|
1281
|
+
return result.data[0] || null;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Count matching items
|
|
1285
|
+
*
|
|
1286
|
+
* @example
|
|
1287
|
+
* ```typescript
|
|
1288
|
+
* const count = await items.filter({ status: { eq: 'active' } }).count();
|
|
1289
|
+
* ```
|
|
1290
|
+
*/
|
|
1291
|
+
async count() {
|
|
1292
|
+
const result = await this.client.get(
|
|
1293
|
+
`/items/${this.collection}`,
|
|
1294
|
+
{
|
|
1295
|
+
params: {
|
|
1296
|
+
...this.buildParams(),
|
|
1297
|
+
limit: 0
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
return result.totalCount || 0;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Build query parameters for the request
|
|
1305
|
+
*/
|
|
1306
|
+
buildParams() {
|
|
1307
|
+
const params = {};
|
|
1308
|
+
if (this.queryParams.fields) {
|
|
1309
|
+
params.fields = this.queryParams.fields;
|
|
1310
|
+
}
|
|
1311
|
+
if (this.queryParams.filter) {
|
|
1312
|
+
params.filter = this.queryParams.filter;
|
|
1313
|
+
}
|
|
1314
|
+
if (this.queryParams.sort) {
|
|
1315
|
+
params.sort = this.queryParams.sort;
|
|
1316
|
+
}
|
|
1317
|
+
if (this.queryParams.limit !== void 0) {
|
|
1318
|
+
params.limit = this.queryParams.limit;
|
|
1319
|
+
}
|
|
1320
|
+
if (this.queryParams.page !== void 0) {
|
|
1321
|
+
params.page = this.queryParams.page;
|
|
1322
|
+
}
|
|
1323
|
+
if (this.queryParams.offset !== void 0) {
|
|
1324
|
+
params.offset = this.queryParams.offset;
|
|
1325
|
+
}
|
|
1326
|
+
if (this.queryParams.search) {
|
|
1327
|
+
params.search = this.queryParams.search;
|
|
1328
|
+
}
|
|
1329
|
+
if (this.queryParams.searchFields) {
|
|
1330
|
+
params.searchFields = this.queryParams.searchFields;
|
|
1331
|
+
}
|
|
1332
|
+
if (this.queryParams.paranoid !== void 0) {
|
|
1333
|
+
params.paranoid = this.queryParams.paranoid;
|
|
1334
|
+
}
|
|
1335
|
+
if (this.queryParams.relConditions) {
|
|
1336
|
+
params.relConditions = this.queryParams.relConditions;
|
|
1337
|
+
}
|
|
1338
|
+
if (this.queryParams.aggregate) {
|
|
1339
|
+
params.aggregate = this.queryParams.aggregate;
|
|
1340
|
+
}
|
|
1341
|
+
if (this.queryParams.groupBy) {
|
|
1342
|
+
params.groupBy = this.queryParams.groupBy;
|
|
1343
|
+
}
|
|
1344
|
+
return params;
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
var ItemsModule = class {
|
|
1348
|
+
collection;
|
|
1349
|
+
client;
|
|
1350
|
+
constructor(collection, config) {
|
|
1351
|
+
this.collection = collection;
|
|
1352
|
+
this.client = config.client;
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Create a query builder for fluent query construction
|
|
1356
|
+
*
|
|
1357
|
+
* @example
|
|
1358
|
+
* ```typescript
|
|
1359
|
+
* const results = await baasix.items('posts')
|
|
1360
|
+
* .query()
|
|
1361
|
+
* .select('*', 'author.*')
|
|
1362
|
+
* .filter({ status: { eq: 'published' } })
|
|
1363
|
+
* .sort({ createdAt: 'desc' })
|
|
1364
|
+
* .limit(10)
|
|
1365
|
+
* .get();
|
|
1366
|
+
* ```
|
|
1367
|
+
*/
|
|
1368
|
+
query() {
|
|
1369
|
+
return new QueryBuilder(this.collection, this.client);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Find items with optional query parameters
|
|
1373
|
+
*
|
|
1374
|
+
* @example
|
|
1375
|
+
* ```typescript
|
|
1376
|
+
* // Simple query
|
|
1377
|
+
* const { data } = await items.find();
|
|
1378
|
+
*
|
|
1379
|
+
* // With parameters
|
|
1380
|
+
* const { data, totalCount } = await items.find({
|
|
1381
|
+
* filter: { status: { eq: 'active' } },
|
|
1382
|
+
* sort: { createdAt: 'desc' },
|
|
1383
|
+
* limit: 20,
|
|
1384
|
+
* page: 1,
|
|
1385
|
+
* fields: ['id', 'name', 'price']
|
|
1386
|
+
* });
|
|
1387
|
+
* ```
|
|
1388
|
+
*/
|
|
1389
|
+
async find(params) {
|
|
1390
|
+
return this.client.get(`/items/${this.collection}`, {
|
|
1391
|
+
params
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Alias for find()
|
|
1396
|
+
*/
|
|
1397
|
+
async findMany(params) {
|
|
1398
|
+
return this.find(params);
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Find a single item by ID
|
|
1402
|
+
*
|
|
1403
|
+
* @example
|
|
1404
|
+
* ```typescript
|
|
1405
|
+
* const product = await items.findOne('product-uuid');
|
|
1406
|
+
*
|
|
1407
|
+
* // With specific fields
|
|
1408
|
+
* const product = await items.findOne('product-uuid', {
|
|
1409
|
+
* fields: ['id', 'name', 'category.*']
|
|
1410
|
+
* });
|
|
1411
|
+
* ```
|
|
1412
|
+
*/
|
|
1413
|
+
async findOne(id, params) {
|
|
1414
|
+
const response = await this.client.get(
|
|
1415
|
+
`/items/${this.collection}/${id}`,
|
|
1416
|
+
{ params }
|
|
1417
|
+
);
|
|
1418
|
+
return response.data;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Alias for findOne()
|
|
1422
|
+
*/
|
|
1423
|
+
async get(id, params) {
|
|
1424
|
+
return this.findOne(id, params);
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Create a new item
|
|
1428
|
+
*
|
|
1429
|
+
* @example
|
|
1430
|
+
* ```typescript
|
|
1431
|
+
* const id = await items.create({
|
|
1432
|
+
* name: 'New Product',
|
|
1433
|
+
* price: 29.99,
|
|
1434
|
+
* status: 'draft'
|
|
1435
|
+
* });
|
|
1436
|
+
* ```
|
|
1437
|
+
*/
|
|
1438
|
+
async create(data) {
|
|
1439
|
+
const response = await this.client.post(
|
|
1440
|
+
`/items/${this.collection}`,
|
|
1441
|
+
data
|
|
1442
|
+
);
|
|
1443
|
+
return response.data;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Alias for create()
|
|
1447
|
+
*/
|
|
1448
|
+
async insert(data) {
|
|
1449
|
+
return this.create(data);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Create multiple items at once
|
|
1453
|
+
*
|
|
1454
|
+
* @example
|
|
1455
|
+
* ```typescript
|
|
1456
|
+
* const ids = await items.createMany([
|
|
1457
|
+
* { name: 'Product 1', price: 10 },
|
|
1458
|
+
* { name: 'Product 2', price: 20 }
|
|
1459
|
+
* ]);
|
|
1460
|
+
* ```
|
|
1461
|
+
*/
|
|
1462
|
+
async createMany(data) {
|
|
1463
|
+
const response = await this.client.post(
|
|
1464
|
+
`/items/${this.collection}/bulk`,
|
|
1465
|
+
data
|
|
1466
|
+
);
|
|
1467
|
+
return response.data;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Alias for createMany()
|
|
1471
|
+
*/
|
|
1472
|
+
async insertMany(data) {
|
|
1473
|
+
return this.createMany(data);
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Update an existing item
|
|
1477
|
+
*
|
|
1478
|
+
* @example
|
|
1479
|
+
* ```typescript
|
|
1480
|
+
* await items.update('product-uuid', {
|
|
1481
|
+
* price: 24.99,
|
|
1482
|
+
* status: 'published'
|
|
1483
|
+
* });
|
|
1484
|
+
* ```
|
|
1485
|
+
*/
|
|
1486
|
+
async update(id, data) {
|
|
1487
|
+
const response = await this.client.patch(
|
|
1488
|
+
`/items/${this.collection}/${id}`,
|
|
1489
|
+
data
|
|
1490
|
+
);
|
|
1491
|
+
return response.data;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Update multiple items at once
|
|
1495
|
+
*
|
|
1496
|
+
* @example
|
|
1497
|
+
* ```typescript
|
|
1498
|
+
* // Update by IDs
|
|
1499
|
+
* await items.updateMany(['id1', 'id2'], { status: 'archived' });
|
|
1500
|
+
* ```
|
|
1501
|
+
*/
|
|
1502
|
+
async updateMany(ids, data) {
|
|
1503
|
+
const response = await this.client.patch(
|
|
1504
|
+
`/items/${this.collection}/bulk`,
|
|
1505
|
+
{ ids, data }
|
|
1506
|
+
);
|
|
1507
|
+
return response.data;
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Upsert an item (create if not exists, update if exists)
|
|
1511
|
+
*
|
|
1512
|
+
* @example
|
|
1513
|
+
* ```typescript
|
|
1514
|
+
* const id = await items.upsert(
|
|
1515
|
+
* { sku: 'WIDGET-001' },
|
|
1516
|
+
* { name: 'Widget', price: 29.99, sku: 'WIDGET-001' }
|
|
1517
|
+
* );
|
|
1518
|
+
* ```
|
|
1519
|
+
*/
|
|
1520
|
+
async upsert(filter, data) {
|
|
1521
|
+
const existing = await this.find({ filter, limit: 1 });
|
|
1522
|
+
if (existing.data.length > 0) {
|
|
1523
|
+
return this.update(existing.data[0].id, data);
|
|
1524
|
+
}
|
|
1525
|
+
return this.create(data);
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Delete an item by ID
|
|
1529
|
+
*
|
|
1530
|
+
* @example
|
|
1531
|
+
* ```typescript
|
|
1532
|
+
* await items.delete('product-uuid');
|
|
1533
|
+
* ```
|
|
1534
|
+
*/
|
|
1535
|
+
async delete(id) {
|
|
1536
|
+
await this.client.delete(`/items/${this.collection}/${id}`);
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Delete multiple items
|
|
1540
|
+
*
|
|
1541
|
+
* @example
|
|
1542
|
+
* ```typescript
|
|
1543
|
+
* await items.deleteMany(['id1', 'id2', 'id3']);
|
|
1544
|
+
* ```
|
|
1545
|
+
*/
|
|
1546
|
+
async deleteMany(ids) {
|
|
1547
|
+
await this.client.delete(`/items/${this.collection}/bulk`, {
|
|
1548
|
+
params: { ids }
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Soft delete an item (if paranoid mode is enabled)
|
|
1553
|
+
*
|
|
1554
|
+
* @example
|
|
1555
|
+
* ```typescript
|
|
1556
|
+
* await items.softDelete('product-uuid');
|
|
1557
|
+
* ```
|
|
1558
|
+
*/
|
|
1559
|
+
async softDelete(id) {
|
|
1560
|
+
await this.update(id, { deletedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Restore a soft-deleted item
|
|
1564
|
+
*
|
|
1565
|
+
* @example
|
|
1566
|
+
* ```typescript
|
|
1567
|
+
* await items.restore('product-uuid');
|
|
1568
|
+
* ```
|
|
1569
|
+
*/
|
|
1570
|
+
async restore(id) {
|
|
1571
|
+
await this.update(id, { deletedAt: null });
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Aggregate data with grouping
|
|
1575
|
+
*
|
|
1576
|
+
* @example
|
|
1577
|
+
* ```typescript
|
|
1578
|
+
* const results = await items.aggregate({
|
|
1579
|
+
* aggregate: {
|
|
1580
|
+
* total: { function: 'sum', field: 'amount' },
|
|
1581
|
+
* count: { function: 'count', field: 'id' },
|
|
1582
|
+
* avgPrice: { function: 'avg', field: 'price' }
|
|
1583
|
+
* },
|
|
1584
|
+
* groupBy: ['category', 'status'],
|
|
1585
|
+
* filter: { createdAt: { gte: '$NOW-DAYS_30' } }
|
|
1586
|
+
* });
|
|
1587
|
+
* ```
|
|
1588
|
+
*/
|
|
1589
|
+
async aggregate(params) {
|
|
1590
|
+
const response = await this.client.get(
|
|
1591
|
+
`/items/${this.collection}`,
|
|
1592
|
+
{ params }
|
|
1593
|
+
);
|
|
1594
|
+
return response.data;
|
|
1595
|
+
}
|
|
1596
|
+
// ===================
|
|
1597
|
+
// Import Operations
|
|
1598
|
+
// ===================
|
|
1599
|
+
/**
|
|
1600
|
+
* Import items from a CSV file
|
|
1601
|
+
*
|
|
1602
|
+
* @example
|
|
1603
|
+
* ```typescript
|
|
1604
|
+
* // Browser
|
|
1605
|
+
* const fileInput = document.querySelector('input[type="file"]');
|
|
1606
|
+
* const file = fileInput.files[0];
|
|
1607
|
+
*
|
|
1608
|
+
* const result = await baasix.items('products').importCSV(file, {
|
|
1609
|
+
* delimiter: ',',
|
|
1610
|
+
* skipFirstRow: true
|
|
1611
|
+
* });
|
|
1612
|
+
*
|
|
1613
|
+
* console.log(`Imported ${result.created} items`);
|
|
1614
|
+
* ```
|
|
1615
|
+
*/
|
|
1616
|
+
async importCSV(file, options) {
|
|
1617
|
+
const formData = new FormData();
|
|
1618
|
+
if (file instanceof File) {
|
|
1619
|
+
formData.append("file", file);
|
|
1620
|
+
} else {
|
|
1621
|
+
formData.append("file", file);
|
|
1622
|
+
}
|
|
1623
|
+
if (options?.delimiter) {
|
|
1624
|
+
formData.append("delimiter", options.delimiter);
|
|
1625
|
+
}
|
|
1626
|
+
if (options?.skipFirstRow !== void 0) {
|
|
1627
|
+
formData.append("skipFirstRow", String(options.skipFirstRow));
|
|
1628
|
+
}
|
|
1629
|
+
if (options?.dateFormat) {
|
|
1630
|
+
formData.append("dateFormat", options.dateFormat);
|
|
1631
|
+
}
|
|
1632
|
+
if (options?.fieldMapping) {
|
|
1633
|
+
formData.append("fieldMapping", JSON.stringify(options.fieldMapping));
|
|
1634
|
+
}
|
|
1635
|
+
const response = await this.client.post(
|
|
1636
|
+
`/items/${this.collection}/import/csv`,
|
|
1637
|
+
formData
|
|
1638
|
+
);
|
|
1639
|
+
return response.data;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Import items from a JSON file
|
|
1643
|
+
*
|
|
1644
|
+
* @example
|
|
1645
|
+
* ```typescript
|
|
1646
|
+
* const file = fileInput.files[0]; // JSON file
|
|
1647
|
+
* const result = await baasix.items('products').importJSON(file);
|
|
1648
|
+
*
|
|
1649
|
+
* console.log(`Imported ${result.created} items`);
|
|
1650
|
+
* ```
|
|
1651
|
+
*/
|
|
1652
|
+
async importJSON(file, options) {
|
|
1653
|
+
const formData = new FormData();
|
|
1654
|
+
if (file instanceof File) {
|
|
1655
|
+
formData.append("file", file);
|
|
1656
|
+
} else {
|
|
1657
|
+
formData.append("file", file);
|
|
1658
|
+
}
|
|
1659
|
+
if (options?.fieldMapping) {
|
|
1660
|
+
formData.append("fieldMapping", JSON.stringify(options.fieldMapping));
|
|
1661
|
+
}
|
|
1662
|
+
const response = await this.client.post(
|
|
1663
|
+
`/items/${this.collection}/import/json`,
|
|
1664
|
+
formData
|
|
1665
|
+
);
|
|
1666
|
+
return response.data;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Import items from an array of objects
|
|
1670
|
+
*
|
|
1671
|
+
* @example
|
|
1672
|
+
* ```typescript
|
|
1673
|
+
* const data = [
|
|
1674
|
+
* { name: 'Product 1', price: 29.99 },
|
|
1675
|
+
* { name: 'Product 2', price: 39.99 }
|
|
1676
|
+
* ];
|
|
1677
|
+
*
|
|
1678
|
+
* const result = await baasix.items('products').importData(data);
|
|
1679
|
+
* ```
|
|
1680
|
+
*/
|
|
1681
|
+
async importData(data) {
|
|
1682
|
+
const response = await this.client.post(
|
|
1683
|
+
`/items/${this.collection}/bulk`,
|
|
1684
|
+
data
|
|
1685
|
+
);
|
|
1686
|
+
return response;
|
|
1687
|
+
}
|
|
1688
|
+
// ===================
|
|
1689
|
+
// Sort Operations
|
|
1690
|
+
// ===================
|
|
1691
|
+
/**
|
|
1692
|
+
* Sort/reorder items (move item before another)
|
|
1693
|
+
*
|
|
1694
|
+
* @example
|
|
1695
|
+
* ```typescript
|
|
1696
|
+
* // Move item1 before item2
|
|
1697
|
+
* await baasix.items('products').sortItem('item1-uuid', 'item2-uuid');
|
|
1698
|
+
* ```
|
|
1699
|
+
*/
|
|
1700
|
+
async sortItem(itemId, beforeItemId) {
|
|
1701
|
+
await this.client.post("/utils/sort", {
|
|
1702
|
+
collection: this.collection,
|
|
1703
|
+
item: itemId,
|
|
1704
|
+
to: beforeItemId
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Reorder multiple items
|
|
1709
|
+
*
|
|
1710
|
+
* @example
|
|
1711
|
+
* ```typescript
|
|
1712
|
+
* // Set explicit order
|
|
1713
|
+
* await baasix.items('products').reorder([
|
|
1714
|
+
* 'item3-uuid',
|
|
1715
|
+
* 'item1-uuid',
|
|
1716
|
+
* 'item2-uuid'
|
|
1717
|
+
* ]);
|
|
1718
|
+
* ```
|
|
1719
|
+
*/
|
|
1720
|
+
async reorder(orderedIds) {
|
|
1721
|
+
for (let i = 1; i < orderedIds.length; i++) {
|
|
1722
|
+
await this.client.post("/utils/sort", {
|
|
1723
|
+
collection: this.collection,
|
|
1724
|
+
item: orderedIds[i],
|
|
1725
|
+
to: orderedIds[i - 1]
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
// src/modules/files.ts
|
|
1732
|
+
var FilesModule = class {
|
|
1733
|
+
client;
|
|
1734
|
+
constructor(config) {
|
|
1735
|
+
this.client = config.client;
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Upload a file
|
|
1739
|
+
*
|
|
1740
|
+
* @example
|
|
1741
|
+
* ```typescript
|
|
1742
|
+
* // Browser File API
|
|
1743
|
+
* const metadata = await baasix.files.upload(fileInput.files[0], {
|
|
1744
|
+
* title: 'Product Image',
|
|
1745
|
+
* folder: 'products',
|
|
1746
|
+
* isPublic: true,
|
|
1747
|
+
* onProgress: (progress) => console.log(`${progress}% uploaded`)
|
|
1748
|
+
* });
|
|
1749
|
+
*
|
|
1750
|
+
* // React Native with expo-image-picker
|
|
1751
|
+
* const metadata = await baasix.files.upload({
|
|
1752
|
+
* uri: result.uri,
|
|
1753
|
+
* name: 'photo.jpg',
|
|
1754
|
+
* type: 'image/jpeg'
|
|
1755
|
+
* });
|
|
1756
|
+
* ```
|
|
1757
|
+
*/
|
|
1758
|
+
async upload(file, options) {
|
|
1759
|
+
const formData = new FormData();
|
|
1760
|
+
if (file instanceof File || file instanceof Blob) {
|
|
1761
|
+
formData.append("file", file);
|
|
1762
|
+
} else {
|
|
1763
|
+
formData.append("file", file);
|
|
1764
|
+
}
|
|
1765
|
+
if (options?.title) {
|
|
1766
|
+
formData.append("title", options.title);
|
|
1767
|
+
}
|
|
1768
|
+
if (options?.description) {
|
|
1769
|
+
formData.append("description", options.description);
|
|
1770
|
+
}
|
|
1771
|
+
if (options?.folder) {
|
|
1772
|
+
formData.append("folder", options.folder);
|
|
1773
|
+
}
|
|
1774
|
+
if (options?.storage) {
|
|
1775
|
+
formData.append("storage", options.storage);
|
|
1776
|
+
}
|
|
1777
|
+
if (options?.isPublic !== void 0) {
|
|
1778
|
+
formData.append("isPublic", String(options.isPublic));
|
|
1779
|
+
}
|
|
1780
|
+
if (options?.metadata) {
|
|
1781
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
1782
|
+
}
|
|
1783
|
+
const response = await this.client.upload(
|
|
1784
|
+
"/files",
|
|
1785
|
+
formData,
|
|
1786
|
+
{ onProgress: options?.onProgress }
|
|
1787
|
+
);
|
|
1788
|
+
return response.data;
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Upload multiple files
|
|
1792
|
+
*
|
|
1793
|
+
* @example
|
|
1794
|
+
* ```typescript
|
|
1795
|
+
* const files = await baasix.files.uploadMany(fileInput.files, {
|
|
1796
|
+
* folder: 'gallery',
|
|
1797
|
+
* isPublic: true
|
|
1798
|
+
* });
|
|
1799
|
+
* ```
|
|
1800
|
+
*/
|
|
1801
|
+
async uploadMany(files, options) {
|
|
1802
|
+
const results = [];
|
|
1803
|
+
const fileArray = files instanceof FileList ? Array.from(files) : files;
|
|
1804
|
+
for (const file of fileArray) {
|
|
1805
|
+
const metadata = await this.upload(file, options);
|
|
1806
|
+
results.push(metadata);
|
|
1807
|
+
}
|
|
1808
|
+
return results;
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* List files with optional filtering
|
|
1812
|
+
*
|
|
1813
|
+
* @example
|
|
1814
|
+
* ```typescript
|
|
1815
|
+
* const { data, totalCount } = await baasix.files.find({
|
|
1816
|
+
* filter: { mimeType: { startsWith: 'image/' } },
|
|
1817
|
+
* limit: 20
|
|
1818
|
+
* });
|
|
1819
|
+
* ```
|
|
1820
|
+
*/
|
|
1821
|
+
async find(params) {
|
|
1822
|
+
return this.client.get("/files", {
|
|
1823
|
+
params
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Get file metadata by ID
|
|
1828
|
+
*
|
|
1829
|
+
* @example
|
|
1830
|
+
* ```typescript
|
|
1831
|
+
* const file = await baasix.files.findOne('file-uuid');
|
|
1832
|
+
* console.log(file.filename, file.size);
|
|
1833
|
+
* ```
|
|
1834
|
+
*/
|
|
1835
|
+
async findOne(id) {
|
|
1836
|
+
const response = await this.client.get(`/files/${id}`);
|
|
1837
|
+
return response.data;
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Update file metadata
|
|
1841
|
+
*
|
|
1842
|
+
* @example
|
|
1843
|
+
* ```typescript
|
|
1844
|
+
* await baasix.files.update('file-uuid', {
|
|
1845
|
+
* title: 'Updated Title',
|
|
1846
|
+
* description: 'New description'
|
|
1847
|
+
* });
|
|
1848
|
+
* ```
|
|
1849
|
+
*/
|
|
1850
|
+
async update(id, data) {
|
|
1851
|
+
const response = await this.client.patch(
|
|
1852
|
+
`/files/${id}`,
|
|
1853
|
+
data
|
|
1854
|
+
);
|
|
1855
|
+
return response.data;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Delete a file
|
|
1859
|
+
*
|
|
1860
|
+
* @example
|
|
1861
|
+
* ```typescript
|
|
1862
|
+
* await baasix.files.delete('file-uuid');
|
|
1863
|
+
* ```
|
|
1864
|
+
*/
|
|
1865
|
+
async delete(id) {
|
|
1866
|
+
await this.client.delete(`/files/${id}`);
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Delete multiple files
|
|
1870
|
+
*
|
|
1871
|
+
* @example
|
|
1872
|
+
* ```typescript
|
|
1873
|
+
* await baasix.files.deleteMany(['id1', 'id2', 'id3']);
|
|
1874
|
+
* ```
|
|
1875
|
+
*/
|
|
1876
|
+
async deleteMany(ids) {
|
|
1877
|
+
await Promise.all(ids.map((id) => this.delete(id)));
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Get the URL for an asset with optional transformations
|
|
1881
|
+
*
|
|
1882
|
+
* @example
|
|
1883
|
+
* ```typescript
|
|
1884
|
+
* // Original file
|
|
1885
|
+
* const url = baasix.files.getAssetUrl('file-uuid');
|
|
1886
|
+
*
|
|
1887
|
+
* // Resized thumbnail
|
|
1888
|
+
* const thumbUrl = baasix.files.getAssetUrl('file-uuid', {
|
|
1889
|
+
* width: 200,
|
|
1890
|
+
* height: 200,
|
|
1891
|
+
* fit: 'cover',
|
|
1892
|
+
* quality: 80
|
|
1893
|
+
* });
|
|
1894
|
+
*
|
|
1895
|
+
* // Convert to WebP
|
|
1896
|
+
* const webpUrl = baasix.files.getAssetUrl('file-uuid', {
|
|
1897
|
+
* format: 'webp',
|
|
1898
|
+
* quality: 85
|
|
1899
|
+
* });
|
|
1900
|
+
* ```
|
|
1901
|
+
*/
|
|
1902
|
+
getAssetUrl(id, options) {
|
|
1903
|
+
const baseUrl = this.client.getBaseUrl();
|
|
1904
|
+
const url = new URL(`/assets/${id}`, baseUrl);
|
|
1905
|
+
if (options) {
|
|
1906
|
+
if (options.width) {
|
|
1907
|
+
url.searchParams.set("width", String(options.width));
|
|
1908
|
+
}
|
|
1909
|
+
if (options.height) {
|
|
1910
|
+
url.searchParams.set("height", String(options.height));
|
|
1911
|
+
}
|
|
1912
|
+
if (options.fit) {
|
|
1913
|
+
url.searchParams.set("fit", options.fit);
|
|
1914
|
+
}
|
|
1915
|
+
if (options.quality) {
|
|
1916
|
+
url.searchParams.set("quality", String(options.quality));
|
|
1917
|
+
}
|
|
1918
|
+
if (options.format) {
|
|
1919
|
+
url.searchParams.set("format", options.format);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return url.toString();
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Download a file as a Blob
|
|
1926
|
+
*
|
|
1927
|
+
* @example
|
|
1928
|
+
* ```typescript
|
|
1929
|
+
* const blob = await baasix.files.download('file-uuid');
|
|
1930
|
+
*
|
|
1931
|
+
* // Create download link
|
|
1932
|
+
* const url = URL.createObjectURL(blob);
|
|
1933
|
+
* const a = document.createElement('a');
|
|
1934
|
+
* a.href = url;
|
|
1935
|
+
* a.download = 'filename.pdf';
|
|
1936
|
+
* a.click();
|
|
1937
|
+
* ```
|
|
1938
|
+
*/
|
|
1939
|
+
async download(id, options) {
|
|
1940
|
+
const url = this.getAssetUrl(id, options);
|
|
1941
|
+
const response = await fetch(url);
|
|
1942
|
+
if (!response.ok) {
|
|
1943
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
1944
|
+
}
|
|
1945
|
+
return response.blob();
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Get file as base64 string (useful for React Native)
|
|
1949
|
+
*
|
|
1950
|
+
* @example
|
|
1951
|
+
* ```typescript
|
|
1952
|
+
* const base64 = await baasix.files.toBase64('file-uuid');
|
|
1953
|
+
* // Use in Image component: <Image source={{ uri: `data:image/jpeg;base64,${base64}` }} />
|
|
1954
|
+
* ```
|
|
1955
|
+
*/
|
|
1956
|
+
async toBase64(id, options) {
|
|
1957
|
+
const blob = await this.download(id, options);
|
|
1958
|
+
return new Promise((resolve, reject) => {
|
|
1959
|
+
const reader = new FileReader();
|
|
1960
|
+
reader.onloadend = () => {
|
|
1961
|
+
const result = reader.result;
|
|
1962
|
+
const base64 = result.split(",")[1];
|
|
1963
|
+
resolve(base64);
|
|
1964
|
+
};
|
|
1965
|
+
reader.onerror = reject;
|
|
1966
|
+
reader.readAsDataURL(blob);
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Import file from URL
|
|
1971
|
+
*
|
|
1972
|
+
* @example
|
|
1973
|
+
* ```typescript
|
|
1974
|
+
* const file = await baasix.files.importFromUrl(
|
|
1975
|
+
* 'https://example.com/image.jpg',
|
|
1976
|
+
* { title: 'Imported Image' }
|
|
1977
|
+
* );
|
|
1978
|
+
* ```
|
|
1979
|
+
*/
|
|
1980
|
+
async importFromUrl(url, options) {
|
|
1981
|
+
const response = await this.client.post(
|
|
1982
|
+
"/files/import",
|
|
1983
|
+
{
|
|
1984
|
+
url,
|
|
1985
|
+
...options
|
|
1986
|
+
}
|
|
1987
|
+
);
|
|
1988
|
+
return response.data;
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
// src/modules/schemas.ts
|
|
1993
|
+
var SchemasModule = class {
|
|
1994
|
+
client;
|
|
1995
|
+
constructor(config) {
|
|
1996
|
+
this.client = config.client;
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
* List all schemas
|
|
2000
|
+
*
|
|
2001
|
+
* @example
|
|
2002
|
+
* ```typescript
|
|
2003
|
+
* const { data } = await baasix.schemas.find();
|
|
2004
|
+
* console.log(data.map(s => s.collectionName));
|
|
2005
|
+
* ```
|
|
2006
|
+
*/
|
|
2007
|
+
async find() {
|
|
2008
|
+
return this.client.get("/schemas");
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Get schema for a specific collection
|
|
2012
|
+
*
|
|
2013
|
+
* @example
|
|
2014
|
+
* ```typescript
|
|
2015
|
+
* const schema = await baasix.schemas.findOne('products');
|
|
2016
|
+
* console.log(schema.fields);
|
|
2017
|
+
* ```
|
|
2018
|
+
*/
|
|
2019
|
+
async findOne(collection) {
|
|
2020
|
+
const response = await this.client.get(
|
|
2021
|
+
`/schemas/${collection}`
|
|
2022
|
+
);
|
|
2023
|
+
return response.data;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Create a new collection/schema
|
|
2027
|
+
*
|
|
2028
|
+
* @example
|
|
2029
|
+
* ```typescript
|
|
2030
|
+
* await baasix.schemas.create({
|
|
2031
|
+
* collectionName: 'orders',
|
|
2032
|
+
* schema: {
|
|
2033
|
+
* name: 'Order',
|
|
2034
|
+
* timestamps: true,
|
|
2035
|
+
* paranoid: true,
|
|
2036
|
+
* fields: {
|
|
2037
|
+
* id: {
|
|
2038
|
+
* type: 'UUID',
|
|
2039
|
+
* primaryKey: true,
|
|
2040
|
+
* defaultValue: { type: 'UUIDV4' }
|
|
2041
|
+
* },
|
|
2042
|
+
* orderNumber: {
|
|
2043
|
+
* type: 'String',
|
|
2044
|
+
* allowNull: false,
|
|
2045
|
+
* unique: true
|
|
2046
|
+
* },
|
|
2047
|
+
* total: {
|
|
2048
|
+
* type: 'Decimal',
|
|
2049
|
+
* values: { precision: 10, scale: 2 },
|
|
2050
|
+
* allowNull: false,
|
|
2051
|
+
* defaultValue: 0
|
|
2052
|
+
* },
|
|
2053
|
+
* status: {
|
|
2054
|
+
* type: 'String',
|
|
2055
|
+
* allowNull: false,
|
|
2056
|
+
* defaultValue: 'pending'
|
|
2057
|
+
* },
|
|
2058
|
+
* items: {
|
|
2059
|
+
* type: 'JSONB',
|
|
2060
|
+
* allowNull: true,
|
|
2061
|
+
* defaultValue: []
|
|
2062
|
+
* }
|
|
2063
|
+
* }
|
|
2064
|
+
* }
|
|
2065
|
+
* });
|
|
2066
|
+
* ```
|
|
2067
|
+
*/
|
|
2068
|
+
async create(data) {
|
|
2069
|
+
const response = await this.client.post(
|
|
2070
|
+
"/schemas",
|
|
2071
|
+
data
|
|
2072
|
+
);
|
|
2073
|
+
return response.data;
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Update an existing schema
|
|
2077
|
+
*
|
|
2078
|
+
* @example
|
|
2079
|
+
* ```typescript
|
|
2080
|
+
* await baasix.schemas.update('products', {
|
|
2081
|
+
* schema: {
|
|
2082
|
+
* name: 'Product',
|
|
2083
|
+
* timestamps: true,
|
|
2084
|
+
* fields: {
|
|
2085
|
+
* // Updated fields
|
|
2086
|
+
* description: { type: 'Text', allowNull: true }
|
|
2087
|
+
* }
|
|
2088
|
+
* }
|
|
2089
|
+
* });
|
|
2090
|
+
* ```
|
|
2091
|
+
*/
|
|
2092
|
+
async update(collection, data) {
|
|
2093
|
+
const response = await this.client.patch(
|
|
2094
|
+
`/schemas/${collection}`,
|
|
2095
|
+
data
|
|
2096
|
+
);
|
|
2097
|
+
return response.data;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Delete a schema (drops the table)
|
|
2101
|
+
*
|
|
2102
|
+
* @example
|
|
2103
|
+
* ```typescript
|
|
2104
|
+
* await baasix.schemas.delete('old_collection');
|
|
2105
|
+
* ```
|
|
2106
|
+
*/
|
|
2107
|
+
async delete(collection) {
|
|
2108
|
+
await this.client.delete(`/schemas/${collection}`);
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Create a relationship between collections
|
|
2112
|
+
*
|
|
2113
|
+
* @example
|
|
2114
|
+
* ```typescript
|
|
2115
|
+
* // Many-to-One (BelongsTo)
|
|
2116
|
+
* await baasix.schemas.createRelationship('posts', {
|
|
2117
|
+
* type: 'M2O',
|
|
2118
|
+
* target: 'baasix_User',
|
|
2119
|
+
* name: 'author',
|
|
2120
|
+
* alias: 'posts'
|
|
2121
|
+
* });
|
|
2122
|
+
*
|
|
2123
|
+
* // Many-to-Many
|
|
2124
|
+
* await baasix.schemas.createRelationship('posts', {
|
|
2125
|
+
* type: 'M2M',
|
|
2126
|
+
* target: 'tags',
|
|
2127
|
+
* name: 'tags',
|
|
2128
|
+
* alias: 'posts'
|
|
2129
|
+
* });
|
|
2130
|
+
* ```
|
|
2131
|
+
*/
|
|
2132
|
+
async createRelationship(collection, relationship) {
|
|
2133
|
+
await this.client.post(
|
|
2134
|
+
`/schemas/${collection}/relationships`,
|
|
2135
|
+
relationship
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Delete a relationship
|
|
2140
|
+
*
|
|
2141
|
+
* @example
|
|
2142
|
+
* ```typescript
|
|
2143
|
+
* await baasix.schemas.deleteRelationship('posts', 'author');
|
|
2144
|
+
* ```
|
|
2145
|
+
*/
|
|
2146
|
+
async deleteRelationship(collection, relationshipName) {
|
|
2147
|
+
await this.client.delete(
|
|
2148
|
+
`/schemas/${collection}/relationships/${relationshipName}`
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Create an index on a collection
|
|
2153
|
+
*
|
|
2154
|
+
* @example
|
|
2155
|
+
* ```typescript
|
|
2156
|
+
* // Unique index
|
|
2157
|
+
* await baasix.schemas.createIndex('users', {
|
|
2158
|
+
* name: 'idx_users_email',
|
|
2159
|
+
* fields: ['email'],
|
|
2160
|
+
* unique: true
|
|
2161
|
+
* });
|
|
2162
|
+
*
|
|
2163
|
+
* // Composite index
|
|
2164
|
+
* await baasix.schemas.createIndex('orders', {
|
|
2165
|
+
* name: 'idx_orders_status_created',
|
|
2166
|
+
* fields: ['status', 'createdAt']
|
|
2167
|
+
* });
|
|
2168
|
+
* ```
|
|
2169
|
+
*/
|
|
2170
|
+
async createIndex(collection, index) {
|
|
2171
|
+
await this.client.post(`/schemas/${collection}/indexes`, index);
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Delete an index
|
|
2175
|
+
*
|
|
2176
|
+
* @example
|
|
2177
|
+
* ```typescript
|
|
2178
|
+
* await baasix.schemas.deleteIndex('users', 'idx_users_email');
|
|
2179
|
+
* ```
|
|
2180
|
+
*/
|
|
2181
|
+
async deleteIndex(collection, indexName) {
|
|
2182
|
+
await this.client.delete(`/schemas/${collection}/indexes/${indexName}`);
|
|
2183
|
+
}
|
|
2184
|
+
/**
|
|
2185
|
+
* Add a field to an existing schema
|
|
2186
|
+
*
|
|
2187
|
+
* @example
|
|
2188
|
+
* ```typescript
|
|
2189
|
+
* await baasix.schemas.addField('products', 'rating', {
|
|
2190
|
+
* type: 'Decimal',
|
|
2191
|
+
* values: { precision: 3, scale: 2 },
|
|
2192
|
+
* allowNull: true,
|
|
2193
|
+
* defaultValue: 0
|
|
2194
|
+
* });
|
|
2195
|
+
* ```
|
|
2196
|
+
*/
|
|
2197
|
+
async addField(collection, fieldName, fieldDefinition) {
|
|
2198
|
+
const currentSchema = await this.findOne(collection);
|
|
2199
|
+
const updatedFields = {
|
|
2200
|
+
...currentSchema.schema.fields,
|
|
2201
|
+
[fieldName]: fieldDefinition
|
|
2202
|
+
};
|
|
2203
|
+
return this.update(collection, {
|
|
2204
|
+
schema: {
|
|
2205
|
+
...currentSchema.schema,
|
|
2206
|
+
fields: updatedFields
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Remove a field from a schema
|
|
2212
|
+
*
|
|
2213
|
+
* @example
|
|
2214
|
+
* ```typescript
|
|
2215
|
+
* await baasix.schemas.removeField('products', 'deprecated_field');
|
|
2216
|
+
* ```
|
|
2217
|
+
*/
|
|
2218
|
+
async removeField(collection, fieldName) {
|
|
2219
|
+
const currentSchema = await this.findOne(collection);
|
|
2220
|
+
const { [fieldName]: _, ...remainingFields } = currentSchema.schema.fields;
|
|
2221
|
+
return this.update(collection, {
|
|
2222
|
+
schema: {
|
|
2223
|
+
...currentSchema.schema,
|
|
2224
|
+
fields: remainingFields
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Export all schemas as JSON
|
|
2230
|
+
*
|
|
2231
|
+
* @example
|
|
2232
|
+
* ```typescript
|
|
2233
|
+
* const schemas = await baasix.schemas.export();
|
|
2234
|
+
* // Save to file for backup
|
|
2235
|
+
* ```
|
|
2236
|
+
*/
|
|
2237
|
+
async export() {
|
|
2238
|
+
const response = await this.client.get(
|
|
2239
|
+
"/schemas/export"
|
|
2240
|
+
);
|
|
2241
|
+
return response.data;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Import schemas from JSON
|
|
2245
|
+
*
|
|
2246
|
+
* @example
|
|
2247
|
+
* ```typescript
|
|
2248
|
+
* await baasix.schemas.import(savedSchemas);
|
|
2249
|
+
* ```
|
|
2250
|
+
*/
|
|
2251
|
+
async import(schemas) {
|
|
2252
|
+
await this.client.post("/schemas/import", { schemas });
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
|
|
2256
|
+
// src/modules/notifications.ts
|
|
2257
|
+
var NotificationsModule = class {
|
|
2258
|
+
client;
|
|
2259
|
+
constructor(config) {
|
|
2260
|
+
this.client = config.client;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* List notifications for the current user
|
|
2264
|
+
*
|
|
2265
|
+
* @example
|
|
2266
|
+
* ```typescript
|
|
2267
|
+
* const { data } = await baasix.notifications.find({
|
|
2268
|
+
* limit: 20,
|
|
2269
|
+
* filter: { seen: { eq: false } }
|
|
2270
|
+
* });
|
|
2271
|
+
* ```
|
|
2272
|
+
*/
|
|
2273
|
+
async find(params) {
|
|
2274
|
+
return this.client.get("/notifications", {
|
|
2275
|
+
params
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Get a single notification by ID
|
|
2280
|
+
*/
|
|
2281
|
+
async findOne(id) {
|
|
2282
|
+
const response = await this.client.get(
|
|
2283
|
+
`/notifications/${id}`
|
|
2284
|
+
);
|
|
2285
|
+
return response.data;
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Mark a notification as seen
|
|
2289
|
+
*
|
|
2290
|
+
* @example
|
|
2291
|
+
* ```typescript
|
|
2292
|
+
* await baasix.notifications.markAsSeen('notification-uuid');
|
|
2293
|
+
* ```
|
|
2294
|
+
*/
|
|
2295
|
+
async markAsSeen(id) {
|
|
2296
|
+
await this.client.patch(`/notifications/${id}/seen`, { seen: true });
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Mark multiple notifications as seen
|
|
2300
|
+
*
|
|
2301
|
+
* @example
|
|
2302
|
+
* ```typescript
|
|
2303
|
+
* await baasix.notifications.markManySeen(['id1', 'id2', 'id3']);
|
|
2304
|
+
* ```
|
|
2305
|
+
*/
|
|
2306
|
+
async markManySeen(ids) {
|
|
2307
|
+
await Promise.all(ids.map((id) => this.markAsSeen(id)));
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Mark all notifications as seen
|
|
2311
|
+
*
|
|
2312
|
+
* @example
|
|
2313
|
+
* ```typescript
|
|
2314
|
+
* await baasix.notifications.markAllSeen();
|
|
2315
|
+
* ```
|
|
2316
|
+
*/
|
|
2317
|
+
async markAllSeen() {
|
|
2318
|
+
await this.client.post("/notifications/seen-all");
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Get unread notification count
|
|
2322
|
+
*
|
|
2323
|
+
* @example
|
|
2324
|
+
* ```typescript
|
|
2325
|
+
* const count = await baasix.notifications.getUnreadCount();
|
|
2326
|
+
* ```
|
|
2327
|
+
*/
|
|
2328
|
+
async getUnreadCount() {
|
|
2329
|
+
const response = await this.client.get(
|
|
2330
|
+
"/notifications/unread-count"
|
|
2331
|
+
);
|
|
2332
|
+
return response.data.count;
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Delete a notification
|
|
2336
|
+
*
|
|
2337
|
+
* @example
|
|
2338
|
+
* ```typescript
|
|
2339
|
+
* await baasix.notifications.delete('notification-uuid');
|
|
2340
|
+
* ```
|
|
2341
|
+
*/
|
|
2342
|
+
async delete(id) {
|
|
2343
|
+
await this.client.delete(`/notifications/${id}`);
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Send a notification to users (requires admin permissions)
|
|
2347
|
+
*
|
|
2348
|
+
* @example
|
|
2349
|
+
* ```typescript
|
|
2350
|
+
* await baasix.notifications.send({
|
|
2351
|
+
* type: 'alert',
|
|
2352
|
+
* title: 'System Update',
|
|
2353
|
+
* message: 'The system will be down for maintenance',
|
|
2354
|
+
* data: { link: '/updates' },
|
|
2355
|
+
* userIds: ['user1-uuid', 'user2-uuid']
|
|
2356
|
+
* });
|
|
2357
|
+
* ```
|
|
2358
|
+
*/
|
|
2359
|
+
async send(data) {
|
|
2360
|
+
await this.client.post("/notifications/send", data);
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
|
|
2364
|
+
// src/modules/permissions.ts
|
|
2365
|
+
var PermissionsModule = class {
|
|
2366
|
+
client;
|
|
2367
|
+
constructor(config) {
|
|
2368
|
+
this.client = config.client;
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* List all permissions
|
|
2372
|
+
*
|
|
2373
|
+
* @example
|
|
2374
|
+
* ```typescript
|
|
2375
|
+
* const { data } = await baasix.permissions.find();
|
|
2376
|
+
* ```
|
|
2377
|
+
*/
|
|
2378
|
+
async find(params) {
|
|
2379
|
+
return this.client.get("/permissions", {
|
|
2380
|
+
params
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Get a permission by ID
|
|
2385
|
+
*/
|
|
2386
|
+
async findOne(id) {
|
|
2387
|
+
const response = await this.client.get(
|
|
2388
|
+
`/permissions/${id}`
|
|
2389
|
+
);
|
|
2390
|
+
return response.data;
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Get permissions for a specific role
|
|
2394
|
+
*
|
|
2395
|
+
* @example
|
|
2396
|
+
* ```typescript
|
|
2397
|
+
* const { data } = await baasix.permissions.findByRole('role-uuid');
|
|
2398
|
+
* ```
|
|
2399
|
+
*/
|
|
2400
|
+
async findByRole(roleId) {
|
|
2401
|
+
return this.client.get("/permissions", {
|
|
2402
|
+
params: {
|
|
2403
|
+
filter: { role_Id: { eq: roleId } },
|
|
2404
|
+
limit: -1
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Get permissions for a specific collection
|
|
2410
|
+
*
|
|
2411
|
+
* @example
|
|
2412
|
+
* ```typescript
|
|
2413
|
+
* const { data } = await baasix.permissions.findByCollection('products');
|
|
2414
|
+
* ```
|
|
2415
|
+
*/
|
|
2416
|
+
async findByCollection(collection) {
|
|
2417
|
+
return this.client.get("/permissions", {
|
|
2418
|
+
params: {
|
|
2419
|
+
filter: { collection: { eq: collection } },
|
|
2420
|
+
limit: -1
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Create a new permission
|
|
2426
|
+
*
|
|
2427
|
+
* @example
|
|
2428
|
+
* ```typescript
|
|
2429
|
+
* await baasix.permissions.create({
|
|
2430
|
+
* role_Id: 'editor-role-uuid',
|
|
2431
|
+
* collection: 'posts',
|
|
2432
|
+
* action: 'update',
|
|
2433
|
+
* fields: ['title', 'content', 'status'],
|
|
2434
|
+
* conditions: {
|
|
2435
|
+
* author_Id: { eq: '$CURRENT_USER' }
|
|
2436
|
+
* }
|
|
2437
|
+
* });
|
|
2438
|
+
* ```
|
|
2439
|
+
*/
|
|
2440
|
+
async create(data) {
|
|
2441
|
+
const response = await this.client.post(
|
|
2442
|
+
"/permissions",
|
|
2443
|
+
data
|
|
2444
|
+
);
|
|
2445
|
+
return response.data;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Update a permission
|
|
2449
|
+
*
|
|
2450
|
+
* @example
|
|
2451
|
+
* ```typescript
|
|
2452
|
+
* await baasix.permissions.update('permission-uuid', {
|
|
2453
|
+
* fields: ['*'],
|
|
2454
|
+
* conditions: null
|
|
2455
|
+
* });
|
|
2456
|
+
* ```
|
|
2457
|
+
*/
|
|
2458
|
+
async update(id, data) {
|
|
2459
|
+
const response = await this.client.patch(
|
|
2460
|
+
`/permissions/${id}`,
|
|
2461
|
+
data
|
|
2462
|
+
);
|
|
2463
|
+
return response.data;
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Delete a permission
|
|
2467
|
+
*
|
|
2468
|
+
* @example
|
|
2469
|
+
* ```typescript
|
|
2470
|
+
* await baasix.permissions.delete('permission-uuid');
|
|
2471
|
+
* ```
|
|
2472
|
+
*/
|
|
2473
|
+
async delete(id) {
|
|
2474
|
+
await this.client.delete(`/permissions/${id}`);
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* List all roles
|
|
2478
|
+
*
|
|
2479
|
+
* @example
|
|
2480
|
+
* ```typescript
|
|
2481
|
+
* const { data } = await baasix.permissions.getRoles();
|
|
2482
|
+
* ```
|
|
2483
|
+
*/
|
|
2484
|
+
async getRoles() {
|
|
2485
|
+
return this.client.get("/items/baasix_Role", {
|
|
2486
|
+
params: { limit: -1 }
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* Create CRUD permissions for a collection
|
|
2491
|
+
*
|
|
2492
|
+
* @example
|
|
2493
|
+
* ```typescript
|
|
2494
|
+
* await baasix.permissions.createCrudPermissions('role-uuid', 'products', {
|
|
2495
|
+
* create: { fields: ['name', 'price', 'description'] },
|
|
2496
|
+
* read: { fields: ['*'] },
|
|
2497
|
+
* update: { fields: ['name', 'price', 'description'] },
|
|
2498
|
+
* delete: false
|
|
2499
|
+
* });
|
|
2500
|
+
* ```
|
|
2501
|
+
*/
|
|
2502
|
+
async createCrudPermissions(roleId, collection, config) {
|
|
2503
|
+
const permissions = [];
|
|
2504
|
+
const actions = ["create", "read", "update", "delete"];
|
|
2505
|
+
for (const action of actions) {
|
|
2506
|
+
const actionConfig = config[action];
|
|
2507
|
+
if (actionConfig === false) continue;
|
|
2508
|
+
const permission = await this.create({
|
|
2509
|
+
role_Id: roleId,
|
|
2510
|
+
collection,
|
|
2511
|
+
action,
|
|
2512
|
+
fields: actionConfig?.fields || ["*"],
|
|
2513
|
+
conditions: actionConfig?.conditions
|
|
2514
|
+
});
|
|
2515
|
+
permissions.push(permission);
|
|
2516
|
+
}
|
|
2517
|
+
return permissions;
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Reload permissions cache (admin only)
|
|
2521
|
+
*
|
|
2522
|
+
* @example
|
|
2523
|
+
* ```typescript
|
|
2524
|
+
* await baasix.permissions.reloadCache();
|
|
2525
|
+
* ```
|
|
2526
|
+
*/
|
|
2527
|
+
async reloadCache() {
|
|
2528
|
+
await this.client.post("/permissions/reload");
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
// src/modules/settings.ts
|
|
2533
|
+
var SettingsModule = class {
|
|
2534
|
+
client;
|
|
2535
|
+
constructor(config) {
|
|
2536
|
+
this.client = config.client;
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Get all settings
|
|
2540
|
+
*
|
|
2541
|
+
* @example
|
|
2542
|
+
* ```typescript
|
|
2543
|
+
* const settings = await baasix.settings.get();
|
|
2544
|
+
* console.log(settings.appName);
|
|
2545
|
+
* ```
|
|
2546
|
+
*/
|
|
2547
|
+
async get() {
|
|
2548
|
+
const response = await this.client.get("/settings");
|
|
2549
|
+
return response.data;
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Get a specific setting by key
|
|
2553
|
+
*
|
|
2554
|
+
* @example
|
|
2555
|
+
* ```typescript
|
|
2556
|
+
* const appName = await baasix.settings.getKey('appName');
|
|
2557
|
+
* ```
|
|
2558
|
+
*/
|
|
2559
|
+
async getKey(key) {
|
|
2560
|
+
const settings = await this.get();
|
|
2561
|
+
return settings[key] ?? null;
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Update settings
|
|
2565
|
+
*
|
|
2566
|
+
* @example
|
|
2567
|
+
* ```typescript
|
|
2568
|
+
* await baasix.settings.update({
|
|
2569
|
+
* appName: 'Updated App Name',
|
|
2570
|
+
* logo: 'file-uuid',
|
|
2571
|
+
* customConfig: {
|
|
2572
|
+
* feature1: true,
|
|
2573
|
+
* feature2: false
|
|
2574
|
+
* }
|
|
2575
|
+
* });
|
|
2576
|
+
* ```
|
|
2577
|
+
*/
|
|
2578
|
+
async update(settings) {
|
|
2579
|
+
const response = await this.client.patch(
|
|
2580
|
+
"/settings",
|
|
2581
|
+
settings
|
|
2582
|
+
);
|
|
2583
|
+
return response.data;
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Set a specific setting
|
|
2587
|
+
*
|
|
2588
|
+
* @example
|
|
2589
|
+
* ```typescript
|
|
2590
|
+
* await baasix.settings.set('appName', 'My New App Name');
|
|
2591
|
+
* ```
|
|
2592
|
+
*/
|
|
2593
|
+
async set(key, value) {
|
|
2594
|
+
return this.update({ [key]: value });
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
|
|
2598
|
+
// src/modules/reports.ts
|
|
2599
|
+
var ReportsModule = class {
|
|
2600
|
+
client;
|
|
2601
|
+
constructor(config) {
|
|
2602
|
+
this.client = config.client;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Generate a report for a collection
|
|
2606
|
+
*
|
|
2607
|
+
* @example
|
|
2608
|
+
* ```typescript
|
|
2609
|
+
* // Sales by month
|
|
2610
|
+
* const report = await baasix.reports.generate('orders', {
|
|
2611
|
+
* aggregate: {
|
|
2612
|
+
* revenue: { function: 'sum', field: 'total' },
|
|
2613
|
+
* orders: { function: 'count', field: 'id' }
|
|
2614
|
+
* },
|
|
2615
|
+
* groupBy: 'month',
|
|
2616
|
+
* dateRange: {
|
|
2617
|
+
* start: '2025-01-01',
|
|
2618
|
+
* end: '2025-12-31'
|
|
2619
|
+
* }
|
|
2620
|
+
* });
|
|
2621
|
+
* ```
|
|
2622
|
+
*/
|
|
2623
|
+
async generate(collection, config) {
|
|
2624
|
+
const response = await this.client.post(
|
|
2625
|
+
`/reports/${collection}`,
|
|
2626
|
+
config
|
|
2627
|
+
);
|
|
2628
|
+
return response;
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Get collection statistics
|
|
2632
|
+
*
|
|
2633
|
+
* @example
|
|
2634
|
+
* ```typescript
|
|
2635
|
+
* const stats = await baasix.reports.getStats('products');
|
|
2636
|
+
* console.log(stats.totalCount, stats.recentCount);
|
|
2637
|
+
* ```
|
|
2638
|
+
*/
|
|
2639
|
+
async getStats(collection, options) {
|
|
2640
|
+
const response = await this.client.get(`/reports/${collection}/stats`, {
|
|
2641
|
+
params: options
|
|
2642
|
+
});
|
|
2643
|
+
return response.data;
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Generate an aggregation query
|
|
2647
|
+
*
|
|
2648
|
+
* @example
|
|
2649
|
+
* ```typescript
|
|
2650
|
+
* const results = await baasix.reports.aggregate('orders', {
|
|
2651
|
+
* aggregate: {
|
|
2652
|
+
* total: { function: 'sum', field: 'amount' },
|
|
2653
|
+
* count: { function: 'count', field: 'id' },
|
|
2654
|
+
* min: { function: 'min', field: 'amount' },
|
|
2655
|
+
* max: { function: 'max', field: 'amount' },
|
|
2656
|
+
* avg: { function: 'avg', field: 'amount' }
|
|
2657
|
+
* },
|
|
2658
|
+
* groupBy: ['status', 'paymentMethod'],
|
|
2659
|
+
* filter: { createdAt: { gte: '$NOW-DAYS_30' } }
|
|
2660
|
+
* });
|
|
2661
|
+
* ```
|
|
2662
|
+
*/
|
|
2663
|
+
async aggregate(collection, options) {
|
|
2664
|
+
const response = await this.client.get(
|
|
2665
|
+
`/items/${collection}`,
|
|
2666
|
+
{
|
|
2667
|
+
params: {
|
|
2668
|
+
aggregate: options.aggregate,
|
|
2669
|
+
groupBy: options.groupBy,
|
|
2670
|
+
filter: options.filter,
|
|
2671
|
+
limit: -1
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
);
|
|
2675
|
+
return response.data;
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Count items matching a filter
|
|
2679
|
+
*
|
|
2680
|
+
* @example
|
|
2681
|
+
* ```typescript
|
|
2682
|
+
* const activeUsers = await baasix.reports.count('users', {
|
|
2683
|
+
* status: { eq: 'active' }
|
|
2684
|
+
* });
|
|
2685
|
+
* ```
|
|
2686
|
+
*/
|
|
2687
|
+
async count(collection, filter) {
|
|
2688
|
+
const response = await this.client.get(
|
|
2689
|
+
`/items/${collection}`,
|
|
2690
|
+
{
|
|
2691
|
+
params: {
|
|
2692
|
+
filter,
|
|
2693
|
+
limit: 0
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
);
|
|
2697
|
+
return response.totalCount || 0;
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* Get distinct values for a field
|
|
2701
|
+
*
|
|
2702
|
+
* @example
|
|
2703
|
+
* ```typescript
|
|
2704
|
+
* const categories = await baasix.reports.distinct('products', 'category');
|
|
2705
|
+
* ```
|
|
2706
|
+
*/
|
|
2707
|
+
async distinct(collection, field, filter) {
|
|
2708
|
+
const response = await this.client.get(
|
|
2709
|
+
`/items/${collection}`,
|
|
2710
|
+
{
|
|
2711
|
+
params: {
|
|
2712
|
+
filter,
|
|
2713
|
+
fields: [field],
|
|
2714
|
+
groupBy: [field],
|
|
2715
|
+
limit: -1
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
);
|
|
2719
|
+
return response.data.map((item) => item[field]);
|
|
2720
|
+
}
|
|
2721
|
+
};
|
|
2722
|
+
|
|
2723
|
+
// src/modules/workflows.ts
|
|
2724
|
+
var WorkflowsModule = class {
|
|
2725
|
+
client;
|
|
2726
|
+
constructor(config) {
|
|
2727
|
+
this.client = config.client;
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* List all workflows
|
|
2731
|
+
*
|
|
2732
|
+
* @example
|
|
2733
|
+
* ```typescript
|
|
2734
|
+
* const { data } = await baasix.workflows.find();
|
|
2735
|
+
* ```
|
|
2736
|
+
*/
|
|
2737
|
+
async find(params) {
|
|
2738
|
+
return this.client.get("/workflows", {
|
|
2739
|
+
params
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Get a workflow by ID
|
|
2744
|
+
*
|
|
2745
|
+
* @example
|
|
2746
|
+
* ```typescript
|
|
2747
|
+
* const workflow = await baasix.workflows.findOne('workflow-uuid');
|
|
2748
|
+
* ```
|
|
2749
|
+
*/
|
|
2750
|
+
async findOne(id) {
|
|
2751
|
+
const response = await this.client.get(
|
|
2752
|
+
`/workflows/${id}`
|
|
2753
|
+
);
|
|
2754
|
+
return response.data;
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Create a new workflow
|
|
2758
|
+
*
|
|
2759
|
+
* @example
|
|
2760
|
+
* ```typescript
|
|
2761
|
+
* const workflow = await baasix.workflows.create({
|
|
2762
|
+
* name: 'Order Processing',
|
|
2763
|
+
* description: 'Process new orders',
|
|
2764
|
+
* trigger: {
|
|
2765
|
+
* type: 'hook',
|
|
2766
|
+
* config: {
|
|
2767
|
+
* collection: 'orders',
|
|
2768
|
+
* event: 'items.create.after'
|
|
2769
|
+
* }
|
|
2770
|
+
* },
|
|
2771
|
+
* nodes: [...],
|
|
2772
|
+
* edges: [...],
|
|
2773
|
+
* isActive: true
|
|
2774
|
+
* });
|
|
2775
|
+
* ```
|
|
2776
|
+
*/
|
|
2777
|
+
async create(data) {
|
|
2778
|
+
const response = await this.client.post(
|
|
2779
|
+
"/workflows",
|
|
2780
|
+
data
|
|
2781
|
+
);
|
|
2782
|
+
return response.data;
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* Update a workflow
|
|
2786
|
+
*
|
|
2787
|
+
* @example
|
|
2788
|
+
* ```typescript
|
|
2789
|
+
* await baasix.workflows.update('workflow-uuid', {
|
|
2790
|
+
* name: 'Updated Workflow Name',
|
|
2791
|
+
* isActive: false
|
|
2792
|
+
* });
|
|
2793
|
+
* ```
|
|
2794
|
+
*/
|
|
2795
|
+
async update(id, data) {
|
|
2796
|
+
const response = await this.client.patch(
|
|
2797
|
+
`/workflows/${id}`,
|
|
2798
|
+
data
|
|
2799
|
+
);
|
|
2800
|
+
return response.data;
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* Delete a workflow
|
|
2804
|
+
*
|
|
2805
|
+
* @example
|
|
2806
|
+
* ```typescript
|
|
2807
|
+
* await baasix.workflows.delete('workflow-uuid');
|
|
2808
|
+
* ```
|
|
2809
|
+
*/
|
|
2810
|
+
async delete(id) {
|
|
2811
|
+
await this.client.delete(`/workflows/${id}`);
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Execute a workflow manually
|
|
2815
|
+
*
|
|
2816
|
+
* @example
|
|
2817
|
+
* ```typescript
|
|
2818
|
+
* const result = await baasix.workflows.execute('workflow-uuid', {
|
|
2819
|
+
* // Trigger data
|
|
2820
|
+
* customerId: 'customer-123',
|
|
2821
|
+
* action: 'sendEmail'
|
|
2822
|
+
* });
|
|
2823
|
+
* ```
|
|
2824
|
+
*/
|
|
2825
|
+
async execute(id, triggerData) {
|
|
2826
|
+
const response = await this.client.post(
|
|
2827
|
+
`/workflows/${id}/execute`,
|
|
2828
|
+
{ triggerData }
|
|
2829
|
+
);
|
|
2830
|
+
return response.data;
|
|
2831
|
+
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Test a workflow without persisting execution
|
|
2834
|
+
*
|
|
2835
|
+
* @example
|
|
2836
|
+
* ```typescript
|
|
2837
|
+
* const result = await baasix.workflows.test('workflow-uuid', {
|
|
2838
|
+
* testData: { value: 123 }
|
|
2839
|
+
* });
|
|
2840
|
+
* ```
|
|
2841
|
+
*/
|
|
2842
|
+
async test(id, triggerData) {
|
|
2843
|
+
const response = await this.client.post(
|
|
2844
|
+
`/workflows/${id}/test`,
|
|
2845
|
+
{ triggerData }
|
|
2846
|
+
);
|
|
2847
|
+
return response.data;
|
|
2848
|
+
}
|
|
2849
|
+
/**
|
|
2850
|
+
* Get workflow execution history
|
|
2851
|
+
*
|
|
2852
|
+
* @example
|
|
2853
|
+
* ```typescript
|
|
2854
|
+
* const { data } = await baasix.workflows.getExecutions('workflow-uuid', {
|
|
2855
|
+
* limit: 50
|
|
2856
|
+
* });
|
|
2857
|
+
* ```
|
|
2858
|
+
*/
|
|
2859
|
+
async getExecutions(id, params) {
|
|
2860
|
+
return this.client.get(
|
|
2861
|
+
`/workflows/${id}/executions`,
|
|
2862
|
+
{ params }
|
|
2863
|
+
);
|
|
2864
|
+
}
|
|
2865
|
+
/**
|
|
2866
|
+
* Get a specific execution
|
|
2867
|
+
*
|
|
2868
|
+
* @example
|
|
2869
|
+
* ```typescript
|
|
2870
|
+
* const execution = await baasix.workflows.getExecution(
|
|
2871
|
+
* 'workflow-uuid',
|
|
2872
|
+
* 'execution-uuid'
|
|
2873
|
+
* );
|
|
2874
|
+
* ```
|
|
2875
|
+
*/
|
|
2876
|
+
async getExecution(workflowId, executionId) {
|
|
2877
|
+
const response = await this.client.get(
|
|
2878
|
+
`/workflows/${workflowId}/executions/${executionId}`
|
|
2879
|
+
);
|
|
2880
|
+
return response.data;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Cancel a running execution
|
|
2884
|
+
*
|
|
2885
|
+
* @example
|
|
2886
|
+
* ```typescript
|
|
2887
|
+
* await baasix.workflows.cancelExecution('workflow-uuid', 'execution-uuid');
|
|
2888
|
+
* ```
|
|
2889
|
+
*/
|
|
2890
|
+
async cancelExecution(workflowId, executionId) {
|
|
2891
|
+
await this.client.post(
|
|
2892
|
+
`/workflows/${workflowId}/executions/${executionId}/cancel`
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Enable a workflow
|
|
2897
|
+
*
|
|
2898
|
+
* @example
|
|
2899
|
+
* ```typescript
|
|
2900
|
+
* await baasix.workflows.enable('workflow-uuid');
|
|
2901
|
+
* ```
|
|
2902
|
+
*/
|
|
2903
|
+
async enable(id) {
|
|
2904
|
+
return this.update(id, { isActive: true });
|
|
2905
|
+
}
|
|
2906
|
+
/**
|
|
2907
|
+
* Disable a workflow
|
|
2908
|
+
*
|
|
2909
|
+
* @example
|
|
2910
|
+
* ```typescript
|
|
2911
|
+
* await baasix.workflows.disable('workflow-uuid');
|
|
2912
|
+
* ```
|
|
2913
|
+
*/
|
|
2914
|
+
async disable(id) {
|
|
2915
|
+
return this.update(id, { isActive: false });
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Duplicate a workflow
|
|
2919
|
+
*
|
|
2920
|
+
* @example
|
|
2921
|
+
* ```typescript
|
|
2922
|
+
* const newWorkflow = await baasix.workflows.duplicate('workflow-uuid', {
|
|
2923
|
+
* name: 'Copy of My Workflow'
|
|
2924
|
+
* });
|
|
2925
|
+
* ```
|
|
2926
|
+
*/
|
|
2927
|
+
async duplicate(id, overrides) {
|
|
2928
|
+
const original = await this.findOne(id);
|
|
2929
|
+
const { id: _, createdAt, updatedAt, ...workflowData } = original;
|
|
2930
|
+
return this.create({
|
|
2931
|
+
...workflowData,
|
|
2932
|
+
name: `Copy of ${workflowData.name}`,
|
|
2933
|
+
...overrides
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
|
|
2938
|
+
// src/modules/realtime.ts
|
|
2939
|
+
var RealtimeModule = class {
|
|
2940
|
+
client;
|
|
2941
|
+
storage;
|
|
2942
|
+
socket = null;
|
|
2943
|
+
socketClient = null;
|
|
2944
|
+
socketUrl;
|
|
2945
|
+
socketPath;
|
|
2946
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
2947
|
+
workflowCallbacks = /* @__PURE__ */ new Map();
|
|
2948
|
+
connectionCallbacks = /* @__PURE__ */ new Set();
|
|
2949
|
+
reconnecting = false;
|
|
2950
|
+
connectionPromise = null;
|
|
2951
|
+
constructor(config) {
|
|
2952
|
+
this.client = config.client;
|
|
2953
|
+
this.storage = config.storage;
|
|
2954
|
+
this.socketUrl = config.socketUrl || "";
|
|
2955
|
+
this.socketPath = config.socketPath || "/socket";
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Set the socket.io client instance
|
|
2959
|
+
* This allows the SDK to work without bundling socket.io-client
|
|
2960
|
+
*
|
|
2961
|
+
* @example
|
|
2962
|
+
* ```typescript
|
|
2963
|
+
* import { io } from 'socket.io-client';
|
|
2964
|
+
* baasix.realtime.setSocketClient(io);
|
|
2965
|
+
* ```
|
|
2966
|
+
*/
|
|
2967
|
+
setSocketClient(socketIO) {
|
|
2968
|
+
this.socketClient = socketIO;
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Set the WebSocket server URL
|
|
2972
|
+
* By default, uses the same URL as the API
|
|
2973
|
+
*/
|
|
2974
|
+
setSocketUrl(url) {
|
|
2975
|
+
this.socketUrl = url;
|
|
2976
|
+
}
|
|
2977
|
+
/**
|
|
2978
|
+
* Check if socket.io client is available
|
|
2979
|
+
*/
|
|
2980
|
+
ensureSocketClient() {
|
|
2981
|
+
if (!this.socketClient) {
|
|
2982
|
+
throw new Error(
|
|
2983
|
+
"Socket.io client not set. Please call baasix.realtime.setSocketClient(io) with socket.io-client."
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Get the authentication token for socket connection
|
|
2989
|
+
*/
|
|
2990
|
+
async getAuthToken() {
|
|
2991
|
+
return await this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Connect to the realtime server
|
|
2995
|
+
*
|
|
2996
|
+
* @example
|
|
2997
|
+
* ```typescript
|
|
2998
|
+
* await baasix.realtime.connect();
|
|
2999
|
+
* console.log('Connected to realtime server');
|
|
3000
|
+
* ```
|
|
3001
|
+
*/
|
|
3002
|
+
async connect() {
|
|
3003
|
+
if (this.connectionPromise) {
|
|
3004
|
+
return this.connectionPromise;
|
|
3005
|
+
}
|
|
3006
|
+
if (this.socket?.connected) {
|
|
3007
|
+
return Promise.resolve();
|
|
3008
|
+
}
|
|
3009
|
+
this.ensureSocketClient();
|
|
3010
|
+
this.connectionPromise = new Promise(async (resolve, reject) => {
|
|
3011
|
+
try {
|
|
3012
|
+
const token = await this.getAuthToken();
|
|
3013
|
+
const baseUrl = this.socketUrl || this.client.getBaseUrl();
|
|
3014
|
+
this.socket = this.socketClient(baseUrl, {
|
|
3015
|
+
auth: { token: token || void 0 },
|
|
3016
|
+
path: this.socketPath,
|
|
3017
|
+
transports: ["websocket", "polling"],
|
|
3018
|
+
reconnection: true,
|
|
3019
|
+
reconnectionAttempts: 10,
|
|
3020
|
+
reconnectionDelay: 1e3,
|
|
3021
|
+
timeout: 2e4
|
|
3022
|
+
});
|
|
3023
|
+
this.socket.on("connect", () => {
|
|
3024
|
+
console.log("[Baasix Realtime] Connected");
|
|
3025
|
+
this.reconnecting = false;
|
|
3026
|
+
this.notifyConnectionChange(true);
|
|
3027
|
+
resolve();
|
|
3028
|
+
});
|
|
3029
|
+
this.socket.on("disconnect", (reason) => {
|
|
3030
|
+
console.log("[Baasix Realtime] Disconnected:", reason);
|
|
3031
|
+
this.notifyConnectionChange(false);
|
|
3032
|
+
});
|
|
3033
|
+
this.socket.on("connect_error", (error) => {
|
|
3034
|
+
console.error("[Baasix Realtime] Connection error:", error.message);
|
|
3035
|
+
if (!this.reconnecting) {
|
|
3036
|
+
reject(error);
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
this.socket.on("reconnect", () => {
|
|
3040
|
+
console.log("[Baasix Realtime] Reconnected");
|
|
3041
|
+
this.reconnecting = false;
|
|
3042
|
+
this.resubscribeAll();
|
|
3043
|
+
this.notifyConnectionChange(true);
|
|
3044
|
+
});
|
|
3045
|
+
this.socket.on("reconnect_attempt", () => {
|
|
3046
|
+
this.reconnecting = true;
|
|
3047
|
+
});
|
|
3048
|
+
this.socket.on("connected", (data) => {
|
|
3049
|
+
console.log("[Baasix Realtime] Authenticated as user:", data.userId);
|
|
3050
|
+
});
|
|
3051
|
+
this.socket.on("workflow:execution:update", (data) => {
|
|
3052
|
+
this.handleWorkflowUpdate(data);
|
|
3053
|
+
});
|
|
3054
|
+
this.socket.on("workflow:execution:complete", (data) => {
|
|
3055
|
+
this.handleWorkflowUpdate({ ...data, status: "complete" });
|
|
3056
|
+
});
|
|
3057
|
+
this.socket.connect();
|
|
3058
|
+
} catch (error) {
|
|
3059
|
+
this.connectionPromise = null;
|
|
3060
|
+
reject(error);
|
|
3061
|
+
}
|
|
3062
|
+
});
|
|
3063
|
+
try {
|
|
3064
|
+
await this.connectionPromise;
|
|
3065
|
+
} finally {
|
|
3066
|
+
this.connectionPromise = null;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
/**
|
|
3070
|
+
* Disconnect from the realtime server
|
|
3071
|
+
*
|
|
3072
|
+
* @example
|
|
3073
|
+
* ```typescript
|
|
3074
|
+
* baasix.realtime.disconnect();
|
|
3075
|
+
* ```
|
|
3076
|
+
*/
|
|
3077
|
+
disconnect() {
|
|
3078
|
+
if (this.socket) {
|
|
3079
|
+
this.socket.disconnect();
|
|
3080
|
+
this.socket = null;
|
|
3081
|
+
}
|
|
3082
|
+
this.subscriptions.clear();
|
|
3083
|
+
this.workflowCallbacks.clear();
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Check if connected to the realtime server
|
|
3087
|
+
*/
|
|
3088
|
+
get isConnected() {
|
|
3089
|
+
return this.socket?.connected ?? false;
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Subscribe to connection state changes
|
|
3093
|
+
*
|
|
3094
|
+
* @example
|
|
3095
|
+
* ```typescript
|
|
3096
|
+
* const unsubscribe = baasix.realtime.onConnectionChange((connected) => {
|
|
3097
|
+
* console.log('Connection state:', connected ? 'online' : 'offline');
|
|
3098
|
+
* });
|
|
3099
|
+
* ```
|
|
3100
|
+
*/
|
|
3101
|
+
onConnectionChange(callback) {
|
|
3102
|
+
this.connectionCallbacks.add(callback);
|
|
3103
|
+
return () => {
|
|
3104
|
+
this.connectionCallbacks.delete(callback);
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
notifyConnectionChange(connected) {
|
|
3108
|
+
this.connectionCallbacks.forEach((callback) => callback(connected));
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3111
|
+
* Subscribe to a collection for real-time updates
|
|
3112
|
+
*
|
|
3113
|
+
* @example
|
|
3114
|
+
* ```typescript
|
|
3115
|
+
* // Subscribe to all changes
|
|
3116
|
+
* const unsubscribe = baasix.realtime.subscribe('products', (payload) => {
|
|
3117
|
+
* console.log(`${payload.action}:`, payload.data);
|
|
3118
|
+
* });
|
|
3119
|
+
*
|
|
3120
|
+
* // Subscribe to specific events
|
|
3121
|
+
* const unsubscribe = baasix.realtime.subscribe('orders', (payload) => {
|
|
3122
|
+
* if (payload.action === 'create') {
|
|
3123
|
+
* console.log('New order:', payload.data);
|
|
3124
|
+
* }
|
|
3125
|
+
* });
|
|
3126
|
+
*
|
|
3127
|
+
* // Unsubscribe when done
|
|
3128
|
+
* unsubscribe();
|
|
3129
|
+
* ```
|
|
3130
|
+
*/
|
|
3131
|
+
subscribe(collection, callback) {
|
|
3132
|
+
if (!this.socket?.connected) {
|
|
3133
|
+
console.warn("[Baasix Realtime] Not connected. Call connect() first.");
|
|
3134
|
+
}
|
|
3135
|
+
const callbackId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3136
|
+
let subscription = this.subscriptions.get(collection);
|
|
3137
|
+
if (!subscription) {
|
|
3138
|
+
subscription = {
|
|
3139
|
+
collection,
|
|
3140
|
+
callbacks: /* @__PURE__ */ new Map()
|
|
3141
|
+
};
|
|
3142
|
+
this.subscriptions.set(collection, subscription);
|
|
3143
|
+
this.subscribeOnServer(collection);
|
|
3144
|
+
this.setupCollectionListeners(collection);
|
|
3145
|
+
}
|
|
3146
|
+
subscription.callbacks.set(callbackId, callback);
|
|
3147
|
+
return () => {
|
|
3148
|
+
const sub = this.subscriptions.get(collection);
|
|
3149
|
+
if (sub) {
|
|
3150
|
+
sub.callbacks.delete(callbackId);
|
|
3151
|
+
if (sub.callbacks.size === 0) {
|
|
3152
|
+
this.unsubscribeOnServer(collection);
|
|
3153
|
+
this.removeCollectionListeners(collection);
|
|
3154
|
+
this.subscriptions.delete(collection);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
/**
|
|
3160
|
+
* Subscribe to specific events on a collection
|
|
3161
|
+
*
|
|
3162
|
+
* @example
|
|
3163
|
+
* ```typescript
|
|
3164
|
+
* // Only listen for creates
|
|
3165
|
+
* const unsubscribe = baasix.realtime.on('products', 'create', (data) => {
|
|
3166
|
+
* console.log('New product:', data);
|
|
3167
|
+
* });
|
|
3168
|
+
* ```
|
|
3169
|
+
*/
|
|
3170
|
+
on(collection, event, callback) {
|
|
3171
|
+
return this.subscribe(collection, (payload) => {
|
|
3172
|
+
if (payload.action === event) {
|
|
3173
|
+
callback(payload.data);
|
|
3174
|
+
}
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Listen to all changes across all subscribed collections
|
|
3179
|
+
*
|
|
3180
|
+
* @example
|
|
3181
|
+
* ```typescript
|
|
3182
|
+
* const unsubscribe = baasix.realtime.onAny((collection, payload) => {
|
|
3183
|
+
* console.log(`${collection}:${payload.action}`, payload.data);
|
|
3184
|
+
* });
|
|
3185
|
+
* ```
|
|
3186
|
+
*/
|
|
3187
|
+
onAny(callback) {
|
|
3188
|
+
const unsubscribers = [];
|
|
3189
|
+
this.subscriptions.forEach((_, collection) => {
|
|
3190
|
+
const unsub = this.subscribe(collection, (payload) => {
|
|
3191
|
+
callback(collection, payload);
|
|
3192
|
+
});
|
|
3193
|
+
unsubscribers.push(unsub);
|
|
3194
|
+
});
|
|
3195
|
+
return () => {
|
|
3196
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
subscribeOnServer(collection) {
|
|
3200
|
+
if (this.socket?.connected) {
|
|
3201
|
+
this.socket.emit("subscribe", { collection }, (response) => {
|
|
3202
|
+
if (response.status === "error") {
|
|
3203
|
+
console.error(`[Baasix Realtime] Failed to subscribe to ${collection}:`, response.message);
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
unsubscribeOnServer(collection) {
|
|
3209
|
+
if (this.socket?.connected) {
|
|
3210
|
+
this.socket.emit("unsubscribe", { collection });
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
setupCollectionListeners(collection) {
|
|
3214
|
+
if (!this.socket) return;
|
|
3215
|
+
const events = ["create", "update", "delete"];
|
|
3216
|
+
events.forEach((event) => {
|
|
3217
|
+
const eventName = `${collection}:${event}`;
|
|
3218
|
+
this.socket.on(eventName, (payload) => {
|
|
3219
|
+
this.handleCollectionEvent(collection, payload);
|
|
3220
|
+
});
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
removeCollectionListeners(collection) {
|
|
3224
|
+
if (!this.socket) return;
|
|
3225
|
+
const events = ["create", "update", "delete"];
|
|
3226
|
+
events.forEach((event) => {
|
|
3227
|
+
const eventName = `${collection}:${event}`;
|
|
3228
|
+
this.socket.off(eventName);
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
handleCollectionEvent(collection, payload) {
|
|
3232
|
+
const subscription = this.subscriptions.get(collection);
|
|
3233
|
+
if (subscription) {
|
|
3234
|
+
subscription.callbacks.forEach((callback) => {
|
|
3235
|
+
try {
|
|
3236
|
+
callback(payload);
|
|
3237
|
+
} catch (error) {
|
|
3238
|
+
console.error(`[Baasix Realtime] Error in subscription callback:`, error);
|
|
3239
|
+
}
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
resubscribeAll() {
|
|
3244
|
+
this.subscriptions.forEach((_, collection) => {
|
|
3245
|
+
this.subscribeOnServer(collection);
|
|
3246
|
+
this.setupCollectionListeners(collection);
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
// ===================
|
|
3250
|
+
// Workflow Realtime
|
|
3251
|
+
// ===================
|
|
3252
|
+
/**
|
|
3253
|
+
* Join a workflow execution room to receive real-time updates
|
|
3254
|
+
*
|
|
3255
|
+
* @example
|
|
3256
|
+
* ```typescript
|
|
3257
|
+
* const unsubscribe = baasix.realtime.subscribeToExecution(executionId, (update) => {
|
|
3258
|
+
* console.log('Execution update:', update);
|
|
3259
|
+
* if (update.status === 'complete') {
|
|
3260
|
+
* console.log('Workflow finished!');
|
|
3261
|
+
* }
|
|
3262
|
+
* });
|
|
3263
|
+
* ```
|
|
3264
|
+
*/
|
|
3265
|
+
subscribeToExecution(executionId, callback) {
|
|
3266
|
+
const id = String(executionId);
|
|
3267
|
+
if (this.socket?.connected) {
|
|
3268
|
+
this.socket.emit("workflow:execution:join", { executionId: id });
|
|
3269
|
+
}
|
|
3270
|
+
let callbacks = this.workflowCallbacks.get(id);
|
|
3271
|
+
if (!callbacks) {
|
|
3272
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
3273
|
+
this.workflowCallbacks.set(id, callbacks);
|
|
3274
|
+
}
|
|
3275
|
+
callbacks.add(callback);
|
|
3276
|
+
return () => {
|
|
3277
|
+
const cbs = this.workflowCallbacks.get(id);
|
|
3278
|
+
if (cbs) {
|
|
3279
|
+
cbs.delete(callback);
|
|
3280
|
+
if (cbs.size === 0) {
|
|
3281
|
+
this.workflowCallbacks.delete(id);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
handleWorkflowUpdate(data) {
|
|
3287
|
+
const id = String(data.executionId);
|
|
3288
|
+
const callbacks = this.workflowCallbacks.get(id);
|
|
3289
|
+
if (callbacks) {
|
|
3290
|
+
callbacks.forEach((callback) => {
|
|
3291
|
+
try {
|
|
3292
|
+
callback(data);
|
|
3293
|
+
} catch (error) {
|
|
3294
|
+
console.error(`[Baasix Realtime] Error in workflow callback:`, error);
|
|
3295
|
+
}
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
// ===================
|
|
3300
|
+
// Channel (Room) API - Supabase-style
|
|
3301
|
+
// ===================
|
|
3302
|
+
/**
|
|
3303
|
+
* Create a channel for a collection (Supabase-style API)
|
|
3304
|
+
*
|
|
3305
|
+
* @example
|
|
3306
|
+
* ```typescript
|
|
3307
|
+
* const channel = baasix.realtime
|
|
3308
|
+
* .channel('products')
|
|
3309
|
+
* .on('INSERT', (payload) => console.log('New:', payload))
|
|
3310
|
+
* .on('UPDATE', (payload) => console.log('Updated:', payload))
|
|
3311
|
+
* .on('DELETE', (payload) => console.log('Deleted:', payload))
|
|
3312
|
+
* .subscribe();
|
|
3313
|
+
*
|
|
3314
|
+
* // Later
|
|
3315
|
+
* channel.unsubscribe();
|
|
3316
|
+
* ```
|
|
3317
|
+
*/
|
|
3318
|
+
channel(collection) {
|
|
3319
|
+
return new RealtimeChannel(this, collection);
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
var RealtimeChannel = class {
|
|
3323
|
+
realtime;
|
|
3324
|
+
collection;
|
|
3325
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3326
|
+
unsubscribeFn = null;
|
|
3327
|
+
constructor(realtime, collection) {
|
|
3328
|
+
this.realtime = realtime;
|
|
3329
|
+
this.collection = collection;
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Add an event handler (chainable)
|
|
3333
|
+
*
|
|
3334
|
+
* @param event - 'INSERT', 'UPDATE', 'DELETE', or '*' for all
|
|
3335
|
+
* @param callback - Handler function
|
|
3336
|
+
*/
|
|
3337
|
+
on(event, callback) {
|
|
3338
|
+
const mappedEvent = this.mapEvent(event);
|
|
3339
|
+
if (mappedEvent === "*") {
|
|
3340
|
+
["create", "update", "delete"].forEach((e) => {
|
|
3341
|
+
this.addHandler(e, callback);
|
|
3342
|
+
});
|
|
3343
|
+
} else {
|
|
3344
|
+
this.addHandler(mappedEvent, callback);
|
|
3345
|
+
}
|
|
3346
|
+
return this;
|
|
3347
|
+
}
|
|
3348
|
+
mapEvent(event) {
|
|
3349
|
+
switch (event.toUpperCase()) {
|
|
3350
|
+
case "INSERT":
|
|
3351
|
+
return "create";
|
|
3352
|
+
case "UPDATE":
|
|
3353
|
+
return "update";
|
|
3354
|
+
case "DELETE":
|
|
3355
|
+
return "delete";
|
|
3356
|
+
default:
|
|
3357
|
+
return "*";
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
addHandler(event, callback) {
|
|
3361
|
+
if (!this.handlers.has(event)) {
|
|
3362
|
+
this.handlers.set(event, []);
|
|
3363
|
+
}
|
|
3364
|
+
this.handlers.get(event).push(callback);
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Start the subscription
|
|
3368
|
+
*/
|
|
3369
|
+
subscribe() {
|
|
3370
|
+
this.unsubscribeFn = this.realtime.subscribe(this.collection, (payload) => {
|
|
3371
|
+
const handlers = this.handlers.get(payload.action);
|
|
3372
|
+
if (handlers) {
|
|
3373
|
+
handlers.forEach((handler) => {
|
|
3374
|
+
try {
|
|
3375
|
+
handler(payload);
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
console.error("[Baasix Channel] Handler error:", error);
|
|
3378
|
+
}
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
return this;
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Stop the subscription
|
|
3386
|
+
*/
|
|
3387
|
+
unsubscribe() {
|
|
3388
|
+
if (this.unsubscribeFn) {
|
|
3389
|
+
this.unsubscribeFn();
|
|
3390
|
+
this.unsubscribeFn = null;
|
|
3391
|
+
}
|
|
3392
|
+
this.handlers.clear();
|
|
3393
|
+
}
|
|
3394
|
+
};
|
|
3395
|
+
|
|
3396
|
+
// src/modules/roles.ts
|
|
3397
|
+
var RolesModule = class {
|
|
3398
|
+
client;
|
|
3399
|
+
collection = "baasix_Role";
|
|
3400
|
+
constructor(config) {
|
|
3401
|
+
this.client = config.client;
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Get all roles
|
|
3405
|
+
*
|
|
3406
|
+
* @example
|
|
3407
|
+
* ```typescript
|
|
3408
|
+
* const { data: roles } = await baasix.roles.find();
|
|
3409
|
+
* ```
|
|
3410
|
+
*/
|
|
3411
|
+
async find() {
|
|
3412
|
+
return this.client.get(
|
|
3413
|
+
`/items/${this.collection}`,
|
|
3414
|
+
{
|
|
3415
|
+
params: { limit: -1 }
|
|
3416
|
+
}
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Get a role by ID
|
|
3421
|
+
*
|
|
3422
|
+
* @example
|
|
3423
|
+
* ```typescript
|
|
3424
|
+
* const role = await baasix.roles.findOne('role-uuid');
|
|
3425
|
+
* ```
|
|
3426
|
+
*/
|
|
3427
|
+
async findOne(id) {
|
|
3428
|
+
const response = await this.client.get(
|
|
3429
|
+
`/items/${this.collection}/${id}`
|
|
3430
|
+
);
|
|
3431
|
+
return response.data;
|
|
3432
|
+
}
|
|
3433
|
+
/**
|
|
3434
|
+
* Get a role by name
|
|
3435
|
+
*
|
|
3436
|
+
* @example
|
|
3437
|
+
* ```typescript
|
|
3438
|
+
* const role = await baasix.roles.findByName('Administrator');
|
|
3439
|
+
* ```
|
|
3440
|
+
*/
|
|
3441
|
+
async findByName(name) {
|
|
3442
|
+
const response = await this.client.get(
|
|
3443
|
+
`/items/${this.collection}`,
|
|
3444
|
+
{
|
|
3445
|
+
params: {
|
|
3446
|
+
filter: JSON.stringify({ name: { eq: name } }),
|
|
3447
|
+
limit: 1
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
);
|
|
3451
|
+
return response.data[0] || null;
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Create a new role
|
|
3455
|
+
*
|
|
3456
|
+
* @example
|
|
3457
|
+
* ```typescript
|
|
3458
|
+
* const id = await baasix.roles.create({
|
|
3459
|
+
* name: 'Editor',
|
|
3460
|
+
* description: 'Content editor role',
|
|
3461
|
+
* appAccess: true
|
|
3462
|
+
* });
|
|
3463
|
+
* ```
|
|
3464
|
+
*/
|
|
3465
|
+
async create(data) {
|
|
3466
|
+
const response = await this.client.post(
|
|
3467
|
+
`/items/${this.collection}`,
|
|
3468
|
+
data
|
|
3469
|
+
);
|
|
3470
|
+
return response.data.id;
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Update a role
|
|
3474
|
+
*
|
|
3475
|
+
* @example
|
|
3476
|
+
* ```typescript
|
|
3477
|
+
* await baasix.roles.update('role-uuid', {
|
|
3478
|
+
* description: 'Updated description'
|
|
3479
|
+
* });
|
|
3480
|
+
* ```
|
|
3481
|
+
*/
|
|
3482
|
+
async update(id, data) {
|
|
3483
|
+
await this.client.patch(`/items/${this.collection}/${id}`, data);
|
|
3484
|
+
}
|
|
3485
|
+
/**
|
|
3486
|
+
* Delete a role
|
|
3487
|
+
*
|
|
3488
|
+
* @example
|
|
3489
|
+
* ```typescript
|
|
3490
|
+
* await baasix.roles.delete('role-uuid');
|
|
3491
|
+
* ```
|
|
3492
|
+
*/
|
|
3493
|
+
async delete(id) {
|
|
3494
|
+
await this.client.delete(`/items/${this.collection}/${id}`);
|
|
3495
|
+
}
|
|
3496
|
+
};
|
|
3497
|
+
|
|
3498
|
+
// src/modules/users.ts
|
|
3499
|
+
var UsersModule = class {
|
|
3500
|
+
client;
|
|
3501
|
+
collection = "baasix_User";
|
|
3502
|
+
constructor(config) {
|
|
3503
|
+
this.client = config.client;
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Get all users with optional filtering
|
|
3507
|
+
*
|
|
3508
|
+
* @example
|
|
3509
|
+
* ```typescript
|
|
3510
|
+
* const { data: users } = await baasix.users.find({
|
|
3511
|
+
* filter: { status: { eq: 'active' } },
|
|
3512
|
+
* limit: 20
|
|
3513
|
+
* });
|
|
3514
|
+
* ```
|
|
3515
|
+
*/
|
|
3516
|
+
async find(options = {}) {
|
|
3517
|
+
const params = {};
|
|
3518
|
+
if (options.filter) {
|
|
3519
|
+
params.filter = JSON.stringify(options.filter);
|
|
3520
|
+
}
|
|
3521
|
+
if (options.sort) {
|
|
3522
|
+
params.sort = options.sort;
|
|
3523
|
+
}
|
|
3524
|
+
if (options.limit !== void 0) {
|
|
3525
|
+
params.limit = options.limit;
|
|
3526
|
+
}
|
|
3527
|
+
if (options.page !== void 0) {
|
|
3528
|
+
params.page = options.page;
|
|
3529
|
+
}
|
|
3530
|
+
if (options.fields?.length) {
|
|
3531
|
+
params.fields = options.fields.join(",");
|
|
3532
|
+
}
|
|
3533
|
+
return this.client.get(
|
|
3534
|
+
`/items/${this.collection}`,
|
|
3535
|
+
{ params }
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Get a user by ID
|
|
3540
|
+
*
|
|
3541
|
+
* @example
|
|
3542
|
+
* ```typescript
|
|
3543
|
+
* const user = await baasix.users.findOne('user-uuid');
|
|
3544
|
+
* ```
|
|
3545
|
+
*/
|
|
3546
|
+
async findOne(id, fields) {
|
|
3547
|
+
const params = {};
|
|
3548
|
+
if (fields?.length) {
|
|
3549
|
+
params.fields = fields.join(",");
|
|
3550
|
+
}
|
|
3551
|
+
const response = await this.client.get(
|
|
3552
|
+
`/items/${this.collection}/${id}`,
|
|
3553
|
+
{ params }
|
|
3554
|
+
);
|
|
3555
|
+
return response.data;
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Get a user by email
|
|
3559
|
+
*
|
|
3560
|
+
* @example
|
|
3561
|
+
* ```typescript
|
|
3562
|
+
* const user = await baasix.users.findByEmail('user@example.com');
|
|
3563
|
+
* ```
|
|
3564
|
+
*/
|
|
3565
|
+
async findByEmail(email) {
|
|
3566
|
+
const response = await this.client.get(
|
|
3567
|
+
`/items/${this.collection}`,
|
|
3568
|
+
{
|
|
3569
|
+
params: {
|
|
3570
|
+
filter: JSON.stringify({ email: { eq: email } }),
|
|
3571
|
+
limit: 1
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
);
|
|
3575
|
+
return response.data[0] || null;
|
|
3576
|
+
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Create a new user
|
|
3579
|
+
*
|
|
3580
|
+
* @example
|
|
3581
|
+
* ```typescript
|
|
3582
|
+
* const id = await baasix.users.create({
|
|
3583
|
+
* email: 'user@example.com',
|
|
3584
|
+
* password: 'securepassword',
|
|
3585
|
+
* firstName: 'John',
|
|
3586
|
+
* lastName: 'Doe',
|
|
3587
|
+
* role_Id: 'role-uuid'
|
|
3588
|
+
* });
|
|
3589
|
+
* ```
|
|
3590
|
+
*/
|
|
3591
|
+
async create(data) {
|
|
3592
|
+
const response = await this.client.post(
|
|
3593
|
+
`/items/${this.collection}`,
|
|
3594
|
+
data
|
|
3595
|
+
);
|
|
3596
|
+
return response.data.id;
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Update a user
|
|
3600
|
+
*
|
|
3601
|
+
* @example
|
|
3602
|
+
* ```typescript
|
|
3603
|
+
* await baasix.users.update('user-uuid', {
|
|
3604
|
+
* firstName: 'Jane'
|
|
3605
|
+
* });
|
|
3606
|
+
* ```
|
|
3607
|
+
*/
|
|
3608
|
+
async update(id, data) {
|
|
3609
|
+
await this.client.patch(`/items/${this.collection}/${id}`, data);
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Delete a user
|
|
3613
|
+
*
|
|
3614
|
+
* @example
|
|
3615
|
+
* ```typescript
|
|
3616
|
+
* await baasix.users.delete('user-uuid');
|
|
3617
|
+
* ```
|
|
3618
|
+
*/
|
|
3619
|
+
async delete(id) {
|
|
3620
|
+
await this.client.delete(`/items/${this.collection}/${id}`);
|
|
3621
|
+
}
|
|
3622
|
+
/**
|
|
3623
|
+
* Change a user's password (admin operation)
|
|
3624
|
+
*
|
|
3625
|
+
* @example
|
|
3626
|
+
* ```typescript
|
|
3627
|
+
* await baasix.users.changePassword('user-uuid', 'newpassword');
|
|
3628
|
+
* ```
|
|
3629
|
+
*/
|
|
3630
|
+
async changePassword(userId, newPassword) {
|
|
3631
|
+
await this.client.post("/auth/admin/change-password", {
|
|
3632
|
+
userId,
|
|
3633
|
+
password: newPassword
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Suspend a user
|
|
3638
|
+
*
|
|
3639
|
+
* @example
|
|
3640
|
+
* ```typescript
|
|
3641
|
+
* await baasix.users.suspend('user-uuid');
|
|
3642
|
+
* ```
|
|
3643
|
+
*/
|
|
3644
|
+
async suspend(id) {
|
|
3645
|
+
await this.client.patch(`/items/${this.collection}/${id}`, {
|
|
3646
|
+
status: "suspended"
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Activate a user
|
|
3651
|
+
*
|
|
3652
|
+
* @example
|
|
3653
|
+
* ```typescript
|
|
3654
|
+
* await baasix.users.activate('user-uuid');
|
|
3655
|
+
* ```
|
|
3656
|
+
*/
|
|
3657
|
+
async activate(id) {
|
|
3658
|
+
await this.client.patch(`/items/${this.collection}/${id}`, {
|
|
3659
|
+
status: "active"
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
/**
|
|
3663
|
+
* Bulk create users
|
|
3664
|
+
*
|
|
3665
|
+
* @example
|
|
3666
|
+
* ```typescript
|
|
3667
|
+
* const ids = await baasix.users.createMany([
|
|
3668
|
+
* { email: 'user1@example.com', firstName: 'User 1' },
|
|
3669
|
+
* { email: 'user2@example.com', firstName: 'User 2' }
|
|
3670
|
+
* ]);
|
|
3671
|
+
* ```
|
|
3672
|
+
*/
|
|
3673
|
+
async createMany(users) {
|
|
3674
|
+
const response = await this.client.post(
|
|
3675
|
+
`/items/${this.collection}/bulk`,
|
|
3676
|
+
users
|
|
3677
|
+
);
|
|
3678
|
+
return response.data;
|
|
3679
|
+
}
|
|
3680
|
+
/**
|
|
3681
|
+
* Bulk delete users
|
|
3682
|
+
*
|
|
3683
|
+
* @example
|
|
3684
|
+
* ```typescript
|
|
3685
|
+
* await baasix.users.deleteMany(['user-uuid-1', 'user-uuid-2']);
|
|
3686
|
+
* ```
|
|
3687
|
+
*/
|
|
3688
|
+
async deleteMany(ids) {
|
|
3689
|
+
await this.client.delete(`/items/${this.collection}/bulk`, {
|
|
3690
|
+
body: JSON.stringify(ids)
|
|
3691
|
+
});
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
|
|
3695
|
+
// src/modules/migrations.ts
|
|
3696
|
+
var MigrationsModule = class {
|
|
3697
|
+
client;
|
|
3698
|
+
constructor(config) {
|
|
3699
|
+
this.client = config.client;
|
|
3700
|
+
}
|
|
3701
|
+
/**
|
|
3702
|
+
* Get migration status
|
|
3703
|
+
*
|
|
3704
|
+
* @example
|
|
3705
|
+
* ```typescript
|
|
3706
|
+
* const status = await baasix.migrations.status();
|
|
3707
|
+
* console.log(`Pending migrations: ${status.pendingCount}`);
|
|
3708
|
+
* ```
|
|
3709
|
+
*/
|
|
3710
|
+
async status() {
|
|
3711
|
+
const response = await this.client.get(
|
|
3712
|
+
"/migrations/status"
|
|
3713
|
+
);
|
|
3714
|
+
return response.data;
|
|
3715
|
+
}
|
|
3716
|
+
/**
|
|
3717
|
+
* Get all completed migrations
|
|
3718
|
+
*
|
|
3719
|
+
* @example
|
|
3720
|
+
* ```typescript
|
|
3721
|
+
* const migrations = await baasix.migrations.list();
|
|
3722
|
+
* ```
|
|
3723
|
+
*/
|
|
3724
|
+
async list() {
|
|
3725
|
+
const response = await this.client.get("/migrations");
|
|
3726
|
+
return response.data;
|
|
3727
|
+
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Get pending migrations
|
|
3730
|
+
*
|
|
3731
|
+
* @example
|
|
3732
|
+
* ```typescript
|
|
3733
|
+
* const pending = await baasix.migrations.pending();
|
|
3734
|
+
* ```
|
|
3735
|
+
*/
|
|
3736
|
+
async pending() {
|
|
3737
|
+
const response = await this.client.get(
|
|
3738
|
+
"/migrations/pending"
|
|
3739
|
+
);
|
|
3740
|
+
return response.data;
|
|
3741
|
+
}
|
|
3742
|
+
/**
|
|
3743
|
+
* Check if there are pending migrations
|
|
3744
|
+
*
|
|
3745
|
+
* @example
|
|
3746
|
+
* ```typescript
|
|
3747
|
+
* const needsMigration = await baasix.migrations.hasPending();
|
|
3748
|
+
* ```
|
|
3749
|
+
*/
|
|
3750
|
+
async hasPending() {
|
|
3751
|
+
const response = await this.client.get(
|
|
3752
|
+
"/migrations/check"
|
|
3753
|
+
);
|
|
3754
|
+
return response.data.hasPending;
|
|
3755
|
+
}
|
|
3756
|
+
/**
|
|
3757
|
+
* Get a specific migration by name
|
|
3758
|
+
*
|
|
3759
|
+
* @example
|
|
3760
|
+
* ```typescript
|
|
3761
|
+
* const migration = await baasix.migrations.get('20231201_create_users');
|
|
3762
|
+
* ```
|
|
3763
|
+
*/
|
|
3764
|
+
async get(name) {
|
|
3765
|
+
try {
|
|
3766
|
+
const response = await this.client.get(
|
|
3767
|
+
`/migrations/${encodeURIComponent(name)}`
|
|
3768
|
+
);
|
|
3769
|
+
return response.data;
|
|
3770
|
+
} catch {
|
|
3771
|
+
return null;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
/**
|
|
3775
|
+
* Run pending migrations
|
|
3776
|
+
*
|
|
3777
|
+
* @example
|
|
3778
|
+
* ```typescript
|
|
3779
|
+
* const result = await baasix.migrations.run();
|
|
3780
|
+
* console.log(`Ran ${result.migrationsRun.length} migrations`);
|
|
3781
|
+
* ```
|
|
3782
|
+
*/
|
|
3783
|
+
async run() {
|
|
3784
|
+
const response = await this.client.post(
|
|
3785
|
+
"/migrations/run",
|
|
3786
|
+
{}
|
|
3787
|
+
);
|
|
3788
|
+
return response.data;
|
|
3789
|
+
}
|
|
3790
|
+
/**
|
|
3791
|
+
* Rollback a specific migration
|
|
3792
|
+
*
|
|
3793
|
+
* @example
|
|
3794
|
+
* ```typescript
|
|
3795
|
+
* const result = await baasix.migrations.rollback('20231201_create_users');
|
|
3796
|
+
* ```
|
|
3797
|
+
*/
|
|
3798
|
+
async rollback(name) {
|
|
3799
|
+
const response = await this.client.post(
|
|
3800
|
+
"/migrations/rollback",
|
|
3801
|
+
{ name }
|
|
3802
|
+
);
|
|
3803
|
+
return response.data;
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Rollback the last batch of migrations
|
|
3807
|
+
*
|
|
3808
|
+
* @example
|
|
3809
|
+
* ```typescript
|
|
3810
|
+
* const result = await baasix.migrations.rollbackLast();
|
|
3811
|
+
* ```
|
|
3812
|
+
*/
|
|
3813
|
+
async rollbackLast() {
|
|
3814
|
+
const response = await this.client.post(
|
|
3815
|
+
"/migrations/rollback-last",
|
|
3816
|
+
{}
|
|
3817
|
+
);
|
|
3818
|
+
return response.data;
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Create a new migration file
|
|
3822
|
+
*
|
|
3823
|
+
* @example
|
|
3824
|
+
* ```typescript
|
|
3825
|
+
* const migrationName = await baasix.migrations.create('add_status_column');
|
|
3826
|
+
* ```
|
|
3827
|
+
*/
|
|
3828
|
+
async create(name) {
|
|
3829
|
+
const response = await this.client.post(
|
|
3830
|
+
"/migrations/create",
|
|
3831
|
+
{ name }
|
|
3832
|
+
);
|
|
3833
|
+
return response.data.name;
|
|
3834
|
+
}
|
|
3835
|
+
/**
|
|
3836
|
+
* Mark a migration as complete (without running it)
|
|
3837
|
+
*
|
|
3838
|
+
* @example
|
|
3839
|
+
* ```typescript
|
|
3840
|
+
* await baasix.migrations.markComplete('20231201_create_users');
|
|
3841
|
+
* ```
|
|
3842
|
+
*/
|
|
3843
|
+
async markComplete(name) {
|
|
3844
|
+
await this.client.post("/migrations/mark-complete", { name });
|
|
3845
|
+
}
|
|
3846
|
+
/**
|
|
3847
|
+
* Mark all pending migrations as complete
|
|
3848
|
+
*
|
|
3849
|
+
* @example
|
|
3850
|
+
* ```typescript
|
|
3851
|
+
* await baasix.migrations.markAllComplete();
|
|
3852
|
+
* ```
|
|
3853
|
+
*/
|
|
3854
|
+
async markAllComplete() {
|
|
3855
|
+
await this.client.post("/migrations/mark-all-complete", {});
|
|
3856
|
+
}
|
|
3857
|
+
};
|
|
3858
|
+
|
|
3859
|
+
// src/storage/asyncStorage.ts
|
|
3860
|
+
var AsyncStorageAdapter = class {
|
|
3861
|
+
asyncStorage;
|
|
3862
|
+
prefix;
|
|
3863
|
+
constructor(asyncStorage, prefix = "baasix_") {
|
|
3864
|
+
this.asyncStorage = asyncStorage;
|
|
3865
|
+
this.prefix = prefix;
|
|
3866
|
+
}
|
|
3867
|
+
getKey(key) {
|
|
3868
|
+
if (key.startsWith(this.prefix)) {
|
|
3869
|
+
return key;
|
|
3870
|
+
}
|
|
3871
|
+
return `${this.prefix}${key}`;
|
|
3872
|
+
}
|
|
3873
|
+
async get(key) {
|
|
3874
|
+
try {
|
|
3875
|
+
return await this.asyncStorage.getItem(this.getKey(key));
|
|
3876
|
+
} catch {
|
|
3877
|
+
console.warn(`[Baasix SDK] Failed to get item from AsyncStorage: ${key}`);
|
|
3878
|
+
return null;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
async set(key, value) {
|
|
3882
|
+
try {
|
|
3883
|
+
await this.asyncStorage.setItem(this.getKey(key), value);
|
|
3884
|
+
} catch {
|
|
3885
|
+
console.warn(`[Baasix SDK] Failed to set item in AsyncStorage: ${key}`);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
async remove(key) {
|
|
3889
|
+
try {
|
|
3890
|
+
await this.asyncStorage.removeItem(this.getKey(key));
|
|
3891
|
+
} catch {
|
|
3892
|
+
console.warn(`[Baasix SDK] Failed to remove item from AsyncStorage: ${key}`);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
async clear() {
|
|
3896
|
+
try {
|
|
3897
|
+
if (this.asyncStorage.getAllKeys && this.asyncStorage.multiRemove) {
|
|
3898
|
+
const allKeys = await this.asyncStorage.getAllKeys();
|
|
3899
|
+
const sdkKeys = allKeys.filter((key) => key.startsWith(this.prefix));
|
|
3900
|
+
if (sdkKeys.length > 0) {
|
|
3901
|
+
await this.asyncStorage.multiRemove(sdkKeys);
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
} catch {
|
|
3905
|
+
console.warn("[Baasix SDK] Failed to clear AsyncStorage");
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3910
|
+
// src/index.ts
|
|
3911
|
+
function getDefaultStorage() {
|
|
3912
|
+
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
|
3913
|
+
return new LocalStorageAdapter();
|
|
3914
|
+
}
|
|
3915
|
+
return new MemoryStorageAdapter();
|
|
3916
|
+
}
|
|
3917
|
+
var Baasix = class {
|
|
3918
|
+
config;
|
|
3919
|
+
httpClient;
|
|
3920
|
+
storage;
|
|
3921
|
+
// Modules
|
|
3922
|
+
auth;
|
|
3923
|
+
files;
|
|
3924
|
+
schemas;
|
|
3925
|
+
notifications;
|
|
3926
|
+
permissions;
|
|
3927
|
+
settings;
|
|
3928
|
+
reports;
|
|
3929
|
+
workflows;
|
|
3930
|
+
realtime;
|
|
3931
|
+
roles;
|
|
3932
|
+
users;
|
|
3933
|
+
migrations;
|
|
3934
|
+
// Items module factory cache
|
|
3935
|
+
itemsModules = /* @__PURE__ */ new Map();
|
|
3936
|
+
constructor(config) {
|
|
3937
|
+
if (!config.url) {
|
|
3938
|
+
throw new Error("Baasix SDK: url is required");
|
|
3939
|
+
}
|
|
3940
|
+
const normalizedUrl = config.url.replace(/\/$/, "");
|
|
3941
|
+
this.config = {
|
|
3942
|
+
...config,
|
|
3943
|
+
url: normalizedUrl,
|
|
3944
|
+
authMode: config.authMode || "jwt",
|
|
3945
|
+
timeout: config.timeout || 3e4,
|
|
3946
|
+
autoRefresh: config.autoRefresh !== false
|
|
3947
|
+
};
|
|
3948
|
+
this.storage = config.storage || getDefaultStorage();
|
|
3949
|
+
const credentials = config.credentials || (this.config.authMode === "cookie" ? "include" : "same-origin");
|
|
3950
|
+
this.httpClient = new HttpClient({
|
|
3951
|
+
baseUrl: normalizedUrl,
|
|
3952
|
+
authMode: this.config.authMode,
|
|
3953
|
+
storage: this.storage,
|
|
3954
|
+
timeout: this.config.timeout,
|
|
3955
|
+
autoRefresh: this.config.autoRefresh,
|
|
3956
|
+
credentials,
|
|
3957
|
+
headers: config.headers || {},
|
|
3958
|
+
token: config.token,
|
|
3959
|
+
tenantId: config.tenantId,
|
|
3960
|
+
onAuthError: () => {
|
|
3961
|
+
this.config.onAuthStateChange?.("SIGNED_OUT", null);
|
|
3962
|
+
},
|
|
3963
|
+
onTokenRefresh: () => {
|
|
3964
|
+
}
|
|
3965
|
+
});
|
|
3966
|
+
this.auth = new AuthModule({
|
|
3967
|
+
client: this.httpClient,
|
|
3968
|
+
storage: this.storage,
|
|
3969
|
+
authMode: this.config.authMode,
|
|
3970
|
+
onAuthStateChange: config.onAuthStateChange
|
|
3971
|
+
});
|
|
3972
|
+
this.files = new FilesModule({ client: this.httpClient });
|
|
3973
|
+
this.schemas = new SchemasModule({ client: this.httpClient });
|
|
3974
|
+
this.notifications = new NotificationsModule({ client: this.httpClient });
|
|
3975
|
+
this.permissions = new PermissionsModule({ client: this.httpClient });
|
|
3976
|
+
this.settings = new SettingsModule({ client: this.httpClient });
|
|
3977
|
+
this.reports = new ReportsModule({ client: this.httpClient });
|
|
3978
|
+
this.workflows = new WorkflowsModule({ client: this.httpClient });
|
|
3979
|
+
this.roles = new RolesModule({ client: this.httpClient });
|
|
3980
|
+
this.users = new UsersModule({ client: this.httpClient });
|
|
3981
|
+
this.migrations = new MigrationsModule({ client: this.httpClient });
|
|
3982
|
+
this.realtime = new RealtimeModule({
|
|
3983
|
+
client: this.httpClient,
|
|
3984
|
+
storage: this.storage,
|
|
3985
|
+
socketUrl: config.socketUrl,
|
|
3986
|
+
socketPath: config.socketPath
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* Get an Items module for a specific collection.
|
|
3991
|
+
* Returns a cached instance for repeated calls with the same collection.
|
|
3992
|
+
*
|
|
3993
|
+
* @example
|
|
3994
|
+
* ```typescript
|
|
3995
|
+
* // Get items module
|
|
3996
|
+
* const products = baasix.items('products');
|
|
3997
|
+
*
|
|
3998
|
+
* // CRUD operations
|
|
3999
|
+
* const { data } = await products.find({ filter: { status: { eq: 'active' } } });
|
|
4000
|
+
* const product = await products.findOne('product-uuid');
|
|
4001
|
+
* const id = await products.create({ name: 'New Product', price: 29.99 });
|
|
4002
|
+
* await products.update('product-uuid', { price: 24.99 });
|
|
4003
|
+
* await products.delete('product-uuid');
|
|
4004
|
+
*
|
|
4005
|
+
* // Query builder
|
|
4006
|
+
* const results = await baasix.items('posts')
|
|
4007
|
+
* .query()
|
|
4008
|
+
* .select('*', 'author.*')
|
|
4009
|
+
* .filter({ status: { eq: 'published' } })
|
|
4010
|
+
* .sort({ createdAt: 'desc' })
|
|
4011
|
+
* .limit(10)
|
|
4012
|
+
* .get();
|
|
4013
|
+
* ```
|
|
4014
|
+
*/
|
|
4015
|
+
items(collection) {
|
|
4016
|
+
if (!this.itemsModules.has(collection)) {
|
|
4017
|
+
this.itemsModules.set(
|
|
4018
|
+
collection,
|
|
4019
|
+
new ItemsModule(collection, { client: this.httpClient })
|
|
4020
|
+
);
|
|
4021
|
+
}
|
|
4022
|
+
return this.itemsModules.get(collection);
|
|
4023
|
+
}
|
|
4024
|
+
/**
|
|
4025
|
+
* Alias for items() - get a collection
|
|
4026
|
+
*
|
|
4027
|
+
* @example
|
|
4028
|
+
* ```typescript
|
|
4029
|
+
* const products = baasix.collection('products');
|
|
4030
|
+
* ```
|
|
4031
|
+
*/
|
|
4032
|
+
collection(collection) {
|
|
4033
|
+
return this.items(collection);
|
|
4034
|
+
}
|
|
4035
|
+
/**
|
|
4036
|
+
* Alias for items() - from a collection (Supabase-style)
|
|
4037
|
+
*
|
|
4038
|
+
* @example
|
|
4039
|
+
* ```typescript
|
|
4040
|
+
* const products = baasix.from('products');
|
|
4041
|
+
* ```
|
|
4042
|
+
*/
|
|
4043
|
+
from(collection) {
|
|
4044
|
+
return this.items(collection);
|
|
4045
|
+
}
|
|
4046
|
+
/**
|
|
4047
|
+
* Get the underlying HTTP client for custom requests
|
|
4048
|
+
*
|
|
4049
|
+
* @example
|
|
4050
|
+
* ```typescript
|
|
4051
|
+
* // Custom GET request
|
|
4052
|
+
* const data = await baasix.request.get('/custom-endpoint');
|
|
4053
|
+
*
|
|
4054
|
+
* // Custom POST request
|
|
4055
|
+
* const result = await baasix.request.post('/custom-endpoint', { data: 'value' });
|
|
4056
|
+
* ```
|
|
4057
|
+
*/
|
|
4058
|
+
get request() {
|
|
4059
|
+
return this.httpClient;
|
|
4060
|
+
}
|
|
4061
|
+
/**
|
|
4062
|
+
* Update SDK configuration
|
|
4063
|
+
*
|
|
4064
|
+
* @example
|
|
4065
|
+
* ```typescript
|
|
4066
|
+
* baasix.configure({
|
|
4067
|
+
* headers: { 'X-Custom-Header': 'value' }
|
|
4068
|
+
* });
|
|
4069
|
+
* ```
|
|
4070
|
+
*/
|
|
4071
|
+
configure(config) {
|
|
4072
|
+
if (config.headers) {
|
|
4073
|
+
this.httpClient.updateConfig({ headers: config.headers });
|
|
4074
|
+
}
|
|
4075
|
+
if (config.tenantId !== void 0) {
|
|
4076
|
+
this.httpClient.updateConfig({ tenantId: config.tenantId });
|
|
4077
|
+
}
|
|
4078
|
+
if (config.token) {
|
|
4079
|
+
this.httpClient.updateConfig({ token: config.token });
|
|
4080
|
+
}
|
|
4081
|
+
if (config.timeout) {
|
|
4082
|
+
this.httpClient.updateConfig({ timeout: config.timeout });
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
/**
|
|
4086
|
+
* Set the tenant for multi-tenant mode
|
|
4087
|
+
*
|
|
4088
|
+
* @example
|
|
4089
|
+
* ```typescript
|
|
4090
|
+
* baasix.setTenant('tenant-uuid');
|
|
4091
|
+
* ```
|
|
4092
|
+
*/
|
|
4093
|
+
async setTenant(tenantId) {
|
|
4094
|
+
this.httpClient.updateConfig({ tenantId });
|
|
4095
|
+
await this.storage.set(STORAGE_KEYS.TENANT, tenantId);
|
|
4096
|
+
}
|
|
4097
|
+
/**
|
|
4098
|
+
* Get the current tenant ID
|
|
4099
|
+
*/
|
|
4100
|
+
async getTenant() {
|
|
4101
|
+
return await this.storage.get(STORAGE_KEYS.TENANT);
|
|
4102
|
+
}
|
|
4103
|
+
/**
|
|
4104
|
+
* Clear the tenant
|
|
4105
|
+
*/
|
|
4106
|
+
async clearTenant() {
|
|
4107
|
+
this.httpClient.updateConfig({ tenantId: void 0 });
|
|
4108
|
+
await this.storage.remove(STORAGE_KEYS.TENANT);
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
4111
|
+
* Get the base URL
|
|
4112
|
+
*/
|
|
4113
|
+
getUrl() {
|
|
4114
|
+
return this.config.url;
|
|
4115
|
+
}
|
|
4116
|
+
/**
|
|
4117
|
+
* Get the current auth mode
|
|
4118
|
+
*/
|
|
4119
|
+
getAuthMode() {
|
|
4120
|
+
return this.config.authMode;
|
|
4121
|
+
}
|
|
4122
|
+
/**
|
|
4123
|
+
* Get the storage adapter
|
|
4124
|
+
*/
|
|
4125
|
+
getStorage() {
|
|
4126
|
+
return this.storage;
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
4129
|
+
function createBaasix(config) {
|
|
4130
|
+
return new Baasix(config);
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
export { AsyncStorageAdapter, AuthModule, Baasix, BaasixError, FilesModule, HttpClient, ItemsModule, LocalStorageAdapter, MemoryStorageAdapter, MigrationsModule, NotificationsModule, PermissionsModule, QueryBuilder, RealtimeChannel, RealtimeModule, ReportsModule, RolesModule, STORAGE_KEYS, SchemasModule, SettingsModule, UsersModule, WorkflowsModule, createBaasix };
|
|
4134
|
+
//# sourceMappingURL=index.js.map
|
|
4135
|
+
//# sourceMappingURL=index.js.map
|