@mitralab.io/platform-sdk 1.0.6 → 1.0.8

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.js CHANGED
@@ -25,7 +25,35 @@ __export(index_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
+ // src/client.ts
29
+ var import_sdk_core6 = require("@mitralab.io/sdk-core");
30
+
28
31
  // src/utils/http-client.ts
32
+ var bearerCredentialPattern = /(Bearer\s+)\S+/gi;
33
+ function redactText(value, currentToken) {
34
+ const withoutBearerCredentials = value.replace(bearerCredentialPattern, "$1[REDACTED]");
35
+ return currentToken ? withoutBearerCredentials.split(currentToken).join("[REDACTED]") : withoutBearerCredentials;
36
+ }
37
+ function redactDetails(value, currentToken) {
38
+ if (typeof value === "string") return redactText(value, currentToken);
39
+ if (Array.isArray(value)) return value.map((item) => redactDetails(item, currentToken));
40
+ if (value && typeof value === "object") {
41
+ return Object.fromEntries(
42
+ Object.entries(value).map(([key, entry]) => [
43
+ redactText(key, currentToken),
44
+ redactDetails(entry, currentToken)
45
+ ])
46
+ );
47
+ }
48
+ return value;
49
+ }
50
+ function asErrorPayload(value) {
51
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
52
+ return value;
53
+ }
54
+ function optionalString(value) {
55
+ return typeof value === "string" ? value : void 0;
56
+ }
29
57
  var HttpClient = class {
30
58
  baseUrl;
31
59
  tokenGetter;
@@ -89,8 +117,18 @@ var HttpClient = class {
89
117
  const response = await fetch(url, {
90
118
  method,
91
119
  headers: requestHeaders,
92
- body: body ? JSON.stringify(body) : void 0
120
+ body: body ? JSON.stringify(body) : void 0,
121
+ redirect: "manual"
93
122
  });
123
+ if (response.redirected || response.type === "opaqueredirect") {
124
+ const error = new MitraApiError(
125
+ "Redirected responses are not allowed",
126
+ response.status,
127
+ "REDIRECT_NOT_ALLOWED"
128
+ );
129
+ this.onError?.(error);
130
+ throw error;
131
+ }
94
132
  if (!response.ok) {
95
133
  if (response.status === 401 && !isRetry && this.onUnauthorized) {
96
134
  const refreshed = await this.onUnauthorized();
@@ -99,11 +137,14 @@ var HttpClient = class {
99
137
  }
100
138
  }
101
139
  const errorBody = await response.json().catch(() => ({}));
140
+ const errorPayload = asErrorPayload(errorBody);
141
+ const rawMessage = optionalString(errorPayload.message);
142
+ const rawCode = optionalString(errorPayload.error_code);
102
143
  const error = new MitraApiError(
103
- errorBody.message || `Request failed with status ${response.status}`,
144
+ redactText(rawMessage || `Request failed with status ${response.status}`, token),
104
145
  response.status,
105
- errorBody.error_code,
106
- errorBody
146
+ rawCode === void 0 ? void 0 : redactText(rawCode, token),
147
+ redactDetails(errorBody, token)
107
148
  );
108
149
  this.onError?.(error);
109
150
  throw error;
@@ -184,22 +225,31 @@ var MitraApiError = class extends Error {
184
225
  }
185
226
  };
186
227
 
228
+ // src/core-errors.ts
229
+ var coreErrors = {
230
+ configuration: (message) => new MitraApiError(message, 0, "INVALID_CONFIGURATION"),
231
+ invalidResponse: (message) => new MitraApiError(message, 200, "INVALID_RESPONSE")
232
+ };
233
+
187
234
  // src/modules/auth.ts
235
+ var import_sdk_core = require("@mitralab.io/sdk-core");
188
236
  var AuthModule = class {
189
237
  appId;
190
238
  _currentUser = null;
191
- _accessToken = null;
192
- _refreshToken = null;
239
+ #accessToken = null;
240
+ #refreshToken = null;
193
241
  refreshPromise = null;
194
242
  listeners = /* @__PURE__ */ new Set();
195
243
  storageKey;
196
244
  publicClient;
197
245
  authedClient;
246
+ currentUserApi;
198
247
  constructor(appId, iamBaseUrl) {
199
248
  this.appId = appId;
200
249
  this.storageKey = `mitra_auth_${appId}`;
201
250
  this.publicClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => null });
202
- this.authedClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => this._accessToken });
251
+ this.authedClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => this.#accessToken });
252
+ this.currentUserApi = (0, import_sdk_core.createAuthModule)(this.authedClient, coreErrors);
203
253
  this.loadFromStorage();
204
254
  }
205
255
  /** The currently authenticated user, or null. */
@@ -208,11 +258,11 @@ var AuthModule = class {
208
258
  }
209
259
  /** The current JWT access token, or null. */
210
260
  get accessToken() {
211
- return this._accessToken;
261
+ return this.#accessToken;
212
262
  }
213
263
  /** Whether a user is currently authenticated (local check, not server-validated). */
214
264
  get isAuthenticated() {
215
- return this._currentUser !== null && this._accessToken !== null;
265
+ return this._currentUser !== null && this.#accessToken !== null;
216
266
  }
217
267
  /**
218
268
  * Signs in a user with email and password.
@@ -237,9 +287,9 @@ var AuthModule = class {
237
287
  "/api/v1/auth/login",
238
288
  { ...credentials, appId: this.appId }
239
289
  );
240
- this._accessToken = tokenResponse.accessToken;
241
- this._refreshToken = tokenResponse.refreshToken;
242
- const user = await this.authedClient.get("/api/v1/auth/me");
290
+ this.#accessToken = tokenResponse.accessToken;
291
+ this.#refreshToken = tokenResponse.refreshToken;
292
+ const user = await this.getCurrentUser();
243
293
  this.setAuthState(user, tokenResponse.accessToken, tokenResponse.refreshToken);
244
294
  return user;
245
295
  }
@@ -299,7 +349,7 @@ var AuthModule = class {
299
349
  * ```
300
350
  */
301
351
  async refreshSession() {
302
- if (!this._refreshToken) return false;
352
+ if (!this.#refreshToken) return false;
303
353
  if (this.refreshPromise) return this.refreshPromise;
304
354
  this.refreshPromise = this.doRefresh();
305
355
  try {
@@ -323,9 +373,9 @@ var AuthModule = class {
323
373
  * ```
324
374
  */
325
375
  async me() {
326
- if (!this._accessToken) return null;
376
+ if (!this.#accessToken) return null;
327
377
  try {
328
- const user = await this.authedClient.get("/api/v1/auth/me");
378
+ const user = await this.getCurrentUser();
329
379
  this._currentUser = user;
330
380
  this.saveToStorage();
331
381
  this.notifyListeners();
@@ -366,7 +416,7 @@ var AuthModule = class {
366
416
  * ```
367
417
  */
368
418
  setToken(token, saveToStorage = true) {
369
- this._accessToken = token;
419
+ this.#accessToken = token;
370
420
  if (saveToStorage) {
371
421
  this.saveToStorage();
372
422
  }
@@ -417,11 +467,11 @@ var AuthModule = class {
417
467
  try {
418
468
  const tokenResponse = await this.publicClient.post(
419
469
  "/api/v1/auth/refresh-token",
420
- { refreshToken: this._refreshToken }
470
+ { refreshToken: this.#refreshToken }
421
471
  );
422
- this._accessToken = tokenResponse.accessToken;
423
- this._refreshToken = tokenResponse.refreshToken;
424
- const user = await this.authedClient.get("/api/v1/auth/me");
472
+ this.#accessToken = tokenResponse.accessToken;
473
+ this.#refreshToken = tokenResponse.refreshToken;
474
+ const user = await this.getCurrentUser();
425
475
  this.setAuthState(user, tokenResponse.accessToken, tokenResponse.refreshToken);
426
476
  return true;
427
477
  } catch {
@@ -431,15 +481,19 @@ var AuthModule = class {
431
481
  }
432
482
  setAuthState(user, token, refreshToken) {
433
483
  this._currentUser = user;
434
- this._accessToken = token;
435
- this._refreshToken = refreshToken;
484
+ this.#accessToken = token;
485
+ this.#refreshToken = refreshToken;
436
486
  this.saveToStorage();
437
487
  this.notifyListeners();
438
488
  }
489
+ async getCurrentUser() {
490
+ const user = await this.currentUserApi.me();
491
+ return { ...user, tenantId: user.tenant.id };
492
+ }
439
493
  clearAuthState() {
440
494
  this._currentUser = null;
441
- this._accessToken = null;
442
- this._refreshToken = null;
495
+ this.#accessToken = null;
496
+ this.#refreshToken = null;
443
497
  this.removeFromStorage();
444
498
  this.notifyListeners();
445
499
  }
@@ -459,8 +513,8 @@ var AuthModule = class {
459
513
  this.storageKey,
460
514
  JSON.stringify({
461
515
  user: this._currentUser,
462
- token: this._accessToken,
463
- refreshToken: this._refreshToken
516
+ token: this.#accessToken,
517
+ refreshToken: this.#refreshToken
464
518
  })
465
519
  );
466
520
  } catch {
@@ -473,8 +527,8 @@ var AuthModule = class {
473
527
  if (stored) {
474
528
  const { user, token, refreshToken } = JSON.parse(stored);
475
529
  this._currentUser = user;
476
- this._accessToken = token;
477
- this._refreshToken = refreshToken ?? null;
530
+ this.#accessToken = token;
531
+ this.#refreshToken = refreshToken ?? null;
478
532
  }
479
533
  } catch {
480
534
  this.removeFromStorage();
@@ -490,208 +544,115 @@ var AuthModule = class {
490
544
  };
491
545
 
492
546
  // src/modules/entities.ts
547
+ var import_sdk_core2 = require("@mitralab.io/sdk-core");
493
548
  var EntitiesModule = class _EntitiesModule {
494
- httpClient;
495
- dataSourceId;
496
- tableProxies = /* @__PURE__ */ new Map();
497
549
  constructor(httpClient, dataSourceId) {
498
550
  this.httpClient = httpClient;
499
- this.dataSourceId = dataSourceId;
551
+ void dataSourceId;
552
+ this.core = (0, import_sdk_core2.createEntitiesModule)(httpClient, coreErrors);
500
553
  }
554
+ core;
501
555
  static createProxy(httpClient, dataSourceId) {
502
556
  const instance = new _EntitiesModule(httpClient, dataSourceId);
503
557
  return new Proxy(instance, {
504
- get: (target, prop) => {
505
- if (prop in target) {
506
- return target[prop];
558
+ get(target, property, receiver) {
559
+ if (typeof property !== "string" || property in target) {
560
+ return Reflect.get(target, property, receiver);
507
561
  }
508
- return target.getTable(prop);
562
+ return target.getTable(property);
509
563
  }
510
564
  });
511
565
  }
566
+ /**
567
+ * Preserved for Platform SDK 1.x compatibility.
568
+ * Records now resolve the app from authenticated context instead of a data source path.
569
+ */
512
570
  setDataSourceId(dataSourceId) {
513
- this.dataSourceId = dataSourceId;
514
- this.tableProxies.clear();
571
+ void dataSourceId;
572
+ this.core = (0, import_sdk_core2.createEntitiesModule)(this.httpClient, coreErrors);
515
573
  }
516
574
  getTable(tableName) {
517
- if (!this.tableProxies.has(tableName)) {
518
- this.tableProxies.set(tableName, this.createTableAccessor(tableName));
519
- }
520
- return this.tableProxies.get(tableName);
521
- }
522
- createTableAccessor(tableName) {
523
- const basePath = `/api/v1/data-sources/${this.dataSourceId}/tables/${tableName}/records`;
524
- return {
525
- list: async (sortOrOptions, limit, skip, fields) => {
526
- let params;
527
- if (typeof sortOrOptions === "string" || sortOrOptions === void 0) {
528
- params = {
529
- sort: sortOrOptions,
530
- limit,
531
- skip,
532
- fields: fields?.join(",")
533
- };
534
- } else {
535
- params = {
536
- sort: sortOrOptions.sort,
537
- limit: sortOrOptions.limit,
538
- skip: sortOrOptions.skip,
539
- fields: sortOrOptions.fields?.join(",")
540
- };
541
- }
542
- const response = await this.httpClient.get(basePath, params);
543
- return response.data;
544
- },
545
- filter: async (query, sort, limit, skip, fields) => {
546
- const params = {
547
- q: JSON.stringify(query),
548
- sort,
549
- limit,
550
- skip,
551
- fields: fields?.join(",")
552
- };
553
- const response = await this.httpClient.get(basePath, params);
554
- return response.data;
555
- },
556
- get: async (id) => {
557
- return this.httpClient.get(`${basePath}/${id}`);
558
- },
559
- create: async (data) => {
560
- return this.httpClient.post(basePath, data);
561
- },
562
- update: async (id, data) => {
563
- return this.httpClient.put(`${basePath}/${id}`, data);
564
- },
565
- delete: async (id) => {
566
- return this.httpClient.delete(`${basePath}/${id}`);
567
- },
568
- deleteMany: async (query) => {
569
- return this.httpClient.delete(basePath, {
570
- q: JSON.stringify(query)
571
- });
572
- },
573
- bulkCreate: async (data) => {
574
- return this.httpClient.post(`${basePath}/bulk`, data);
575
- }
576
- };
575
+ return this.core.getTable(tableName);
577
576
  }
578
577
  };
579
578
 
580
579
  // src/modules/functions.ts
580
+ var import_sdk_core3 = require("@mitralab.io/sdk-core");
581
581
  var FunctionsModule = class {
582
- httpClient;
582
+ core;
583
583
  constructor(httpClient) {
584
- this.httpClient = httpClient;
584
+ this.core = (0, import_sdk_core3.createFunctionsModule)(httpClient, { emptyInput: "omit-body" }, coreErrors);
585
585
  }
586
586
  /**
587
- * Executes a serverless function by ID.
588
- *
589
- * Triggers the function's current published version with the provided input.
590
- *
591
- * @param functionId - UUID of the function to execute.
592
- * @param input - Input data to pass to the function.
593
- * @returns The execution result with status, output, and metadata.
594
- * @throws {MitraApiError} On function not found (404) or unauthorized (401).
595
- *
596
- * @example
597
- * ```typescript
598
- * const execution = await mitra.functions.execute('fn-id', { key: 'value' });
599
- * console.log(execution.status, execution.output);
600
- * ```
587
+ * Executes a Function using the Platform SDK 1.x server-default invocation semantics.
588
+ * The runtime SDK uses an explicit invocation header instead.
601
589
  */
602
590
  async execute(functionId, input) {
603
- return this.httpClient.post(
604
- `/api/v1/functions/${functionId}/execute`,
605
- input ? { input } : void 0
606
- );
591
+ const execution = await this.core.execute(functionId, input);
592
+ if (execution.input === null) {
593
+ throw coreErrors.invalidResponse(
594
+ "Function execution response has an invalid input field"
595
+ );
596
+ }
597
+ return { ...execution, input: execution.input };
607
598
  }
608
599
  };
609
600
 
610
601
  // src/modules/integration.ts
602
+ var import_sdk_core4 = require("@mitralab.io/sdk-core");
611
603
  var IntegrationModule = class {
612
- httpClient;
604
+ core;
613
605
  constructor(httpClient) {
614
- this.httpClient = httpClient;
606
+ this.core = (0, import_sdk_core4.createIntegrationModule)(httpClient, coreErrors);
615
607
  }
616
- /**
617
- * Executes a pre-defined integration resource by ID.
618
- *
619
- * The resource's endpoint, method, and body are resolved server-side
620
- * using the provided parameters. Only declared parameters can be passed.
621
- *
622
- * @param resourceId - UUID of the integration resource.
623
- * @param params - Named parameters declared in the resource's params schema.
624
- * @returns Proxy result with status, headers, body, and execution metadata.
625
- * @throws {MitraApiError} On resource not found (404) or external API failure.
626
- *
627
- * @example
628
- * ```typescript
629
- * const result = await mitra.integration.executeResource('resource-id', {
630
- * descricao: 'Notebook',
631
- * limit: 10,
632
- * });
633
- * console.log(result.body);
634
- * ```
635
- */
636
- async executeResource(resourceId, params) {
637
- return this.httpClient.post(
638
- `/api/v1/proxy/resources/${resourceId}/execute`,
639
- { params }
640
- );
608
+ executeResource(resourceId, params) {
609
+ return this.core.executeResource(resourceId, params);
641
610
  }
642
- /**
643
- * Executes a proxied HTTP request through an integration config.
644
- *
645
- * The Mitra server handles authentication and injects credentials automatically.
646
- * Note: integrations configured with RESOURCE_ONLY mode will block direct proxy access.
647
- *
648
- * @param configId - UUID of the integration config.
649
- * @param request - The HTTP request to proxy (method, endpoint, body, etc.).
650
- * @returns Proxy result with status, headers, body, and execution metadata.
651
- * @throws {MitraApiError} On config not found (404) or external API failure.
652
- */
653
- async execute(configId, request) {
654
- return this.httpClient.post(
655
- `/api/v1/proxy/template-configs/${configId}/execute`,
656
- { ...request, source: "SDK" }
657
- );
611
+ execute(configId, request) {
612
+ return this.core.execute(configId, request);
658
613
  }
659
614
  };
660
615
 
661
616
  // src/modules/queries.ts
617
+ var import_sdk_core5 = require("@mitralab.io/sdk-core");
662
618
  var QueriesModule = class {
663
- httpClient;
664
619
  dataSourceId = "";
620
+ core;
665
621
  constructor(httpClient) {
666
- this.httpClient = httpClient;
622
+ this.core = (0, import_sdk_core5.createQueriesModule)(httpClient, () => this.dataSourceId, coreErrors);
667
623
  }
668
- /** @internal Called by client.init() to set the resolved data source. */
624
+ /** Called by `client.init()` to set the app's resolved data source. */
669
625
  setDataSourceId(dataSourceId) {
670
626
  this.dataSourceId = dataSourceId;
671
627
  }
672
- /**
673
- * Executes a named query.
674
- *
675
- * @param id - UUID of the custom query.
676
- * @param parameters - Named parameters for the prepared statement.
677
- * @returns Query result with rows and affected row count.
678
- * @throws {MitraApiError} On query not found (404).
679
- *
680
- * @example
681
- * ```typescript
682
- * const result = await mitra.queries.execute('query-id', { status: 'active' });
683
- * console.log(`Found ${result.rows.length} rows`);
684
- * ```
685
- */
686
628
  async execute(id, parameters) {
687
- return this.httpClient.post(
688
- `/api/v1/custom-queries/${id}/execute`,
689
- { datasourceId: this.dataSourceId, parameters }
690
- );
629
+ const result = await this.core.execute(id, parameters);
630
+ return { ...result, affectedRows: result.affectedRows ?? null };
691
631
  }
692
632
  };
693
633
 
694
634
  // src/client.ts
635
+ function expectAppInfoResponse(value) {
636
+ const response = (0, import_sdk_core6.expectObject)(
637
+ value,
638
+ "App info response",
639
+ coreErrors
640
+ );
641
+ if (typeof response.dataSourceId !== "string" || !response.dataSourceId.trim()) {
642
+ throw coreErrors.invalidResponse(
643
+ "App info response has an invalid dataSourceId field"
644
+ );
645
+ }
646
+ if (typeof response.allowSignup !== "boolean") {
647
+ throw coreErrors.invalidResponse(
648
+ "App info response has an invalid allowSignup field"
649
+ );
650
+ }
651
+ return {
652
+ dataSourceId: response.dataSourceId,
653
+ allowSignup: response.allowSignup
654
+ };
655
+ }
695
656
  function createClient(config) {
696
657
  const { appId, apiUrl, onError } = config;
697
658
  const iamUrl = `${apiUrl}/iam`;
@@ -735,8 +696,10 @@ function createClient(config) {
735
696
  baseUrl: codeStudioUrl,
736
697
  getToken: () => null
737
698
  });
738
- const appInfo = await publicClient.get(
739
- `/api/v1/apps/${appId}/info`
699
+ const appInfo = expectAppInfoResponse(
700
+ await publicClient.get(
701
+ `/api/v1/apps/${(0, import_sdk_core6.encodePathSegment)(appId, "appId", coreErrors)}/info`
702
+ )
740
703
  );
741
704
  entitiesModule.setDataSourceId(appInfo.dataSourceId);
742
705
  queriesModule.setDataSourceId(appInfo.dataSourceId);