@mitway/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,819 @@
1
+ // src/types.ts
2
+ var MitwayBaasError = class _MitwayBaasError extends Error {
3
+ statusCode;
4
+ error;
5
+ nextActions;
6
+ constructor(message, statusCode, error, nextActions) {
7
+ super(message);
8
+ this.name = "MitwayBaasError";
9
+ this.statusCode = statusCode;
10
+ this.error = error;
11
+ this.nextActions = nextActions;
12
+ }
13
+ static fromApiError(apiError) {
14
+ return new _MitwayBaasError(
15
+ apiError.message,
16
+ apiError.statusCode,
17
+ apiError.error,
18
+ apiError.nextActions
19
+ );
20
+ }
21
+ };
22
+
23
+ // src/lib/logger.ts
24
+ var SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie", "set-cookie"];
25
+ var SENSITIVE_BODY_KEYS = [
26
+ "password",
27
+ "token",
28
+ "accesstoken",
29
+ "refreshtoken",
30
+ "authorization",
31
+ "secret",
32
+ "apikey",
33
+ "api_key",
34
+ "email",
35
+ "ssn",
36
+ "creditcard",
37
+ "credit_card"
38
+ ];
39
+ function redactHeaders(headers) {
40
+ const redacted = {};
41
+ for (const [key, value] of Object.entries(headers)) {
42
+ if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
43
+ redacted[key] = "***REDACTED***";
44
+ } else {
45
+ redacted[key] = value;
46
+ }
47
+ }
48
+ return redacted;
49
+ }
50
+ function sanitizeBody(body) {
51
+ if (body === null || body === void 0) return body;
52
+ if (typeof body === "string") {
53
+ try {
54
+ const parsed = JSON.parse(body);
55
+ return sanitizeBody(parsed);
56
+ } catch {
57
+ return body;
58
+ }
59
+ }
60
+ if (Array.isArray(body)) return body.map(sanitizeBody);
61
+ if (typeof body === "object") {
62
+ const sanitized = {};
63
+ for (const [key, value] of Object.entries(body)) {
64
+ if (SENSITIVE_BODY_KEYS.includes(key.toLowerCase().replace(/[-_]/g, ""))) {
65
+ sanitized[key] = "***REDACTED***";
66
+ } else {
67
+ sanitized[key] = sanitizeBody(value);
68
+ }
69
+ }
70
+ return sanitized;
71
+ }
72
+ return body;
73
+ }
74
+ function formatBody(body) {
75
+ if (body === void 0 || body === null) return "";
76
+ if (typeof body === "string") {
77
+ try {
78
+ return JSON.stringify(JSON.parse(body), null, 2);
79
+ } catch {
80
+ return body;
81
+ }
82
+ }
83
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
84
+ return "[FormData]";
85
+ }
86
+ try {
87
+ return JSON.stringify(body, null, 2);
88
+ } catch {
89
+ return "[Unserializable body]";
90
+ }
91
+ }
92
+ var Logger = class {
93
+ enabled;
94
+ customLog;
95
+ constructor(debug) {
96
+ if (typeof debug === "function") {
97
+ this.enabled = true;
98
+ this.customLog = debug;
99
+ } else {
100
+ this.enabled = !!debug;
101
+ this.customLog = null;
102
+ }
103
+ }
104
+ log(message, ...args) {
105
+ if (!this.enabled) return;
106
+ const formatted = `[MITWAY-BaaS Debug] ${message}`;
107
+ if (this.customLog) {
108
+ this.customLog(formatted, ...args);
109
+ } else {
110
+ console.log(formatted, ...args);
111
+ }
112
+ }
113
+ warn(message, ...args) {
114
+ if (!this.enabled) return;
115
+ const formatted = `[MITWAY-BaaS Debug] ${message}`;
116
+ if (this.customLog) {
117
+ this.customLog(formatted, ...args);
118
+ } else {
119
+ console.warn(formatted, ...args);
120
+ }
121
+ }
122
+ error(message, ...args) {
123
+ if (!this.enabled) return;
124
+ const formatted = `[MITWAY-BaaS Debug] ${message}`;
125
+ if (this.customLog) {
126
+ this.customLog(formatted, ...args);
127
+ } else {
128
+ console.error(formatted, ...args);
129
+ }
130
+ }
131
+ logRequest(method, url, headers, body) {
132
+ if (!this.enabled) return;
133
+ const parts = [`\u2192 ${method} ${url}`];
134
+ if (headers && Object.keys(headers).length > 0) {
135
+ parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
136
+ }
137
+ const formattedBody = formatBody(sanitizeBody(body));
138
+ if (formattedBody) {
139
+ const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
140
+ parts.push(` Body: ${truncated}`);
141
+ }
142
+ this.log(parts.join("\n"));
143
+ }
144
+ logResponse(method, url, status, durationMs, body) {
145
+ if (!this.enabled) return;
146
+ const parts = [
147
+ `\u2190 ${method} ${url} ${status} (${durationMs}ms)`
148
+ ];
149
+ const formattedBody = formatBody(sanitizeBody(body));
150
+ if (formattedBody) {
151
+ const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
152
+ parts.push(` Body: ${truncated}`);
153
+ }
154
+ if (status >= 400) {
155
+ this.error(parts.join("\n"));
156
+ } else {
157
+ this.log(parts.join("\n"));
158
+ }
159
+ }
160
+ };
161
+
162
+ // src/lib/token-manager.ts
163
+ var CSRF_TOKEN_COOKIE = "mitway_baas_csrf_token";
164
+ function getCsrfToken() {
165
+ if (typeof document === "undefined") return null;
166
+ const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
167
+ if (!match) return null;
168
+ return match.split("=")[1] || null;
169
+ }
170
+ function setCsrfToken(token) {
171
+ if (typeof document === "undefined") return;
172
+ const maxAge = 7 * 24 * 60 * 60;
173
+ const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
174
+ document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
175
+ }
176
+ function clearCsrfToken() {
177
+ if (typeof document === "undefined") return;
178
+ const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
179
+ document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
180
+ }
181
+ var TokenManager = class {
182
+ accessToken = null;
183
+ user = null;
184
+ /** Fired when the access token changes (used by long-lived consumers). */
185
+ onTokenChange = null;
186
+ saveSession(session) {
187
+ const tokenChanged = session.accessToken !== this.accessToken;
188
+ this.accessToken = session.accessToken;
189
+ this.user = session.user;
190
+ if (tokenChanged && this.onTokenChange) {
191
+ this.onTokenChange();
192
+ }
193
+ }
194
+ getSession() {
195
+ if (!this.accessToken || !this.user) return null;
196
+ return {
197
+ accessToken: this.accessToken,
198
+ user: this.user
199
+ };
200
+ }
201
+ getAccessToken() {
202
+ return this.accessToken;
203
+ }
204
+ setAccessToken(token) {
205
+ const tokenChanged = token !== this.accessToken;
206
+ this.accessToken = token;
207
+ if (tokenChanged && this.onTokenChange) {
208
+ this.onTokenChange();
209
+ }
210
+ }
211
+ getUser() {
212
+ return this.user;
213
+ }
214
+ setUser(user) {
215
+ this.user = user;
216
+ }
217
+ clearSession() {
218
+ const hadToken = this.accessToken !== null;
219
+ this.accessToken = null;
220
+ this.user = null;
221
+ if (hadToken && this.onTokenChange) {
222
+ this.onTokenChange();
223
+ }
224
+ }
225
+ };
226
+
227
+ // src/lib/http-client.ts
228
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
229
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
230
+ var HttpClient = class {
231
+ baseUrl;
232
+ fetch;
233
+ defaultHeaders;
234
+ anonKey;
235
+ userToken = null;
236
+ logger;
237
+ autoRefreshToken = true;
238
+ isRefreshing = false;
239
+ refreshPromise = null;
240
+ tokenManager;
241
+ refreshToken = null;
242
+ timeout;
243
+ retryCount;
244
+ retryDelay;
245
+ constructor(config, tokenManager, logger) {
246
+ this.baseUrl = config.baseUrl || "http://localhost:7130";
247
+ this.autoRefreshToken = config.autoRefreshToken ?? true;
248
+ this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
249
+ this.anonKey = config.anonKey;
250
+ this.defaultHeaders = {
251
+ ...config.headers
252
+ };
253
+ this.tokenManager = tokenManager ?? new TokenManager();
254
+ this.logger = logger || new Logger(false);
255
+ this.timeout = config.timeout ?? 3e4;
256
+ this.retryCount = config.retryCount ?? 3;
257
+ this.retryDelay = config.retryDelay ?? 500;
258
+ if (!this.fetch) {
259
+ throw new Error(
260
+ "Fetch is not available. Provide a fetch implementation in the SDK config."
261
+ );
262
+ }
263
+ }
264
+ buildUrl(path, params) {
265
+ const url = new URL(path, this.baseUrl);
266
+ if (params) {
267
+ Object.entries(params).forEach(([key, value]) => {
268
+ if (key === "select") {
269
+ let normalizedValue = value.replace(/\s+/g, " ").trim();
270
+ normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
271
+ url.searchParams.append(key, normalizedValue);
272
+ } else {
273
+ url.searchParams.append(key, value);
274
+ }
275
+ });
276
+ }
277
+ return url.toString();
278
+ }
279
+ isRetryableStatus(status) {
280
+ return RETRYABLE_STATUS_CODES.has(status);
281
+ }
282
+ computeRetryDelay(attempt) {
283
+ const base = this.retryDelay * Math.pow(2, attempt - 1);
284
+ const jitter = base * (0.85 + Math.random() * 0.3);
285
+ return Math.round(jitter);
286
+ }
287
+ async handleRequest(method, path, options = {}) {
288
+ const {
289
+ params,
290
+ headers = {},
291
+ body,
292
+ signal: callerSignal,
293
+ ...fetchOptions
294
+ } = options;
295
+ const url = this.buildUrl(path, params);
296
+ const startTime = Date.now();
297
+ const canRetry = IDEMPOTENT_METHODS.has(method.toUpperCase()) || options.idempotent === true;
298
+ const maxAttempts = canRetry ? this.retryCount : 0;
299
+ const requestHeaders = {
300
+ ...this.defaultHeaders
301
+ };
302
+ const authToken = this.userToken || this.anonKey;
303
+ if (authToken) {
304
+ requestHeaders["Authorization"] = `Bearer ${authToken}`;
305
+ }
306
+ let processedBody;
307
+ if (body !== void 0) {
308
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
309
+ processedBody = body;
310
+ } else {
311
+ if (method !== "GET") {
312
+ requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
313
+ }
314
+ processedBody = JSON.stringify(body);
315
+ }
316
+ }
317
+ if (headers instanceof Headers) {
318
+ headers.forEach((value, key) => {
319
+ requestHeaders[key] = value;
320
+ });
321
+ } else if (Array.isArray(headers)) {
322
+ headers.forEach(([key, value]) => {
323
+ requestHeaders[key] = value;
324
+ });
325
+ } else {
326
+ Object.assign(requestHeaders, headers);
327
+ }
328
+ this.logger.logRequest(method, url, requestHeaders, processedBody);
329
+ let lastError;
330
+ for (let attempt = 0; attempt <= maxAttempts; attempt++) {
331
+ if (attempt > 0) {
332
+ const delay = this.computeRetryDelay(attempt);
333
+ this.logger.warn(
334
+ `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
335
+ );
336
+ if (callerSignal?.aborted) throw callerSignal.reason;
337
+ await new Promise((resolve, reject) => {
338
+ const onAbort = () => {
339
+ clearTimeout(timer2);
340
+ reject(callerSignal.reason);
341
+ };
342
+ const timer2 = setTimeout(() => {
343
+ if (callerSignal)
344
+ callerSignal.removeEventListener("abort", onAbort);
345
+ resolve();
346
+ }, delay);
347
+ if (callerSignal) {
348
+ callerSignal.addEventListener("abort", onAbort, { once: true });
349
+ }
350
+ });
351
+ }
352
+ let controller;
353
+ let timer;
354
+ if (this.timeout > 0 || callerSignal) {
355
+ controller = new AbortController();
356
+ if (this.timeout > 0) {
357
+ timer = setTimeout(() => controller.abort(), this.timeout);
358
+ }
359
+ if (callerSignal) {
360
+ if (callerSignal.aborted) {
361
+ controller.abort(callerSignal.reason);
362
+ } else {
363
+ const onCallerAbort = () => controller.abort(callerSignal.reason);
364
+ callerSignal.addEventListener("abort", onCallerAbort, { once: true });
365
+ controller.signal.addEventListener(
366
+ "abort",
367
+ () => {
368
+ callerSignal.removeEventListener("abort", onCallerAbort);
369
+ },
370
+ { once: true }
371
+ );
372
+ }
373
+ }
374
+ }
375
+ try {
376
+ const response = await this.fetch(url, {
377
+ method,
378
+ headers: requestHeaders,
379
+ body: processedBody,
380
+ ...fetchOptions,
381
+ ...controller ? { signal: controller.signal } : {}
382
+ });
383
+ if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
384
+ if (timer !== void 0) clearTimeout(timer);
385
+ await response.body?.cancel();
386
+ lastError = new MitwayBaasError(
387
+ `Server error: ${response.status} ${response.statusText}`,
388
+ response.status,
389
+ "SERVER_ERROR"
390
+ );
391
+ continue;
392
+ }
393
+ if (response.status === 204) {
394
+ if (timer !== void 0) clearTimeout(timer);
395
+ return void 0;
396
+ }
397
+ let data;
398
+ const contentType = response.headers.get("content-type");
399
+ try {
400
+ if (contentType?.includes("json")) {
401
+ data = await response.json();
402
+ } else {
403
+ data = await response.text();
404
+ }
405
+ } catch (parseErr) {
406
+ if (timer !== void 0) clearTimeout(timer);
407
+ throw new MitwayBaasError(
408
+ `Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
409
+ response.status,
410
+ response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
411
+ );
412
+ }
413
+ if (timer !== void 0) clearTimeout(timer);
414
+ if (!response.ok) {
415
+ this.logger.logResponse(
416
+ method,
417
+ url,
418
+ response.status,
419
+ Date.now() - startTime,
420
+ data
421
+ );
422
+ if (data && typeof data === "object" && "error" in data) {
423
+ if (!data.statusCode && !data.status) {
424
+ data.statusCode = response.status;
425
+ }
426
+ const error = MitwayBaasError.fromApiError(data);
427
+ Object.keys(data).forEach((key) => {
428
+ if (key !== "error" && key !== "message" && key !== "statusCode") {
429
+ error[key] = data[key];
430
+ }
431
+ });
432
+ throw error;
433
+ }
434
+ throw new MitwayBaasError(
435
+ `Request failed: ${response.statusText}`,
436
+ response.status,
437
+ "REQUEST_FAILED"
438
+ );
439
+ }
440
+ this.logger.logResponse(
441
+ method,
442
+ url,
443
+ response.status,
444
+ Date.now() - startTime,
445
+ data
446
+ );
447
+ return data;
448
+ } catch (err) {
449
+ if (timer !== void 0) clearTimeout(timer);
450
+ if (err?.name === "AbortError") {
451
+ if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
452
+ throw new MitwayBaasError(
453
+ `Request timed out after ${this.timeout}ms`,
454
+ 408,
455
+ "REQUEST_TIMEOUT"
456
+ );
457
+ }
458
+ throw err;
459
+ }
460
+ if (err instanceof MitwayBaasError) {
461
+ throw err;
462
+ }
463
+ if (attempt < maxAttempts) {
464
+ lastError = err;
465
+ continue;
466
+ }
467
+ throw new MitwayBaasError(
468
+ `Network request failed: ${err?.message || "Unknown error"}`,
469
+ 0,
470
+ "NETWORK_ERROR"
471
+ );
472
+ }
473
+ }
474
+ throw lastError || new MitwayBaasError(
475
+ "Request failed after all retry attempts",
476
+ 0,
477
+ "NETWORK_ERROR"
478
+ );
479
+ }
480
+ async request(method, path, options = {}) {
481
+ try {
482
+ return await this.handleRequest(method, path, { ...options });
483
+ } catch (error) {
484
+ if (error instanceof MitwayBaasError && error.statusCode === 401 && error.error === "INVALID_TOKEN" && this.autoRefreshToken) {
485
+ try {
486
+ const newTokenData = await this.handleTokenRefresh();
487
+ this.setAuthToken(newTokenData.accessToken);
488
+ this.tokenManager.saveSession(newTokenData);
489
+ if (newTokenData.csrfToken) {
490
+ setCsrfToken(newTokenData.csrfToken);
491
+ }
492
+ if (newTokenData.refreshToken) {
493
+ this.setRefreshToken(newTokenData.refreshToken);
494
+ }
495
+ return await this.handleRequest(method, path, { ...options });
496
+ } catch (refreshError) {
497
+ this.tokenManager.clearSession();
498
+ this.userToken = null;
499
+ this.refreshToken = null;
500
+ clearCsrfToken();
501
+ throw refreshError;
502
+ }
503
+ }
504
+ throw error;
505
+ }
506
+ }
507
+ get(path, options) {
508
+ return this.request("GET", path, options);
509
+ }
510
+ post(path, body, options) {
511
+ return this.request("POST", path, { ...options, body });
512
+ }
513
+ put(path, body, options) {
514
+ return this.request("PUT", path, { ...options, body });
515
+ }
516
+ patch(path, body, options) {
517
+ return this.request("PATCH", path, { ...options, body });
518
+ }
519
+ delete(path, options) {
520
+ return this.request("DELETE", path, options);
521
+ }
522
+ setAuthToken(token) {
523
+ this.userToken = token;
524
+ }
525
+ setRefreshToken(token) {
526
+ this.refreshToken = token;
527
+ }
528
+ getHeaders() {
529
+ const headers = { ...this.defaultHeaders };
530
+ const authToken = this.userToken || this.anonKey;
531
+ if (authToken) {
532
+ headers["Authorization"] = `Bearer ${authToken}`;
533
+ }
534
+ return headers;
535
+ }
536
+ /**
537
+ * Refresh the current session by calling the MITWAY-BaaS refresh endpoint.
538
+ * Note: the route is `/api/auth/refresh` (POST), not the InsForge
539
+ * `/api/auth/sessions/current` route. Returns the new access token + user.
540
+ */
541
+ async handleTokenRefresh() {
542
+ if (this.isRefreshing) {
543
+ return this.refreshPromise;
544
+ }
545
+ this.isRefreshing = true;
546
+ this.refreshPromise = (async () => {
547
+ try {
548
+ const csrfToken = getCsrfToken();
549
+ const body = this.refreshToken ? { refreshToken: this.refreshToken } : void 0;
550
+ const response = await this.handleRequest(
551
+ "POST",
552
+ "/api/auth/refresh",
553
+ {
554
+ body,
555
+ headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
556
+ credentials: "include"
557
+ }
558
+ );
559
+ return response;
560
+ } finally {
561
+ this.isRefreshing = false;
562
+ this.refreshPromise = null;
563
+ }
564
+ })();
565
+ return this.refreshPromise;
566
+ }
567
+ };
568
+
569
+ // src/modules/auth.ts
570
+ function wrapError(error, fallbackMessage) {
571
+ if (error instanceof MitwayBaasError) {
572
+ return { data: null, error };
573
+ }
574
+ return {
575
+ data: null,
576
+ error: new MitwayBaasError(
577
+ error instanceof Error ? error.message : fallbackMessage,
578
+ 500,
579
+ "AUTH_ERROR"
580
+ )
581
+ };
582
+ }
583
+ var Auth = class {
584
+ constructor(http, tokenManager) {
585
+ this.http = http;
586
+ this.tokenManager = tokenManager;
587
+ }
588
+ http;
589
+ tokenManager;
590
+ /**
591
+ * Persist the session in memory + HttpClient defaults so subsequent
592
+ * requests carry the new bearer token automatically.
593
+ */
594
+ saveSessionFromResponse(response) {
595
+ const session = {
596
+ accessToken: response.accessToken,
597
+ user: response.user
598
+ };
599
+ if (response.csrfToken) {
600
+ setCsrfToken(response.csrfToken);
601
+ }
602
+ this.tokenManager.saveSession(session);
603
+ this.http.setAuthToken(response.accessToken);
604
+ this.http.setRefreshToken(response.refreshToken ?? null);
605
+ }
606
+ /**
607
+ * Create a new user account and start a session.
608
+ *
609
+ * @example
610
+ * const { data, error } = await client.auth.signUp({
611
+ * email: 'a@b.com',
612
+ * password: 'a-strong-password',
613
+ * name: 'Alice'
614
+ * });
615
+ */
616
+ async signUp(request) {
617
+ try {
618
+ const response = await this.http.post(
619
+ "/api/auth/register",
620
+ request,
621
+ { credentials: "include" }
622
+ );
623
+ if (response?.accessToken && response.user) {
624
+ this.saveSessionFromResponse(response);
625
+ }
626
+ return { data: response, error: null };
627
+ } catch (error) {
628
+ return wrapError(error, "Sign up failed");
629
+ }
630
+ }
631
+ /**
632
+ * Sign in with email + password and start a session.
633
+ */
634
+ async signInWithPassword(request) {
635
+ try {
636
+ const response = await this.http.post(
637
+ "/api/auth/login",
638
+ request,
639
+ { credentials: "include" }
640
+ );
641
+ if (response?.accessToken && response.user) {
642
+ this.saveSessionFromResponse(response);
643
+ }
644
+ return { data: response, error: null };
645
+ } catch (error) {
646
+ return wrapError(error, "Sign in failed");
647
+ }
648
+ }
649
+ /**
650
+ * End the current session. Clears in-memory state even if the backend
651
+ * call fails (network/offline).
652
+ */
653
+ async signOut() {
654
+ try {
655
+ try {
656
+ await this.http.post("/api/auth/logout", void 0, {
657
+ credentials: "include"
658
+ });
659
+ } catch {
660
+ }
661
+ this.tokenManager.clearSession();
662
+ this.http.setAuthToken(null);
663
+ this.http.setRefreshToken(null);
664
+ clearCsrfToken();
665
+ return { error: null };
666
+ } catch {
667
+ return {
668
+ error: new MitwayBaasError("Failed to sign out", 500, "SIGNOUT_ERROR")
669
+ };
670
+ }
671
+ }
672
+ /**
673
+ * Manually refresh the current session. The HttpClient will call this
674
+ * automatically on 401 INVALID_TOKEN responses; consumers usually do
675
+ * not need to call it directly.
676
+ */
677
+ async refreshSession() {
678
+ try {
679
+ const response = await this.http.handleTokenRefresh();
680
+ if (response?.accessToken && response.user) {
681
+ this.saveSessionFromResponse(response);
682
+ }
683
+ return { data: response, error: null };
684
+ } catch (error) {
685
+ return wrapError(error, "Session refresh failed");
686
+ }
687
+ }
688
+ /**
689
+ * Get the current in-memory session, or null if the user is not signed in.
690
+ * Synchronous — does not hit the network.
691
+ */
692
+ getSession() {
693
+ return this.tokenManager.getSession();
694
+ }
695
+ /**
696
+ * Get the current in-memory user, or null if not signed in.
697
+ */
698
+ getUser() {
699
+ return this.tokenManager.getUser();
700
+ }
701
+ };
702
+
703
+ // src/modules/database.ts
704
+ import { PostgrestClient } from "@supabase/postgrest-js";
705
+ function createAuthedFetch(tokenManager, anonKey) {
706
+ return async (input, init) => {
707
+ const headers = new Headers(init?.headers);
708
+ if (!headers.has("Authorization")) {
709
+ const token = tokenManager.getAccessToken() ?? anonKey;
710
+ if (token) {
711
+ headers.set("Authorization", `Bearer ${token}`);
712
+ }
713
+ }
714
+ return fetch(input, { ...init, headers });
715
+ };
716
+ }
717
+ var Database = class {
718
+ postgrest;
719
+ postgrestUrl;
720
+ constructor(tokenManager, postgrestUrl, anonKey) {
721
+ this.postgrestUrl = postgrestUrl;
722
+ if (postgrestUrl) {
723
+ this.postgrest = new PostgrestClient(postgrestUrl, {
724
+ fetch: createAuthedFetch(tokenManager, anonKey),
725
+ headers: {}
726
+ });
727
+ } else {
728
+ this.postgrest = null;
729
+ }
730
+ }
731
+ requireClient() {
732
+ if (!this.postgrest) {
733
+ throw new MitwayBaasError(
734
+ "Database is not configured. Pass `postgrestUrl` in the SDK config to enable database operations.",
735
+ 500,
736
+ "DATABASE_NOT_CONFIGURED"
737
+ );
738
+ }
739
+ return this.postgrest;
740
+ }
741
+ /**
742
+ * Build a PostgREST query against a table.
743
+ *
744
+ * @example
745
+ * const { data, error } = await client.database
746
+ * .from('posts')
747
+ * .select('*')
748
+ * .eq('user_id', userId)
749
+ * .order('created_at', { ascending: false })
750
+ * .limit(10);
751
+ *
752
+ * @example
753
+ * const { data, error } = await client.database
754
+ * .from('posts')
755
+ * .insert({ title: 'Hello', content: 'World' })
756
+ * .select()
757
+ * .single();
758
+ */
759
+ from(table) {
760
+ return this.requireClient().from(table);
761
+ }
762
+ /**
763
+ * Call a PostgreSQL stored function (RPC).
764
+ *
765
+ * @example
766
+ * const { data, error } = await client.database
767
+ * .rpc('get_user_stats', { user_id: 123 });
768
+ */
769
+ rpc(fn, args, options) {
770
+ return this.requireClient().rpc(fn, args, options);
771
+ }
772
+ /**
773
+ * The PostgREST URL the database client is talking to, or undefined if
774
+ * the database module is not configured.
775
+ */
776
+ getUrl() {
777
+ return this.postgrestUrl;
778
+ }
779
+ };
780
+
781
+ // src/client.ts
782
+ var MitwayBaasClient = class {
783
+ http;
784
+ tokenManager;
785
+ auth;
786
+ database;
787
+ constructor(config = {}) {
788
+ const logger = new Logger(config.debug);
789
+ this.tokenManager = new TokenManager();
790
+ this.http = new HttpClient(config, this.tokenManager, logger);
791
+ this.auth = new Auth(this.http, this.tokenManager);
792
+ this.database = new Database(this.tokenManager, config.postgrestUrl, config.anonKey);
793
+ }
794
+ /**
795
+ * Escape hatch for callers that need to make custom requests against the
796
+ * backend without going through `auth` or `database`.
797
+ */
798
+ getHttpClient() {
799
+ return this.http;
800
+ }
801
+ };
802
+
803
+ // src/index.ts
804
+ function createClient(config) {
805
+ return new MitwayBaasClient(config);
806
+ }
807
+ var index_default = MitwayBaasClient;
808
+ export {
809
+ Auth,
810
+ Database,
811
+ HttpClient,
812
+ Logger,
813
+ MitwayBaasClient,
814
+ MitwayBaasError,
815
+ TokenManager,
816
+ createClient,
817
+ index_default as default
818
+ };
819
+ //# sourceMappingURL=index.js.map