@mitralab.io/platform-sdk 1.0.8 → 1.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs DELETED
@@ -1,706 +0,0 @@
1
- // src/client.ts
2
- import { encodePathSegment, expectObject } from "@mitralab.io/sdk-core";
3
-
4
- // src/utils/http-client.ts
5
- var bearerCredentialPattern = /(Bearer\s+)\S+/gi;
6
- function redactText(value, currentToken) {
7
- const withoutBearerCredentials = value.replace(bearerCredentialPattern, "$1[REDACTED]");
8
- return currentToken ? withoutBearerCredentials.split(currentToken).join("[REDACTED]") : withoutBearerCredentials;
9
- }
10
- function redactDetails(value, currentToken) {
11
- if (typeof value === "string") return redactText(value, currentToken);
12
- if (Array.isArray(value)) return value.map((item) => redactDetails(item, currentToken));
13
- if (value && typeof value === "object") {
14
- return Object.fromEntries(
15
- Object.entries(value).map(([key, entry]) => [
16
- redactText(key, currentToken),
17
- redactDetails(entry, currentToken)
18
- ])
19
- );
20
- }
21
- return value;
22
- }
23
- function asErrorPayload(value) {
24
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
25
- return value;
26
- }
27
- function optionalString(value) {
28
- return typeof value === "string" ? value : void 0;
29
- }
30
- var HttpClient = class {
31
- baseUrl;
32
- tokenGetter;
33
- onUnauthorized;
34
- onError;
35
- defaultHeaders;
36
- constructor(config) {
37
- this.baseUrl = config.baseUrl.replace(/\/$/, "");
38
- this.tokenGetter = config.getToken ?? (() => null);
39
- this.onUnauthorized = config.onUnauthorized;
40
- this.onError = config.onError;
41
- this.defaultHeaders = config.defaultHeaders ?? {};
42
- }
43
- /**
44
- * Returns the current authentication token.
45
- * @returns The JWT token if authenticated, null otherwise
46
- */
47
- getToken() {
48
- return this.tokenGetter();
49
- }
50
- /**
51
- * Makes an HTTP request with automatic JSON handling and authentication.
52
- *
53
- * @param path - API endpoint path (e.g., '/users')
54
- * @param options - Request options including method, body, headers, and params
55
- * @returns Promise resolving to the parsed JSON response
56
- * @throws {MitraApiError} When the API returns an error response
57
- *
58
- * @example
59
- * ```typescript
60
- * const result = await client.request<User>('/users/123', {
61
- * method: 'PUT',
62
- * body: { name: 'Updated Name' },
63
- * });
64
- * ```
65
- */
66
- async request(path, options = {}) {
67
- const { method = "GET", body, headers = {}, params, isRetry } = options;
68
- let url = `${this.baseUrl}${path}`;
69
- if (params) {
70
- const searchParams = new URLSearchParams();
71
- Object.entries(params).forEach(([key, value]) => {
72
- if (value !== void 0) {
73
- searchParams.append(key, String(value));
74
- }
75
- });
76
- const queryString = searchParams.toString();
77
- if (queryString) {
78
- url += `?${queryString}`;
79
- }
80
- }
81
- const requestHeaders = {
82
- "Content-Type": "application/json",
83
- ...this.defaultHeaders,
84
- ...headers
85
- };
86
- const token = this.tokenGetter();
87
- if (token) {
88
- requestHeaders["Authorization"] = `Bearer ${token}`;
89
- }
90
- const response = await fetch(url, {
91
- method,
92
- headers: requestHeaders,
93
- body: body ? JSON.stringify(body) : void 0,
94
- redirect: "manual"
95
- });
96
- if (response.redirected || response.type === "opaqueredirect") {
97
- const error = new MitraApiError(
98
- "Redirected responses are not allowed",
99
- response.status,
100
- "REDIRECT_NOT_ALLOWED"
101
- );
102
- this.onError?.(error);
103
- throw error;
104
- }
105
- if (!response.ok) {
106
- if (response.status === 401 && !isRetry && this.onUnauthorized) {
107
- const refreshed = await this.onUnauthorized();
108
- if (refreshed) {
109
- return this.request(path, { ...options, isRetry: true });
110
- }
111
- }
112
- const errorBody = await response.json().catch(() => ({}));
113
- const errorPayload = asErrorPayload(errorBody);
114
- const rawMessage = optionalString(errorPayload.message);
115
- const rawCode = optionalString(errorPayload.error_code);
116
- const error = new MitraApiError(
117
- redactText(rawMessage || `Request failed with status ${response.status}`, token),
118
- response.status,
119
- rawCode === void 0 ? void 0 : redactText(rawCode, token),
120
- redactDetails(errorBody, token)
121
- );
122
- this.onError?.(error);
123
- throw error;
124
- }
125
- if (response.status === 204) {
126
- return void 0;
127
- }
128
- return response.json();
129
- }
130
- /**
131
- * Makes a GET request.
132
- *
133
- * @param path - API endpoint path
134
- * @param params - Optional query parameters
135
- * @returns Promise resolving to the parsed JSON response
136
- *
137
- * @example
138
- * ```typescript
139
- * const users = await client.get<User[]>('/users', { limit: 10 });
140
- * ```
141
- */
142
- get(path, params) {
143
- return this.request(path, { method: "GET", params });
144
- }
145
- /**
146
- * Makes a POST request.
147
- *
148
- * @param path - API endpoint path
149
- * @param body - Request body (will be JSON stringified)
150
- * @returns Promise resolving to the parsed JSON response
151
- *
152
- * @example
153
- * ```typescript
154
- * const user = await client.post<User>('/users', { name: 'John', email: 'john@example.com' });
155
- * ```
156
- */
157
- post(path, body) {
158
- return this.request(path, { method: "POST", body });
159
- }
160
- /**
161
- * Makes a PUT request.
162
- *
163
- * @param path - API endpoint path
164
- * @param body - Request body (will be JSON stringified)
165
- * @returns Promise resolving to the parsed JSON response
166
- *
167
- * @example
168
- * ```typescript
169
- * const user = await client.put<User>('/users/123', { name: 'Updated Name' });
170
- * ```
171
- */
172
- put(path, body) {
173
- return this.request(path, { method: "PUT", body });
174
- }
175
- /**
176
- * Makes a DELETE request.
177
- *
178
- * @param path - API endpoint path
179
- * @param params - Optional query parameters
180
- * @returns Promise resolving to the parsed JSON response (or undefined for 204 responses)
181
- *
182
- * @example
183
- * ```typescript
184
- * await client.delete('/users/123');
185
- * ```
186
- */
187
- delete(path, params) {
188
- return this.request(path, { method: "DELETE", params });
189
- }
190
- };
191
- var MitraApiError = class extends Error {
192
- constructor(message, status, code, details) {
193
- super(message);
194
- this.status = status;
195
- this.code = code;
196
- this.details = details;
197
- this.name = "MitraApiError";
198
- }
199
- };
200
-
201
- // src/core-errors.ts
202
- var coreErrors = {
203
- configuration: (message) => new MitraApiError(message, 0, "INVALID_CONFIGURATION"),
204
- invalidResponse: (message) => new MitraApiError(message, 200, "INVALID_RESPONSE")
205
- };
206
-
207
- // src/modules/auth.ts
208
- import { createAuthModule } from "@mitralab.io/sdk-core";
209
- var AuthModule = class {
210
- appId;
211
- _currentUser = null;
212
- #accessToken = null;
213
- #refreshToken = null;
214
- refreshPromise = null;
215
- listeners = /* @__PURE__ */ new Set();
216
- storageKey;
217
- publicClient;
218
- authedClient;
219
- currentUserApi;
220
- constructor(appId, iamBaseUrl) {
221
- this.appId = appId;
222
- this.storageKey = `mitra_auth_${appId}`;
223
- this.publicClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => null });
224
- this.authedClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => this.#accessToken });
225
- this.currentUserApi = createAuthModule(this.authedClient, coreErrors);
226
- this.loadFromStorage();
227
- }
228
- /** The currently authenticated user, or null. */
229
- get currentUser() {
230
- return this._currentUser;
231
- }
232
- /** The current JWT access token, or null. */
233
- get accessToken() {
234
- return this.#accessToken;
235
- }
236
- /** Whether a user is currently authenticated (local check, not server-validated). */
237
- get isAuthenticated() {
238
- return this._currentUser !== null && this.#accessToken !== null;
239
- }
240
- /**
241
- * Signs in a user with email and password.
242
- *
243
- * On success, stores access token, refresh token, and user data.
244
- * Subsequent API requests use the token automatically.
245
- *
246
- * @param credentials - Email and password.
247
- * @returns The authenticated user.
248
- * @throws {MitraApiError} On invalid credentials (401).
249
- *
250
- * @example
251
- * ```typescript
252
- * const user = await mitra.auth.signIn({
253
- * email: 'user@example.com',
254
- * password: 'password123',
255
- * });
256
- * ```
257
- */
258
- async signIn(credentials) {
259
- const tokenResponse = await this.publicClient.post(
260
- "/api/v1/auth/login",
261
- { ...credentials, appId: this.appId }
262
- );
263
- this.#accessToken = tokenResponse.accessToken;
264
- this.#refreshToken = tokenResponse.refreshToken;
265
- const user = await this.getCurrentUser();
266
- this.setAuthState(user, tokenResponse.accessToken, tokenResponse.refreshToken);
267
- return user;
268
- }
269
- /**
270
- * Registers a new user and signs them in automatically.
271
- *
272
- * @param data - Email, password, and optional name.
273
- * @returns The newly created and authenticated user.
274
- * @throws {MitraApiError} On duplicate email (409) or validation error (400).
275
- *
276
- * @example
277
- * ```typescript
278
- * const user = await mitra.auth.signUp({
279
- * email: 'new@example.com',
280
- * password: 'securepassword',
281
- * name: 'Jane Doe',
282
- * });
283
- * ```
284
- */
285
- async signUp(data) {
286
- await this.publicClient.post("/api/v1/auth/register", {
287
- ...data,
288
- appId: this.appId
289
- });
290
- return this.signIn({ email: data.email, password: data.password });
291
- }
292
- /**
293
- * Signs out the current user, clearing all auth state and localStorage.
294
- *
295
- * @param redirectUrl - Optional URL to navigate to after sign-out.
296
- *
297
- * @example
298
- * ```typescript
299
- * mitra.auth.signOut();
300
- * mitra.auth.signOut('/login');
301
- * ```
302
- */
303
- signOut(redirectUrl) {
304
- this.clearAuthState();
305
- if (globalThis.window !== void 0 && redirectUrl) {
306
- globalThis.window.location.href = redirectUrl;
307
- }
308
- }
309
- /**
310
- * Refreshes the session using the stored refresh token.
311
- *
312
- * Called automatically by the SDK on 401 responses. Can also be called
313
- * manually. Multiple concurrent calls are deduplicated (only one refresh
314
- * request is made).
315
- *
316
- * @returns `true` if refresh succeeded, `false` otherwise.
317
- *
318
- * @example
319
- * ```typescript
320
- * const ok = await mitra.auth.refreshSession();
321
- * if (!ok) mitra.auth.redirectToLogin();
322
- * ```
323
- */
324
- async refreshSession() {
325
- if (!this.#refreshToken) return false;
326
- if (this.refreshPromise) return this.refreshPromise;
327
- this.refreshPromise = this.doRefresh();
328
- try {
329
- return await this.refreshPromise;
330
- } finally {
331
- this.refreshPromise = null;
332
- }
333
- }
334
- /**
335
- * Fetches the current user from the server and updates local state.
336
- *
337
- * Only clears auth state on 401 (expired/invalid token).
338
- * Transient errors (500, network) return null without clearing the session.
339
- *
340
- * @returns The user if authenticated, `null` otherwise.
341
- *
342
- * @example
343
- * ```typescript
344
- * const user = await mitra.auth.me();
345
- * if (!user) console.log('Not authenticated');
346
- * ```
347
- */
348
- async me() {
349
- if (!this.#accessToken) return null;
350
- try {
351
- const user = await this.getCurrentUser();
352
- this._currentUser = user;
353
- this.saveToStorage();
354
- this.notifyListeners();
355
- return user;
356
- } catch (error) {
357
- if (error instanceof MitraApiError && error.status === 401) {
358
- this.clearAuthState();
359
- }
360
- return null;
361
- }
362
- }
363
- /**
364
- * Validates the current session with the server.
365
- *
366
- * @returns `true` if the session is valid, `false` otherwise.
367
- *
368
- * @example
369
- * ```typescript
370
- * const valid = await mitra.auth.checkAuth();
371
- * if (!valid) mitra.auth.redirectToLogin();
372
- * ```
373
- */
374
- async checkAuth() {
375
- return await this.me() !== null;
376
- }
377
- /**
378
- * Sets the access token manually (e.g., from SSO/OAuth callback).
379
- *
380
- * Call `me()` afterwards to fetch the associated user data.
381
- *
382
- * @param token - JWT access token.
383
- * @param saveToStorage - Whether to persist to localStorage (default: true).
384
- *
385
- * @example
386
- * ```typescript
387
- * mitra.auth.setToken(tokenFromCallback);
388
- * await mitra.auth.me();
389
- * ```
390
- */
391
- setToken(token, saveToStorage = true) {
392
- this.#accessToken = token;
393
- if (saveToStorage) {
394
- this.saveToStorage();
395
- }
396
- }
397
- /**
398
- * Redirects to `/login?returnUrl=...` for unauthenticated users.
399
- *
400
- * @param returnUrl - URL to return to after login (default: '/').
401
- *
402
- * @example
403
- * ```typescript
404
- * if (!mitra.auth.isAuthenticated) {
405
- * mitra.auth.redirectToLogin(window.location.pathname);
406
- * }
407
- * ```
408
- */
409
- redirectToLogin(returnUrl = "/") {
410
- if (globalThis.window === void 0) return;
411
- globalThis.window.location.href = `/login?returnUrl=${encodeURIComponent(returnUrl)}`;
412
- }
413
- /**
414
- * Registers a callback for auth state changes.
415
- *
416
- * Called immediately with the current state, then on every sign-in/sign-out.
417
- *
418
- * @param callback - Receives the User on login, null on logout.
419
- * @returns Unsubscribe function.
420
- *
421
- * @example
422
- * ```typescript
423
- * useEffect(() => {
424
- * const unsub = mitra.auth.onAuthStateChange((user) => {
425
- * setUser(user);
426
- * setLoading(false);
427
- * });
428
- * return unsub;
429
- * }, []);
430
- * ```
431
- */
432
- onAuthStateChange(callback) {
433
- this.listeners.add(callback);
434
- callback(this._currentUser);
435
- return () => {
436
- this.listeners.delete(callback);
437
- };
438
- }
439
- async doRefresh() {
440
- try {
441
- const tokenResponse = await this.publicClient.post(
442
- "/api/v1/auth/refresh-token",
443
- { refreshToken: this.#refreshToken }
444
- );
445
- this.#accessToken = tokenResponse.accessToken;
446
- this.#refreshToken = tokenResponse.refreshToken;
447
- const user = await this.getCurrentUser();
448
- this.setAuthState(user, tokenResponse.accessToken, tokenResponse.refreshToken);
449
- return true;
450
- } catch {
451
- this.clearAuthState();
452
- return false;
453
- }
454
- }
455
- setAuthState(user, token, refreshToken) {
456
- this._currentUser = user;
457
- this.#accessToken = token;
458
- this.#refreshToken = refreshToken;
459
- this.saveToStorage();
460
- this.notifyListeners();
461
- }
462
- async getCurrentUser() {
463
- const user = await this.currentUserApi.me();
464
- return { ...user, tenantId: user.tenant.id };
465
- }
466
- clearAuthState() {
467
- this._currentUser = null;
468
- this.#accessToken = null;
469
- this.#refreshToken = null;
470
- this.removeFromStorage();
471
- this.notifyListeners();
472
- }
473
- notifyListeners() {
474
- this.listeners.forEach((callback) => {
475
- try {
476
- callback(this._currentUser);
477
- } catch (error) {
478
- console.error("Auth state change listener error:", error);
479
- }
480
- });
481
- }
482
- saveToStorage() {
483
- if (typeof localStorage === "undefined") return;
484
- try {
485
- localStorage.setItem(
486
- this.storageKey,
487
- JSON.stringify({
488
- user: this._currentUser,
489
- token: this.#accessToken,
490
- refreshToken: this.#refreshToken
491
- })
492
- );
493
- } catch {
494
- }
495
- }
496
- loadFromStorage() {
497
- if (typeof localStorage === "undefined") return;
498
- try {
499
- const stored = localStorage.getItem(this.storageKey);
500
- if (stored) {
501
- const { user, token, refreshToken } = JSON.parse(stored);
502
- this._currentUser = user;
503
- this.#accessToken = token;
504
- this.#refreshToken = refreshToken ?? null;
505
- }
506
- } catch {
507
- this.removeFromStorage();
508
- }
509
- }
510
- removeFromStorage() {
511
- if (typeof localStorage === "undefined") return;
512
- try {
513
- localStorage.removeItem(this.storageKey);
514
- } catch {
515
- }
516
- }
517
- };
518
-
519
- // src/modules/entities.ts
520
- import {
521
- createEntitiesModule
522
- } from "@mitralab.io/sdk-core";
523
- var EntitiesModule = class _EntitiesModule {
524
- constructor(httpClient, dataSourceId) {
525
- this.httpClient = httpClient;
526
- void dataSourceId;
527
- this.core = createEntitiesModule(httpClient, coreErrors);
528
- }
529
- core;
530
- static createProxy(httpClient, dataSourceId) {
531
- const instance = new _EntitiesModule(httpClient, dataSourceId);
532
- return new Proxy(instance, {
533
- get(target, property, receiver) {
534
- if (typeof property !== "string" || property in target) {
535
- return Reflect.get(target, property, receiver);
536
- }
537
- return target.getTable(property);
538
- }
539
- });
540
- }
541
- /**
542
- * Preserved for Platform SDK 1.x compatibility.
543
- * Records now resolve the app from authenticated context instead of a data source path.
544
- */
545
- setDataSourceId(dataSourceId) {
546
- void dataSourceId;
547
- this.core = createEntitiesModule(this.httpClient, coreErrors);
548
- }
549
- getTable(tableName) {
550
- return this.core.getTable(tableName);
551
- }
552
- };
553
-
554
- // src/modules/functions.ts
555
- import {
556
- createFunctionsModule
557
- } from "@mitralab.io/sdk-core";
558
- var FunctionsModule = class {
559
- core;
560
- constructor(httpClient) {
561
- this.core = createFunctionsModule(httpClient, { emptyInput: "omit-body" }, coreErrors);
562
- }
563
- /**
564
- * Executes a Function using the Platform SDK 1.x server-default invocation semantics.
565
- * The runtime SDK uses an explicit invocation header instead.
566
- */
567
- async execute(functionId, input) {
568
- const execution = await this.core.execute(functionId, input);
569
- if (execution.input === null) {
570
- throw coreErrors.invalidResponse(
571
- "Function execution response has an invalid input field"
572
- );
573
- }
574
- return { ...execution, input: execution.input };
575
- }
576
- };
577
-
578
- // src/modules/integration.ts
579
- import {
580
- createIntegrationModule
581
- } from "@mitralab.io/sdk-core";
582
- var IntegrationModule = class {
583
- core;
584
- constructor(httpClient) {
585
- this.core = createIntegrationModule(httpClient, coreErrors);
586
- }
587
- executeResource(resourceId, params) {
588
- return this.core.executeResource(resourceId, params);
589
- }
590
- execute(configId, request) {
591
- return this.core.execute(configId, request);
592
- }
593
- };
594
-
595
- // src/modules/queries.ts
596
- import {
597
- createQueriesModule
598
- } from "@mitralab.io/sdk-core";
599
- var QueriesModule = class {
600
- dataSourceId = "";
601
- core;
602
- constructor(httpClient) {
603
- this.core = createQueriesModule(httpClient, () => this.dataSourceId, coreErrors);
604
- }
605
- /** Called by `client.init()` to set the app's resolved data source. */
606
- setDataSourceId(dataSourceId) {
607
- this.dataSourceId = dataSourceId;
608
- }
609
- async execute(id, parameters) {
610
- const result = await this.core.execute(id, parameters);
611
- return { ...result, affectedRows: result.affectedRows ?? null };
612
- }
613
- };
614
-
615
- // src/client.ts
616
- function expectAppInfoResponse(value) {
617
- const response = expectObject(
618
- value,
619
- "App info response",
620
- coreErrors
621
- );
622
- if (typeof response.dataSourceId !== "string" || !response.dataSourceId.trim()) {
623
- throw coreErrors.invalidResponse(
624
- "App info response has an invalid dataSourceId field"
625
- );
626
- }
627
- if (typeof response.allowSignup !== "boolean") {
628
- throw coreErrors.invalidResponse(
629
- "App info response has an invalid allowSignup field"
630
- );
631
- }
632
- return {
633
- dataSourceId: response.dataSourceId,
634
- allowSignup: response.allowSignup
635
- };
636
- }
637
- function createClient(config) {
638
- const { appId, apiUrl, onError } = config;
639
- const iamUrl = `${apiUrl}/iam`;
640
- const dataManagerUrl = `${apiUrl}/data-manager`;
641
- const functionsUrl = `${apiUrl}/functions`;
642
- const integrationUrl = `${apiUrl}/integration`;
643
- const codeStudioUrl = `${apiUrl}/code-studio`;
644
- const authModule = new AuthModule(appId, iamUrl);
645
- const onUnauthorized = () => authModule.refreshSession();
646
- const defaultHeaders = { "X-App-Id": appId };
647
- const httpClient = new HttpClient({
648
- baseUrl: dataManagerUrl,
649
- getToken: () => authModule.accessToken,
650
- onUnauthorized,
651
- onError,
652
- defaultHeaders
653
- });
654
- const entitiesModule = EntitiesModule.createProxy(httpClient, "");
655
- const functionsHttpClient = new HttpClient({
656
- baseUrl: functionsUrl,
657
- getToken: () => authModule.accessToken,
658
- onUnauthorized,
659
- onError,
660
- defaultHeaders
661
- });
662
- const functionsModule = new FunctionsModule(functionsHttpClient);
663
- const integrationHttpClient = new HttpClient({
664
- baseUrl: integrationUrl,
665
- getToken: () => authModule.accessToken,
666
- onUnauthorized,
667
- onError,
668
- defaultHeaders
669
- });
670
- const integrationModule = new IntegrationModule(integrationHttpClient);
671
- const queriesModule = new QueriesModule(httpClient);
672
- let initialized = false;
673
- let allowSignup = true;
674
- async function init() {
675
- if (initialized) return;
676
- const publicClient = new HttpClient({
677
- baseUrl: codeStudioUrl,
678
- getToken: () => null
679
- });
680
- const appInfo = expectAppInfoResponse(
681
- await publicClient.get(
682
- `/api/v1/apps/${encodePathSegment(appId, "appId", coreErrors)}/info`
683
- )
684
- );
685
- entitiesModule.setDataSourceId(appInfo.dataSourceId);
686
- queriesModule.setDataSourceId(appInfo.dataSourceId);
687
- allowSignup = appInfo.allowSignup;
688
- initialized = true;
689
- }
690
- return {
691
- init,
692
- auth: authModule,
693
- entities: entitiesModule,
694
- functions: functionsModule,
695
- integration: integrationModule,
696
- queries: queriesModule,
697
- get allowSignup() {
698
- return allowSignup;
699
- },
700
- config
701
- };
702
- }
703
- export {
704
- MitraApiError,
705
- createClient
706
- };