@mitway/sdk 0.5.0 → 0.7.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 CHANGED
@@ -1,2054 +1,5 @@
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
- var DEFAULT_STORAGE_KEY = "mitway_baas_session";
165
- function getCsrfToken() {
166
- if (typeof document === "undefined") return null;
167
- const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
168
- if (!match) return null;
169
- return match.split("=")[1] || null;
170
- }
171
- function setCsrfToken(token) {
172
- if (typeof document === "undefined") return;
173
- const maxAge = 7 * 24 * 60 * 60;
174
- const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
175
- document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
176
- }
177
- function clearCsrfToken() {
178
- if (typeof document === "undefined") return;
179
- const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
180
- document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
181
- }
182
- var TokenManager = class {
183
- accessToken = null;
184
- refreshToken = null;
185
- user = null;
186
- persistSession;
187
- storageKey;
188
- /** Fired when the access token changes (used by long-lived consumers). */
189
- onTokenChange = null;
190
- constructor(opts) {
191
- this.persistSession = opts?.persistSession ?? true;
192
- this.storageKey = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
193
- }
194
- saveSession(session) {
195
- const tokenChanged = session.accessToken !== this.accessToken;
196
- this.accessToken = session.accessToken;
197
- this.user = session.user;
198
- if (session.refreshToken !== void 0) {
199
- this.refreshToken = session.refreshToken ?? null;
200
- }
201
- this.persist();
202
- if (tokenChanged && this.onTokenChange) {
203
- this.onTokenChange();
204
- }
205
- }
206
- getSession() {
207
- if (!this.accessToken || !this.user) return null;
208
- return {
209
- accessToken: this.accessToken,
210
- refreshToken: this.refreshToken ?? void 0,
211
- user: this.user
212
- };
213
- }
214
- getAccessToken() {
215
- return this.accessToken;
216
- }
217
- setAccessToken(token) {
218
- const tokenChanged = token !== this.accessToken;
219
- this.accessToken = token;
220
- this.persist();
221
- if (tokenChanged && this.onTokenChange) {
222
- this.onTokenChange();
223
- }
224
- }
225
- getRefreshToken() {
226
- return this.refreshToken;
227
- }
228
- setRefreshToken(token) {
229
- this.refreshToken = token;
230
- this.persist();
231
- }
232
- getUser() {
233
- return this.user;
234
- }
235
- setUser(user) {
236
- this.user = user;
237
- this.persist();
238
- }
239
- clearSession() {
240
- const hadToken = this.accessToken !== null;
241
- this.accessToken = null;
242
- this.refreshToken = null;
243
- this.user = null;
244
- this.removePersisted();
245
- if (hadToken && this.onTokenChange) {
246
- this.onTokenChange();
247
- }
248
- }
249
- /**
250
- * Restore the session from localStorage. Returns true if a persisted
251
- * session was found and loaded into memory.
252
- */
253
- restoreSession() {
254
- if (!this.persistSession || typeof localStorage === "undefined") return false;
255
- try {
256
- const raw = localStorage.getItem(this.storageKey);
257
- if (!raw) return false;
258
- const stored = JSON.parse(raw);
259
- if (!stored.accessToken || !stored.user) return false;
260
- this.accessToken = stored.accessToken;
261
- this.refreshToken = stored.refreshToken ?? null;
262
- this.user = stored.user;
263
- return true;
264
- } catch {
265
- return false;
266
- }
267
- }
268
- persist() {
269
- if (!this.persistSession || typeof localStorage === "undefined") return;
270
- if (!this.accessToken || !this.user) return;
271
- try {
272
- const data = {
273
- accessToken: this.accessToken,
274
- user: this.user
275
- };
276
- if (this.refreshToken) {
277
- data.refreshToken = this.refreshToken;
278
- }
279
- localStorage.setItem(this.storageKey, JSON.stringify(data));
280
- } catch {
281
- }
282
- }
283
- removePersisted() {
284
- if (!this.persistSession || typeof localStorage === "undefined") return;
285
- try {
286
- localStorage.removeItem(this.storageKey);
287
- } catch {
288
- }
289
- }
290
- };
291
-
292
- // src/lib/auth-envelope.ts
293
- function normalizeAuthPayload(raw) {
294
- if (!raw || typeof raw !== "object") return raw;
295
- const src = raw;
296
- const out = { ...src };
297
- let mutated = false;
298
- if ("access_token" in src && !("accessToken" in src)) {
299
- out.accessToken = src.access_token;
300
- delete out.access_token;
301
- mutated = true;
302
- }
303
- if ("csrf_token" in src && !("csrfToken" in src)) {
304
- out.csrfToken = src.csrf_token;
305
- delete out.csrf_token;
306
- mutated = true;
307
- }
308
- if ("refresh_token" in src && !("refreshToken" in src)) {
309
- out.refreshToken = src.refresh_token;
310
- delete out.refresh_token;
311
- mutated = true;
312
- }
313
- return mutated ? out : raw;
314
- }
315
-
316
- // src/lib/http-client.ts
317
- var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
318
- var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
319
- var HttpClient = class {
320
- baseUrl;
321
- fetch;
322
- defaultHeaders;
323
- anonKey;
324
- userToken = null;
325
- logger;
326
- autoRefreshToken = true;
327
- isRefreshing = false;
328
- refreshPromise = null;
329
- tokenManager;
330
- refreshToken = null;
331
- timeout;
332
- retryCount;
333
- retryDelay;
334
- constructor(config, tokenManager, logger) {
335
- this.baseUrl = config.baseUrl || "http://localhost:7130";
336
- this.autoRefreshToken = config.autoRefreshToken ?? true;
337
- this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
338
- this.anonKey = config.anonKey;
339
- this.defaultHeaders = {
340
- ...config.headers
341
- };
342
- this.tokenManager = tokenManager ?? new TokenManager();
343
- this.logger = logger || new Logger(false);
344
- this.timeout = config.timeout ?? 3e4;
345
- this.retryCount = config.retryCount ?? 3;
346
- this.retryDelay = config.retryDelay ?? 500;
347
- if (!this.fetch) {
348
- throw new Error(
349
- "Fetch is not available. Provide a fetch implementation in the SDK config."
350
- );
351
- }
352
- }
353
- buildUrl(path, params) {
354
- const url = new URL(path, this.baseUrl);
355
- if (params) {
356
- Object.entries(params).forEach(([key, value]) => {
357
- if (key === "select") {
358
- let normalizedValue = value.replace(/\s+/g, " ").trim();
359
- normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
360
- url.searchParams.append(key, normalizedValue);
361
- } else {
362
- url.searchParams.append(key, value);
363
- }
364
- });
365
- }
366
- return url.toString();
367
- }
368
- isRetryableStatus(status) {
369
- return RETRYABLE_STATUS_CODES.has(status);
370
- }
371
- computeRetryDelay(attempt) {
372
- const base = this.retryDelay * Math.pow(2, attempt - 1);
373
- const jitter = base * (0.85 + Math.random() * 0.3);
374
- return Math.round(jitter);
375
- }
376
- async handleRequest(method, path, options = {}) {
377
- const {
378
- params,
379
- headers = {},
380
- body,
381
- signal: callerSignal,
382
- ...fetchOptions
383
- } = options;
384
- const url = this.buildUrl(path, params);
385
- const startTime = Date.now();
386
- const canRetry = IDEMPOTENT_METHODS.has(method.toUpperCase()) || options.idempotent === true;
387
- const maxAttempts = canRetry ? this.retryCount : 0;
388
- const requestHeaders = {
389
- ...this.defaultHeaders
390
- };
391
- const authToken = this.userToken || this.anonKey;
392
- if (authToken) {
393
- requestHeaders["Authorization"] = `Bearer ${authToken}`;
394
- }
395
- let processedBody;
396
- if (body !== void 0) {
397
- if (typeof FormData !== "undefined" && body instanceof FormData) {
398
- processedBody = body;
399
- } else {
400
- if (method !== "GET") {
401
- requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
402
- }
403
- processedBody = JSON.stringify(body);
404
- }
405
- }
406
- if (headers instanceof Headers) {
407
- headers.forEach((value, key) => {
408
- requestHeaders[key] = value;
409
- });
410
- } else if (Array.isArray(headers)) {
411
- headers.forEach(([key, value]) => {
412
- requestHeaders[key] = value;
413
- });
414
- } else {
415
- Object.assign(requestHeaders, headers);
416
- }
417
- this.logger.logRequest(method, url, requestHeaders, processedBody);
418
- let lastError;
419
- for (let attempt = 0; attempt <= maxAttempts; attempt++) {
420
- if (attempt > 0) {
421
- const delay = this.computeRetryDelay(attempt);
422
- this.logger.warn(
423
- `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
424
- );
425
- if (callerSignal?.aborted) throw callerSignal.reason;
426
- await new Promise((resolve, reject) => {
427
- const onAbort = () => {
428
- clearTimeout(timer2);
429
- reject(callerSignal.reason);
430
- };
431
- const timer2 = setTimeout(() => {
432
- if (callerSignal)
433
- callerSignal.removeEventListener("abort", onAbort);
434
- resolve();
435
- }, delay);
436
- if (callerSignal) {
437
- callerSignal.addEventListener("abort", onAbort, { once: true });
438
- }
439
- });
440
- }
441
- let controller;
442
- let timer;
443
- if (this.timeout > 0 || callerSignal) {
444
- controller = new AbortController();
445
- if (this.timeout > 0) {
446
- timer = setTimeout(() => controller.abort(), this.timeout);
447
- }
448
- if (callerSignal) {
449
- if (callerSignal.aborted) {
450
- controller.abort(callerSignal.reason);
451
- } else {
452
- const onCallerAbort = () => controller.abort(callerSignal.reason);
453
- callerSignal.addEventListener("abort", onCallerAbort, { once: true });
454
- controller.signal.addEventListener(
455
- "abort",
456
- () => {
457
- callerSignal.removeEventListener("abort", onCallerAbort);
458
- },
459
- { once: true }
460
- );
461
- }
462
- }
463
- }
464
- try {
465
- const response = await this.fetch(url, {
466
- method,
467
- headers: requestHeaders,
468
- body: processedBody,
469
- ...fetchOptions,
470
- ...controller ? { signal: controller.signal } : {}
471
- });
472
- if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
473
- if (timer !== void 0) clearTimeout(timer);
474
- await response.body?.cancel();
475
- lastError = new MitwayBaasError(
476
- `Server error: ${response.status} ${response.statusText}`,
477
- response.status,
478
- "SERVER_ERROR"
479
- );
480
- continue;
481
- }
482
- if (response.status === 204) {
483
- if (timer !== void 0) clearTimeout(timer);
484
- return void 0;
485
- }
486
- let data;
487
- const contentType = response.headers.get("content-type");
488
- try {
489
- if (contentType?.includes("json")) {
490
- data = await response.json();
491
- } else {
492
- data = await response.text();
493
- }
494
- } catch (parseErr) {
495
- if (timer !== void 0) clearTimeout(timer);
496
- throw new MitwayBaasError(
497
- `Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
498
- response.status,
499
- response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
500
- );
501
- }
502
- if (timer !== void 0) clearTimeout(timer);
503
- if (!response.ok) {
504
- this.logger.logResponse(
505
- method,
506
- url,
507
- response.status,
508
- Date.now() - startTime,
509
- data
510
- );
511
- if (data && typeof data === "object" && "error" in data && data.error !== null && typeof data.error === "object") {
512
- const envErr = data.error;
513
- throw new MitwayBaasError(
514
- envErr.message || response.statusText || "Request failed",
515
- envErr.statusCode || response.status,
516
- envErr.code || envErr.error || "REQUEST_FAILED",
517
- envErr.nextActions
518
- );
519
- }
520
- throw new MitwayBaasError(
521
- `Request failed: ${response.statusText}`,
522
- response.status,
523
- "REQUEST_FAILED"
524
- );
525
- }
526
- this.logger.logResponse(
527
- method,
528
- url,
529
- response.status,
530
- Date.now() - startTime,
531
- data
532
- );
533
- if (data && typeof data === "object" && "data" in data && "error" in data && data.error === null) {
534
- return data.data;
535
- }
536
- return data;
537
- } catch (err) {
538
- if (timer !== void 0) clearTimeout(timer);
539
- if (err?.name === "AbortError") {
540
- if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
541
- throw new MitwayBaasError(
542
- `Request timed out after ${this.timeout}ms`,
543
- 408,
544
- "REQUEST_TIMEOUT"
545
- );
546
- }
547
- throw err;
548
- }
549
- if (err instanceof MitwayBaasError) {
550
- throw err;
551
- }
552
- if (attempt < maxAttempts) {
553
- lastError = err;
554
- continue;
555
- }
556
- throw new MitwayBaasError(
557
- `Network request failed: ${err?.message || "Unknown error"}`,
558
- 0,
559
- "NETWORK_ERROR"
560
- );
561
- }
562
- }
563
- throw lastError || new MitwayBaasError(
564
- "Request failed after all retry attempts",
565
- 0,
566
- "NETWORK_ERROR"
567
- );
568
- }
569
- async request(method, path, options = {}) {
570
- try {
571
- return await this.handleRequest(method, path, { ...options });
572
- } catch (error) {
573
- if (error instanceof MitwayBaasError && error.statusCode === 401 && error.error === "INVALID_TOKEN" && this.autoRefreshToken) {
574
- try {
575
- const newTokenData = await this.handleTokenRefresh();
576
- this.setAuthToken(newTokenData.accessToken);
577
- this.tokenManager.saveSession(newTokenData);
578
- if (newTokenData.csrfToken) {
579
- setCsrfToken(newTokenData.csrfToken);
580
- }
581
- if (newTokenData.refreshToken) {
582
- this.setRefreshToken(newTokenData.refreshToken);
583
- }
584
- return await this.handleRequest(method, path, { ...options });
585
- } catch (refreshError) {
586
- this.tokenManager.clearSession();
587
- this.userToken = null;
588
- this.refreshToken = null;
589
- clearCsrfToken();
590
- throw refreshError;
591
- }
592
- }
593
- throw error;
594
- }
595
- }
596
- /**
597
- * Low-level fetch helper for binary bodies (uploads) and streamed responses
598
- * (downloads). Applies the current Bearer token (user session → anon key
599
- * fallback) plus any configured default headers, resolves `path` against
600
- * `baseUrl`, and returns the raw `Response` — it does NOT unwrap the
601
- * `{ data, error }` envelope, so the caller is responsible for status
602
- * checking and parsing.
603
- *
604
- * Used by the storage module for object upload/download paths where the
605
- * body or response is not JSON.
606
- */
607
- async rawFetch(path, init = {}) {
608
- const url = this.buildUrl(path);
609
- const headers = new Headers(init.headers ?? {});
610
- for (const [k, v] of Object.entries(this.defaultHeaders)) {
611
- if (!headers.has(k)) headers.set(k, v);
612
- }
613
- if (!headers.has("Authorization")) {
614
- const token = this.userToken ?? this.anonKey;
615
- if (token) headers.set("Authorization", `Bearer ${token}`);
616
- }
617
- return this.fetch(url, { ...init, headers });
618
- }
619
- get(path, options) {
620
- return this.request("GET", path, options);
621
- }
622
- post(path, body, options) {
623
- return this.request("POST", path, { ...options, body });
624
- }
625
- put(path, body, options) {
626
- return this.request("PUT", path, { ...options, body });
627
- }
628
- patch(path, body, options) {
629
- return this.request("PATCH", path, { ...options, body });
630
- }
631
- delete(path, options) {
632
- return this.request("DELETE", path, options);
633
- }
634
- setAuthToken(token) {
635
- this.userToken = token;
636
- }
637
- setRefreshToken(token) {
638
- this.refreshToken = token;
639
- }
640
- getHeaders() {
641
- const headers = { ...this.defaultHeaders };
642
- const authToken = this.userToken || this.anonKey;
643
- if (authToken) {
644
- headers["Authorization"] = `Bearer ${authToken}`;
645
- }
646
- return headers;
647
- }
648
- /**
649
- * Refresh the current session by calling the MITWAY-BaaS refresh endpoint.
650
- * Note: the route is `/api/auth/refresh` (POST), not the InsForge
651
- * `/api/auth/sessions/current` route. Returns the new access token + user.
652
- */
653
- async handleTokenRefresh() {
654
- if (this.isRefreshing) {
655
- return this.refreshPromise;
656
- }
657
- this.isRefreshing = true;
658
- this.refreshPromise = (async () => {
659
- try {
660
- const csrfToken = getCsrfToken();
661
- const body = this.refreshToken ? { refreshToken: this.refreshToken } : void 0;
662
- const response = await this.handleRequest(
663
- "POST",
664
- "/api/auth/refresh",
665
- {
666
- body,
667
- headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
668
- credentials: "include"
669
- }
670
- );
671
- return normalizeAuthPayload(response);
672
- } finally {
673
- this.isRefreshing = false;
674
- this.refreshPromise = null;
675
- }
676
- })();
677
- return this.refreshPromise;
678
- }
679
- };
680
-
681
- // src/modules/auth.ts
682
- function wrapError(error, fallbackMessage) {
683
- if (error instanceof MitwayBaasError) {
684
- return { data: null, error };
685
- }
686
- return {
687
- data: null,
688
- error: new MitwayBaasError(
689
- error instanceof Error ? error.message : fallbackMessage,
690
- 500,
691
- "AUTH_ERROR"
692
- )
693
- };
694
- }
695
- var Auth = class {
696
- constructor(http, tokenManager) {
697
- this.http = http;
698
- this.tokenManager = tokenManager;
699
- }
700
- http;
701
- tokenManager;
702
- /**
703
- * Persist the session in memory + HttpClient defaults so subsequent
704
- * requests carry the new bearer token automatically.
705
- */
706
- saveSessionFromResponse(response) {
707
- const session = {
708
- accessToken: response.accessToken,
709
- refreshToken: response.refreshToken,
710
- user: response.user
711
- };
712
- if (response.csrfToken) {
713
- setCsrfToken(response.csrfToken);
714
- }
715
- this.tokenManager.saveSession(session);
716
- this.http.setAuthToken(response.accessToken);
717
- this.http.setRefreshToken(response.refreshToken ?? null);
718
- }
719
- /**
720
- * Create a new user account and start a session.
721
- *
722
- * @example
723
- * const { data, error } = await client.auth.signUp({
724
- * email: 'a@b.com',
725
- * password: 'a-strong-password',
726
- * name: 'Alice'
727
- * });
728
- */
729
- async signUp(request) {
730
- try {
731
- const raw = await this.http.post(
732
- "/api/auth/register",
733
- request,
734
- { credentials: "include" }
735
- );
736
- const response = normalizeAuthPayload(raw);
737
- if (response?.accessToken && response.user) {
738
- this.saveSessionFromResponse(response);
739
- }
740
- return { data: response, error: null };
741
- } catch (error) {
742
- return wrapError(error, "Sign up failed");
743
- }
744
- }
745
- /**
746
- * Sign in with email + password and start a session.
747
- */
748
- async signInWithPassword(request) {
749
- try {
750
- const raw = await this.http.post(
751
- "/api/auth/login",
752
- request,
753
- { credentials: "include" }
754
- );
755
- const response = normalizeAuthPayload(raw);
756
- if (response?.accessToken && response.user) {
757
- this.saveSessionFromResponse(response);
758
- }
759
- return { data: response, error: null };
760
- } catch (error) {
761
- return wrapError(error, "Sign in failed");
762
- }
763
- }
764
- /**
765
- * End the current session. Clears in-memory state even if the backend
766
- * call fails (network/offline).
767
- */
768
- async signOut() {
769
- try {
770
- try {
771
- await this.http.post("/api/auth/logout", void 0, {
772
- credentials: "include"
773
- });
774
- } catch {
775
- }
776
- this.tokenManager.clearSession();
777
- this.http.setAuthToken(null);
778
- this.http.setRefreshToken(null);
779
- clearCsrfToken();
780
- return { error: null };
781
- } catch {
782
- return {
783
- error: new MitwayBaasError("Failed to sign out", 500, "SIGNOUT_ERROR")
784
- };
785
- }
786
- }
787
- /**
788
- * Manually refresh the current session. The HttpClient will call this
789
- * automatically on 401 INVALID_TOKEN responses; consumers usually do
790
- * not need to call it directly.
791
- */
792
- async refreshSession() {
793
- try {
794
- const response = await this.http.handleTokenRefresh();
795
- if (response?.accessToken && response.user) {
796
- this.saveSessionFromResponse(response);
797
- }
798
- return { data: response, error: null };
799
- } catch (error) {
800
- return wrapError(error, "Session refresh failed");
801
- }
802
- }
803
- /**
804
- * Restore the session from localStorage and validate it with the backend.
805
- * Call this once on app startup (e.g. in a React AuthProvider useEffect).
806
- *
807
- * Flow:
808
- * 1. Read persisted session from localStorage.
809
- * 2. Populate in-memory state (TokenManager + HttpClient).
810
- * 3. Validate with `GET /api/auth/sessions/current`.
811
- * - If the access token expired, the HttpClient auto-refresh kicks in
812
- * using the persisted refresh token (sent in the POST body, not
813
- * cookies — works cross-site).
814
- * 4. Return the validated user or an error.
815
- *
816
- * If no persisted session exists, returns `{ data: null, error }` — the
817
- * app should show the login page.
818
- */
819
- async initialize() {
820
- const restored = this.tokenManager.restoreSession();
821
- if (!restored) {
822
- return {
823
- data: null,
824
- error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
825
- };
826
- }
827
- const session = this.tokenManager.getSession();
828
- if (!session) {
829
- return {
830
- data: null,
831
- error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
832
- };
833
- }
834
- this.http.setAuthToken(session.accessToken);
835
- const refreshToken = this.tokenManager.getRefreshToken();
836
- if (refreshToken) {
837
- this.http.setRefreshToken(refreshToken);
838
- }
839
- try {
840
- const response = await this.http.get(
841
- "/api/auth/sessions/current"
842
- );
843
- if (response?.user) {
844
- this.tokenManager.setUser(response.user);
845
- return {
846
- data: {
847
- user: response.user,
848
- accessToken: session.accessToken
849
- },
850
- error: null
851
- };
852
- }
853
- this.tokenManager.clearSession();
854
- this.http.setAuthToken(null);
855
- this.http.setRefreshToken(null);
856
- return {
857
- data: null,
858
- error: new MitwayBaasError("Invalid session", 401, "INVALID_SESSION")
859
- };
860
- } catch (error) {
861
- this.tokenManager.clearSession();
862
- this.http.setAuthToken(null);
863
- this.http.setRefreshToken(null);
864
- return wrapError(error, "Session restore failed");
865
- }
866
- }
867
- /**
868
- * Get the current in-memory session, or null if the user is not signed in.
869
- * Synchronous — does not hit the network.
870
- */
871
- getSession() {
872
- return this.tokenManager.getSession();
873
- }
874
- /**
875
- * Get the current in-memory user, or null if not signed in.
876
- */
877
- getUser() {
878
- return this.tokenManager.getUser();
879
- }
880
- /**
881
- * Fetch the current user from the backend. Unlike getUser() which reads
882
- * from memory, this makes a network request and returns the latest data.
883
- */
884
- async getCurrentUser() {
885
- try {
886
- const response = await this.http.get(
887
- "/api/auth/sessions/current"
888
- );
889
- if (response?.user) {
890
- const session = {
891
- accessToken: this.tokenManager.getSession()?.accessToken ?? "",
892
- user: response.user
893
- };
894
- this.tokenManager.saveSession(session);
895
- }
896
- return { data: response, error: null };
897
- } catch (error) {
898
- return wrapError(error, "Failed to get current user");
899
- }
900
- }
901
- /**
902
- * Get a user's profile by ID. Requires authentication.
903
- */
904
- async getProfile(userId) {
905
- try {
906
- const response = await this.http.get(`/api/auth/profiles/${encodeURIComponent(userId)}`);
907
- return { data: response, error: null };
908
- } catch (error) {
909
- return wrapError(
910
- error,
911
- "Failed to get profile"
912
- );
913
- }
914
- }
915
- /**
916
- * Update the current user's profile. Merges with existing profile data —
917
- * only the fields you pass are updated, existing fields are preserved.
918
- */
919
- async setProfile(profile) {
920
- try {
921
- const response = await this.http.patch("/api/auth/profiles/current", { profile });
922
- return { data: response, error: null };
923
- } catch (error) {
924
- return wrapError(
925
- error,
926
- "Failed to update profile"
927
- );
928
- }
929
- }
930
- };
931
-
932
- // src/modules/database.ts
933
- import { PostgrestClient } from "@supabase/postgrest-js";
934
- function createMitwayBaasFetch(httpClient, tokenManager, anonKey) {
935
- return async (input, init) => {
936
- const url = typeof input === "string" ? input : input.toString();
937
- const urlObj = new URL(url);
938
- const pathname = urlObj.pathname.startsWith("/") ? urlObj.pathname.slice(1) : urlObj.pathname;
939
- const rpcMatch = pathname.match(/^rpc\/(.+)$/);
940
- const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
941
- const targetUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
942
- const headers = new Headers(init?.headers);
943
- if (!headers.has("Authorization")) {
944
- const token = tokenManager.getAccessToken() ?? anonKey;
945
- if (token) {
946
- headers.set("Authorization", `Bearer ${token}`);
947
- }
948
- }
949
- return fetch(targetUrl, { ...init, headers });
950
- };
951
- }
952
- var Database = class {
953
- postgrest;
954
- httpClient;
955
- constructor(httpClient, tokenManager, anonKey) {
956
- this.httpClient = httpClient;
957
- this.postgrest = new PostgrestClient("http://dummy", {
958
- fetch: createMitwayBaasFetch(httpClient, tokenManager, anonKey),
959
- headers: {}
960
- });
961
- }
962
- /**
963
- * Build a PostgREST query against a table.
964
- *
965
- * @example
966
- * const { data, error } = await client.database
967
- * .from('posts')
968
- * .select('*')
969
- * .eq('user_id', userId)
970
- * .order('created_at', { ascending: false })
971
- * .limit(10);
972
- *
973
- * @example
974
- * const { data, error } = await client.database
975
- * .from('posts')
976
- * .insert({ title: 'Hello', content: 'World' })
977
- * .select()
978
- * .single();
979
- */
980
- from(table) {
981
- if (!table || typeof table !== "string") {
982
- throw new MitwayBaasError(
983
- "Database.from(table) requires a non-empty string",
984
- 400,
985
- "INVALID_TABLE_NAME"
986
- );
987
- }
988
- return this.postgrest.from(table);
989
- }
990
- /**
991
- * Call a PostgreSQL stored function (RPC).
992
- *
993
- * @example
994
- * const { data, error } = await client.database
995
- * .rpc('get_user_stats', { user_id: 123 });
996
- */
997
- rpc(fn, args, options) {
998
- return this.postgrest.rpc(fn, args, options);
999
- }
1000
- /**
1001
- * The backend base URL the database client is targeting. Useful for
1002
- * debugging — the actual PostgREST instance is internal and not
1003
- * reachable by the SDK directly.
1004
- */
1005
- getUrl() {
1006
- return this.httpClient.baseUrl;
1007
- }
1008
- };
1009
-
1010
- // src/modules/realtime.ts
1011
- import { io } from "socket.io-client";
1012
- var PRESENCE_HEARTBEAT_MS = 2e4;
1013
- function makeChannelError(code, message) {
1014
- const err = new Error(message);
1015
- err.code = code;
1016
- return err;
1017
- }
1018
- var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
1019
- var RealtimeChannel = class {
1020
- constructor(topic, realtime, options = {}) {
1021
- this.topic = topic;
1022
- this.realtime = realtime;
1023
- this.options = options;
1024
- }
1025
- topic;
1026
- realtime;
1027
- bindings = [];
1028
- state = "closed";
1029
- statusCallback = null;
1030
- /** Local presence state mirror — populated from `presence_state` /
1031
- * `presence_join` / `presence_leave` events. Read via `presenceState()`. */
1032
- presence = {};
1033
- /** Latest state this client has tracked. Non-null means the heartbeat
1034
- * timer is active and we'll re-emit this state every TTL/2. */
1035
- trackedState = null;
1036
- presenceHeartbeat = null;
1037
- /** Timestamp of the most recently received broadcast on this channel —
1038
- * used as the `since` anchor on replay after reconnect. ISO-8601. */
1039
- lastBroadcastTimestamp = null;
1040
- /** Configuration from `channel(topic, opts)`. Frozen at construction. */
1041
- options;
1042
- /** Whether this channel was opened as `private: true`. */
1043
- get isPrivate() {
1044
- return this.options.config?.private === true;
1045
- }
1046
- /** The user-supplied presence key, if any. */
1047
- get presenceKey() {
1048
- return this.options.config?.presence?.key;
1049
- }
1050
- /** Internal — exposed for Realtime to drive resubscription after a
1051
- * network hiccup. Returns the current lifecycle state. */
1052
- _state() {
1053
- return this.state;
1054
- }
1055
- /** Internal — called by `Realtime` when Socket.IO reconnects after a
1056
- * drop. Re-runs the registration flow; the backend assigns fresh
1057
- * subscription_ids and Socket.IO rejoins the per-subscription rooms,
1058
- * so events resume without developer intervention. The user-provided
1059
- * statusCallback (from the original subscribe()) fires again with
1060
- * 'SUBSCRIBED' or 'CHANNEL_ERROR' so the app can reflect state. */
1061
- async _rejoinAfterReconnect() {
1062
- if (this.state === "closed") {
1063
- return;
1064
- }
1065
- for (const b of this.bindings) {
1066
- if (b.type === "postgres_changes") {
1067
- b.subscriptionId = void 0;
1068
- }
1069
- }
1070
- this.state = "joining";
1071
- try {
1072
- await this.registerAllBindings();
1073
- if (this.trackedState) {
1074
- const socket = this.realtime._getSocket();
1075
- const key = this.presenceKey;
1076
- socket?.emit(
1077
- "realtime:presence:track",
1078
- key !== void 0 ? { channel: this.topic, state: this.trackedState, key } : { channel: this.topic, state: this.trackedState }
1079
- );
1080
- }
1081
- if (this.lastBroadcastTimestamp && this.bindings.some((b) => b.type === "broadcast")) {
1082
- void this.replay({ since: this.lastBroadcastTimestamp }).catch(() => void 0);
1083
- }
1084
- this.state = "joined";
1085
- this.statusCallback?.("SUBSCRIBED");
1086
- } catch (err) {
1087
- this.state = "errored";
1088
- this.statusCallback?.(
1089
- "CHANNEL_ERROR",
1090
- makeChannelError("REJOIN_FAILED", err instanceof Error ? err.message : String(err))
1091
- );
1092
- }
1093
- }
1094
- // ── implementation signature (not in public type surface).
1095
- // The callback type is intentionally broad: each overload above pins a
1096
- // specific payload shape, but TypeScript overload resolution needs the
1097
- // implementation to accept the union of every narrow callback without
1098
- // the contravariance conflict (TS2394). `any` is the standard escape
1099
- // hatch for this exact pattern and is confined to this one line — the
1100
- // public surface users see is strictly typed via the overloads.
1101
- on(type, filter, callback) {
1102
- if (type === "postgres_changes") {
1103
- this.bindings.push({
1104
- type: "postgres_changes",
1105
- filter,
1106
- callback
1107
- });
1108
- } else if (type === "broadcast") {
1109
- this.bindings.push({
1110
- type: "broadcast",
1111
- filter,
1112
- callback
1113
- });
1114
- } else {
1115
- this.bindings.push({
1116
- type: "presence",
1117
- filter,
1118
- callback
1119
- });
1120
- }
1121
- return this;
1122
- }
1123
- // -------------------------------------------------------------------------
1124
- // Presence — track / untrack / state accessor
1125
- // -------------------------------------------------------------------------
1126
- /**
1127
- * Register or refresh this client's presence entry on the channel. Safe
1128
- * to call many times — state replaces (not merges). Starts a heartbeat
1129
- * timer at TTL/2 so the entry stays alive while the socket is open.
1130
- * The channel must be `subscribe()`d first (the server enforces this).
1131
- */
1132
- async track(state) {
1133
- const socket = this.realtime._getSocket();
1134
- if (!socket) {
1135
- throw new MitwayBaasError("Socket not connected", 503, "NOT_CONNECTED");
1136
- }
1137
- this.trackedState = state;
1138
- const key = this.presenceKey;
1139
- socket.emit(
1140
- "realtime:presence:track",
1141
- key !== void 0 ? { channel: this.topic, state, key } : { channel: this.topic, state }
1142
- );
1143
- if (!this.presenceHeartbeat) {
1144
- this.presenceHeartbeat = setInterval(() => {
1145
- const s = this.realtime._getSocket();
1146
- if (s && this.trackedState) {
1147
- s.emit(
1148
- "realtime:presence:track",
1149
- key !== void 0 ? { channel: this.topic, state: this.trackedState, key } : { channel: this.topic, state: this.trackedState }
1150
- );
1151
- }
1152
- }, PRESENCE_HEARTBEAT_MS);
1153
- const h = this.presenceHeartbeat;
1154
- h.unref?.();
1155
- }
1156
- }
1157
- /**
1158
- * Remove this client's presence entry immediately, stopping the
1159
- * heartbeat. Safe if the client never called `track`.
1160
- */
1161
- untrack() {
1162
- this.trackedState = null;
1163
- if (this.presenceHeartbeat) {
1164
- clearInterval(this.presenceHeartbeat);
1165
- this.presenceHeartbeat = null;
1166
- }
1167
- const socket = this.realtime._getSocket();
1168
- socket?.emit("realtime:presence:untrack", { channel: this.topic });
1169
- }
1170
- /** Snapshot of the current presence state on this channel. Keys are
1171
- * opaque client identifiers (server-assigned). Re-read inside your
1172
- * `.on('presence', { event: 'sync' }, ...)` handler. */
1173
- presenceState() {
1174
- return this.presence;
1175
- }
1176
- /**
1177
- * Register all bindings with the server:
1178
- * * For each `broadcast` binding, ensure we're subscribed to the topic
1179
- * (one `realtime:subscribe` for the channel as a whole).
1180
- * * For each `postgres_changes` binding, emit
1181
- * `realtime:postgres_changes:subscribe` and record the assigned
1182
- * subscription_id.
1183
- *
1184
- * `statusCallback` is invoked with `'SUBSCRIBED'` when every binding
1185
- * has ack'd, or with `'CHANNEL_ERROR' | 'TIMED_OUT'` on failure.
1186
- */
1187
- subscribe(statusCallback) {
1188
- this.statusCallback = statusCallback ?? null;
1189
- if (this.state === "joining" || this.state === "joined") {
1190
- return this;
1191
- }
1192
- this.state = "joining";
1193
- void this.realtime.connect().then(
1194
- async () => {
1195
- try {
1196
- await this.registerAllBindings();
1197
- this.state = "joined";
1198
- this.statusCallback?.("SUBSCRIBED");
1199
- } catch (err) {
1200
- this.state = "errored";
1201
- const message = err instanceof Error ? err.message : String(err);
1202
- this.statusCallback?.(
1203
- "CHANNEL_ERROR",
1204
- makeChannelError("SUBSCRIBE_FAILED", message)
1205
- );
1206
- }
1207
- },
1208
- (err) => {
1209
- this.state = "errored";
1210
- this.statusCallback?.(
1211
- "CHANNEL_ERROR",
1212
- makeChannelError("CONNECT_FAILED", err.message)
1213
- );
1214
- }
1215
- );
1216
- return this;
1217
- }
1218
- /**
1219
- * Tear down every binding: unsubscribe from the broadcast topic (if
1220
- * any broadcast bindings exist) and remove every postgres_changes
1221
- * subscription by id. Safe to call when state is already closed.
1222
- */
1223
- async unsubscribe() {
1224
- if (this.state === "closed") {
1225
- return;
1226
- }
1227
- const socket = this.realtime._getSocket();
1228
- if (this.presenceHeartbeat) {
1229
- clearInterval(this.presenceHeartbeat);
1230
- this.presenceHeartbeat = null;
1231
- }
1232
- if (!socket) {
1233
- this.trackedState = null;
1234
- this.state = "closed";
1235
- return;
1236
- }
1237
- if (this.trackedState) {
1238
- socket.emit("realtime:presence:untrack", { channel: this.topic });
1239
- this.trackedState = null;
1240
- }
1241
- const pcBindings = this.bindings.filter(
1242
- (b) => b.type === "postgres_changes"
1243
- );
1244
- for (const b of pcBindings) {
1245
- if (b.subscriptionId) {
1246
- socket.emit("realtime:postgres_changes:unsubscribe", {
1247
- subscription_id: b.subscriptionId
1248
- });
1249
- b.subscriptionId = void 0;
1250
- }
1251
- }
1252
- if (this.bindings.some((b) => b.type === "broadcast" || b.type === "presence")) {
1253
- socket.emit("realtime:unsubscribe", { channel: this.topic });
1254
- }
1255
- this.realtime._detachChannel(this);
1256
- this.state = "closed";
1257
- this.statusCallback?.("CLOSED");
1258
- }
1259
- /**
1260
- * Publish a broadcast event to the topic. Broadcasts are always
1261
- * ephemeral — the server fans out to every subscribed socket in memory,
1262
- * with no DB persistence or webhook fan-out. For audited / durable
1263
- * events, write to your own application table and enable
1264
- * `postgres_changes` on it; the SDK will surface the INSERT as a
1265
- * `postgres_changes` event without a separate channel.send call.
1266
- *
1267
- * The returned promise always resolves with the server ack, so callers
1268
- * can `await channel.send(...)` to confirm delivery + get the server-
1269
- * assigned `message_id`. There's no performance cost — Socket.IO piggy-
1270
- * backs the ack on the same frame. Callers that don't need it just
1271
- * don't await.
1272
- */
1273
- async send(args) {
1274
- if (args.type !== "broadcast") {
1275
- throw new MitwayBaasError(
1276
- 'Only "broadcast" sends are supported \u2014 DB changes flow via your DB writes, not channel.send()',
1277
- 400,
1278
- "UNSUPPORTED_SEND_TYPE"
1279
- );
1280
- }
1281
- const RESERVED_BROADCAST_NAMES = /* @__PURE__ */ new Set([
1282
- "postgres_changes",
1283
- "presence_state",
1284
- "presence_join",
1285
- "presence_leave"
1286
- ]);
1287
- if (RESERVED_BROADCAST_NAMES.has(args.event)) {
1288
- throw new MitwayBaasError(
1289
- `"${args.event}" is a reserved event name \u2014 pick a different name for broadcast events`,
1290
- 400,
1291
- "RESERVED_EVENT_NAME"
1292
- );
1293
- }
1294
- const socket = this.realtime._getSocket();
1295
- if (!socket) {
1296
- throw new MitwayBaasError("Socket not connected", 503, "NOT_CONNECTED");
1297
- }
1298
- const self = this.options.config?.broadcast?.self;
1299
- const wirePayload = {
1300
- channel: this.topic,
1301
- event: args.event,
1302
- payload: args.payload
1303
- };
1304
- if (self === false) {
1305
- wirePayload.self = false;
1306
- }
1307
- return await new Promise((resolve) => {
1308
- socket.emit(
1309
- "realtime:publish",
1310
- wirePayload,
1311
- (ack) => {
1312
- resolve(ack);
1313
- }
1314
- );
1315
- });
1316
- }
1317
- /**
1318
- * Replay SQL-originated broadcasts on this topic since the given
1319
- * timestamp. Delivered only to this socket (same envelope format as
1320
- * live broadcasts; the SDK routes them through `.on('broadcast', ...)`
1321
- * bindings just like the real-time path). Backend caps the window at
1322
- * 24 h and the limit at 1000.
1323
- */
1324
- async replay(args) {
1325
- const socket = this.realtime._getSocket();
1326
- if (!socket) {
1327
- throw new MitwayBaasError("Socket not connected", 503, "NOT_CONNECTED");
1328
- }
1329
- socket.emit("realtime:broadcast:replay", {
1330
- channel: this.topic,
1331
- since: args.since,
1332
- limit: args.limit,
1333
- private: this.isPrivate
1334
- });
1335
- }
1336
- /** Internal — called by Realtime's event router on every incoming event. */
1337
- _dispatch(event, envelope) {
1338
- if (event === "postgres_changes") {
1339
- const pcEvent = envelope;
1340
- for (const b of this.bindings) {
1341
- if (b.type !== "postgres_changes") {
1342
- continue;
1343
- }
1344
- if (!b.subscriptionId || !pcEvent.ids.includes(b.subscriptionId)) {
1345
- continue;
1346
- }
1347
- const matchesEvent = b.filter.event === "*" || b.filter.event === pcEvent.data.eventType;
1348
- if (!matchesEvent) {
1349
- continue;
1350
- }
1351
- try {
1352
- b.callback(pcEvent.data);
1353
- } catch {
1354
- }
1355
- }
1356
- return;
1357
- }
1358
- if (event === "presence_state" || event === "presence_join" || event === "presence_leave") {
1359
- const e = envelope;
1360
- if (e.channel !== this.topic) {
1361
- return;
1362
- }
1363
- if (event === "presence_state" && e.state) {
1364
- this.presence = { ...e.state };
1365
- this.firePresence({ event: "sync", state: this.presence });
1366
- } else if (event === "presence_join" && e.joins) {
1367
- Object.assign(this.presence, e.joins);
1368
- this.firePresence({ event: "join", joins: e.joins });
1369
- } else if (event === "presence_leave" && e.leaves) {
1370
- for (const key of Object.keys(e.leaves)) {
1371
- delete this.presence[key];
1372
- }
1373
- this.firePresence({ event: "leave", leaves: e.leaves });
1374
- }
1375
- return;
1376
- }
1377
- const meta = envelope.meta;
1378
- if (meta?.timestamp) {
1379
- if (!this.lastBroadcastTimestamp || meta.timestamp > this.lastBroadcastTimestamp) {
1380
- this.lastBroadcastTimestamp = meta.timestamp;
1381
- }
1382
- }
1383
- for (const b of this.bindings) {
1384
- if (b.type !== "broadcast") {
1385
- continue;
1386
- }
1387
- if (b.filter.event !== event) {
1388
- continue;
1389
- }
1390
- const { meta: _meta, ...payload } = envelope;
1391
- try {
1392
- b.callback({ type: "broadcast", event, payload });
1393
- } catch {
1394
- }
1395
- }
1396
- }
1397
- firePresence(payload) {
1398
- for (const b of this.bindings) {
1399
- if (b.type !== "presence") {
1400
- continue;
1401
- }
1402
- if (b.filter.event !== payload.event) {
1403
- continue;
1404
- }
1405
- try {
1406
- if (payload.event === "sync") {
1407
- b.callback();
1408
- } else if (payload.event === "join") {
1409
- b.callback(payload);
1410
- } else {
1411
- b.callback(payload);
1412
- }
1413
- } catch {
1414
- }
1415
- }
1416
- }
1417
- async registerAllBindings() {
1418
- const socket = this.realtime._getSocket();
1419
- if (!socket) {
1420
- throw new Error("Socket not available");
1421
- }
1422
- const needsRoomJoin = this.bindings.some(
1423
- (b) => b.type === "broadcast" || b.type === "presence"
1424
- );
1425
- if (needsRoomJoin) {
1426
- await new Promise((resolve, reject) => {
1427
- socket.emit(
1428
- "realtime:subscribe",
1429
- { channel: this.topic, private: this.isPrivate },
1430
- (ack) => {
1431
- if (ack.status === "ok") {
1432
- resolve();
1433
- } else {
1434
- reject(new Error(ack.error?.message ?? "subscribe failed"));
1435
- }
1436
- }
1437
- );
1438
- });
1439
- }
1440
- const pcBindings = this.bindings.filter(
1441
- (b) => b.type === "postgres_changes"
1442
- );
1443
- await Promise.all(
1444
- pcBindings.map(
1445
- (b) => new Promise((resolve, reject) => {
1446
- socket.emit(
1447
- "realtime:postgres_changes:subscribe",
1448
- {
1449
- event: b.filter.event,
1450
- schema: b.filter.schema ?? "public",
1451
- table: b.filter.table,
1452
- filter: b.filter.filter
1453
- },
1454
- (ack) => {
1455
- if (ack.status === "ok" && ack.subscription_id) {
1456
- b.subscriptionId = ack.subscription_id;
1457
- resolve();
1458
- } else {
1459
- reject(
1460
- new Error(ack.error?.message ?? "postgres_changes subscribe failed")
1461
- );
1462
- }
1463
- }
1464
- );
1465
- })
1466
- )
1467
- );
1468
- }
1469
- };
1470
- var Realtime = class {
1471
- socket = null;
1472
- baseUrl;
1473
- options;
1474
- anonKey;
1475
- tokenManager;
1476
- channels = /* @__PURE__ */ new Map();
1477
- connecting = null;
1478
- /** Flips to `true` once the initial handshake resolves. Differentiates
1479
- * the first `connect` event (part of `openSocket`) from subsequent
1480
- * reconnect events (which should trigger auto-resubscribe). */
1481
- firstConnected = false;
1482
- constructor(baseUrl, tokenManager, anonKey, options = {}) {
1483
- this.baseUrl = baseUrl;
1484
- this.tokenManager = tokenManager;
1485
- this.anonKey = anonKey;
1486
- this.options = options;
1487
- }
1488
- get isConnected() {
1489
- return this.socket?.connected === true;
1490
- }
1491
- get socketId() {
1492
- return this.socket?.id;
1493
- }
1494
- /**
1495
- * Get (or create) a channel for `topic`. Channels are cached so multiple
1496
- * `.channel('same')` calls return the same instance.
1497
- *
1498
- * The optional `opts` argument lets the caller configure the channel:
1499
- * * `config.private` — enable subscribe-side authorization against
1500
- * `realtime.authorize_subscribe(...)` on the tenant DB.
1501
- * * `config.broadcast.self` — `false` excludes the sender from the
1502
- * fan-out (defaults to `true`).
1503
- * * `config.presence.key` — stable presence key to group multiple
1504
- * tabs of the same user under one entry.
1505
- *
1506
- * `channel.send()` always resolves with the server ack (see its own
1507
- * docstring); there is no separate opt-in needed.
1508
- *
1509
- * Options are locked in when the channel is first created; subsequent
1510
- * `.channel('same')` calls with different opts are ignored. Pass a
1511
- * different topic to get a different-configured channel.
1512
- */
1513
- channel(topic, opts) {
1514
- const existing = this.channels.get(topic);
1515
- if (existing) {
1516
- return existing;
1517
- }
1518
- const channel = new RealtimeChannel(topic, this, opts);
1519
- this.channels.set(topic, channel);
1520
- return channel;
1521
- }
1522
- connect() {
1523
- if (this.isConnected) {
1524
- return Promise.resolve();
1525
- }
1526
- if (this.connecting) {
1527
- return this.connecting;
1528
- }
1529
- this.connecting = this.openSocket();
1530
- return this.connecting;
1531
- }
1532
- /**
1533
- * Close the socket. Channels are left as-is so they can re-subscribe
1534
- * on the next `connect()` — useful for auth token refresh flows.
1535
- */
1536
- disconnect() {
1537
- if (!this.socket) {
1538
- return;
1539
- }
1540
- this.socket.disconnect();
1541
- this.socket = null;
1542
- this.firstConnected = false;
1543
- }
1544
- // -------------------------------------------------------------------------
1545
- // internals used by RealtimeChannel
1546
- // -------------------------------------------------------------------------
1547
- /* istanbul ignore next — tested via channel integration */
1548
- _getSocket() {
1549
- return this.socket;
1550
- }
1551
- _detachChannel(channel) {
1552
- this.channels.delete(channel.topic);
1553
- }
1554
- openSocket() {
1555
- const token = this.tokenManager.getAccessToken() ?? this.anonKey;
1556
- if (!token) {
1557
- const err = new MitwayBaasError(
1558
- "Realtime requires an access token or anonKey",
1559
- 401,
1560
- "AUTH_INVALID_API_KEY"
1561
- );
1562
- this.connecting = null;
1563
- return Promise.reject(err);
1564
- }
1565
- const timeoutMs = this.options.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
1566
- const socket = io(this.baseUrl, {
1567
- path: this.options.path,
1568
- transports: this.options.transports ?? ["websocket"],
1569
- auth: { token, ...this.options.extraAuth ?? {} },
1570
- reconnection: true,
1571
- timeout: timeoutMs
1572
- });
1573
- this.socket = socket;
1574
- socket.onAny((event, ...args) => this.dispatch(event, args));
1575
- socket.on("connect", () => {
1576
- if (!this.firstConnected) {
1577
- return;
1578
- }
1579
- for (const channel of this.channels.values()) {
1580
- const state = channel._state();
1581
- if (state === "joined" || state === "errored") {
1582
- void channel._rejoinAfterReconnect();
1583
- }
1584
- }
1585
- });
1586
- return new Promise((resolve, reject) => {
1587
- const timer = setTimeout(() => {
1588
- socket.off("connect", onConnect);
1589
- socket.off("connect_error", onConnectError);
1590
- this.connecting = null;
1591
- reject(
1592
- new MitwayBaasError(
1593
- `Realtime connection timeout after ${timeoutMs}ms`,
1594
- 408,
1595
- "CONNECTION_TIMEOUT"
1596
- )
1597
- );
1598
- }, timeoutMs);
1599
- const clear = () => {
1600
- clearTimeout(timer);
1601
- socket.off("connect", onConnect);
1602
- socket.off("connect_error", onConnectError);
1603
- };
1604
- const onConnect = () => {
1605
- clear();
1606
- this.connecting = null;
1607
- this.firstConnected = true;
1608
- resolve();
1609
- };
1610
- const onConnectError = (err) => {
1611
- clear();
1612
- this.connecting = null;
1613
- reject(new MitwayBaasError(err.message, 0, "CONNECTION_FAILED"));
1614
- };
1615
- socket.once("connect", onConnect);
1616
- socket.once("connect_error", onConnectError);
1617
- });
1618
- }
1619
- dispatch(event, args) {
1620
- if (event === "postgres_changes") {
1621
- const envelope2 = args[0] ?? {};
1622
- this.channels.forEach((ch) => ch._dispatch("postgres_changes", envelope2));
1623
- return;
1624
- }
1625
- if (event === "connect" || event === "disconnect" || event === "connect_error" || event === "error" || event === "realtime:error" || event === "realtime:shutdown") {
1626
- return;
1627
- }
1628
- const envelope = args[0] ?? {};
1629
- this.channels.forEach((ch) => ch._dispatch(event, envelope));
1630
- }
1631
- };
1632
-
1633
- // src/modules/storage.ts
1634
- function bucketFromWire(row) {
1635
- return {
1636
- id: row.id,
1637
- name: row.name,
1638
- public: row.public,
1639
- fileSizeLimitBytes: row.file_size_limit_bytes,
1640
- allowedMimeTypes: row.allowed_mime_types,
1641
- createdAt: row.created_at,
1642
- updatedAt: row.updated_at
1643
- };
1644
- }
1645
- function objectFromWire(row, bucketName) {
1646
- return {
1647
- id: row.id,
1648
- bucket: bucketName,
1649
- key: row.key,
1650
- size: row.size,
1651
- mimeType: row.mime_type,
1652
- etag: row.etag,
1653
- cacheControl: row.cache_control,
1654
- contentDisposition: row.content_disposition,
1655
- uploadedBy: row.uploaded_by,
1656
- uploadedAt: row.uploaded_at,
1657
- updatedAt: row.updated_at
1658
- };
1659
- }
1660
- function configFromWire(row) {
1661
- return {
1662
- defaultFileSizeLimitBytes: row.default_file_size_limit_bytes,
1663
- maxFileSizeLimitBytes: row.max_file_size_limit_bytes,
1664
- tenantStorageQuotaBytes: row.tenant_storage_quota_bytes,
1665
- reservedSpaceBytes: row.reserved_space_bytes,
1666
- signedUrlDefaultTtlSec: row.signed_url_default_ttl_sec,
1667
- signedUrlMaxTtlSec: row.signed_url_max_ttl_sec
1668
- };
1669
- }
1670
- function encodeKey(key) {
1671
- return key.split("/").map(encodeURIComponent).join("/");
1672
- }
1673
- function wrapError2(err, fallback) {
1674
- if (err instanceof MitwayBaasError) return { data: null, error: err };
1675
- return {
1676
- data: null,
1677
- error: new MitwayBaasError(
1678
- err instanceof Error ? err.message : fallback,
1679
- 0,
1680
- "STORAGE_ERROR"
1681
- )
1682
- };
1683
- }
1684
- async function readEnvelopeError(response) {
1685
- let code = "STORAGE_ERROR";
1686
- let message = `HTTP ${response.status}`;
1687
- try {
1688
- const body = await response.json();
1689
- if (body && body.error) {
1690
- code = body.error.code ?? code;
1691
- message = body.error.message ?? message;
1692
- }
1693
- } catch {
1694
- }
1695
- return new MitwayBaasError(message, response.status, code);
1696
- }
1697
- var StorageBucketClient = class {
1698
- constructor(http, bucketName) {
1699
- this.http = http;
1700
- this.bucketName = bucketName;
1701
- }
1702
- http;
1703
- bucketName;
1704
- bucketBase() {
1705
- return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`;
1706
- }
1707
- objectPath(key) {
1708
- return `${this.bucketBase()}/objects/${encodeKey(key)}`;
1709
- }
1710
- async upload(key, body, opts = {}) {
1711
- try {
1712
- const method = opts.upsert ? "PUT" : "POST";
1713
- const headers = {
1714
- "Content-Type": opts.contentType ?? "application/octet-stream"
1715
- };
1716
- if (opts.cacheControl) headers["Cache-Control"] = opts.cacheControl;
1717
- if (opts.contentDisposition)
1718
- headers["Content-Disposition"] = opts.contentDisposition;
1719
- const response = await this.http.rawFetch(this.objectPath(key), {
1720
- method,
1721
- headers,
1722
- body,
1723
- signal: opts.abortSignal
1724
- });
1725
- if (!response.ok) {
1726
- return { data: null, error: await readEnvelopeError(response) };
1727
- }
1728
- const parsed = await response.json();
1729
- if (parsed.error || !parsed.data) {
1730
- return {
1731
- data: null,
1732
- error: new MitwayBaasError(
1733
- parsed.error?.message ?? "Upload failed",
1734
- response.status,
1735
- parsed.error?.code ?? "STORAGE_ERROR"
1736
- )
1737
- };
1738
- }
1739
- return { data: objectFromWire(parsed.data, this.bucketName), error: null };
1740
- } catch (err) {
1741
- return wrapError2(err, "Upload failed");
1742
- }
1743
- }
1744
- async download(key, opts = {}) {
1745
- try {
1746
- const headers = {};
1747
- if (opts.range) {
1748
- headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
1749
- }
1750
- const response = await this.http.rawFetch(this.objectPath(key), {
1751
- method: "GET",
1752
- headers,
1753
- signal: opts.abortSignal
1754
- });
1755
- if (!response.ok) {
1756
- return { data: null, error: await readEnvelopeError(response) };
1757
- }
1758
- const blob = await response.blob();
1759
- return { data: blob, error: null };
1760
- } catch (err) {
1761
- return wrapError2(err, "Download failed");
1762
- }
1763
- }
1764
- async getStream(key, opts = {}) {
1765
- try {
1766
- const headers = {};
1767
- if (opts.range) {
1768
- headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
1769
- }
1770
- const response = await this.http.rawFetch(this.objectPath(key), {
1771
- method: "GET",
1772
- headers,
1773
- signal: opts.abortSignal
1774
- });
1775
- if (!response.ok) {
1776
- return { data: null, error: await readEnvelopeError(response) };
1777
- }
1778
- if (!response.body) {
1779
- return {
1780
- data: null,
1781
- error: new MitwayBaasError(
1782
- "Response body is not a stream",
1783
- response.status,
1784
- "STORAGE_ERROR"
1785
- )
1786
- };
1787
- }
1788
- return {
1789
- data: response.body,
1790
- error: null
1791
- };
1792
- } catch (err) {
1793
- return wrapError2(err, "Download failed");
1794
- }
1795
- }
1796
- async remove(keys) {
1797
- try {
1798
- const results = await Promise.allSettled(
1799
- keys.map(
1800
- (key) => this.http.rawFetch(this.objectPath(key), { method: "DELETE" })
1801
- )
1802
- );
1803
- const removed = [];
1804
- const errors = [];
1805
- for (let i = 0; i < keys.length; i++) {
1806
- const key = keys[i];
1807
- const r = results[i];
1808
- if (r.status === "fulfilled" && r.value.ok) {
1809
- removed.push(key);
1810
- } else if (r.status === "fulfilled") {
1811
- errors.push(`${key}: HTTP ${r.value.status}`);
1812
- } else {
1813
- const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
1814
- errors.push(`${key}: ${msg}`);
1815
- }
1816
- }
1817
- if (errors.length > 0) {
1818
- return {
1819
- data: null,
1820
- error: new MitwayBaasError(
1821
- `Failed to delete some objects: ${errors.join("; ")}`,
1822
- 0,
1823
- "STORAGE_ERROR"
1824
- )
1825
- };
1826
- }
1827
- return { data: { removed }, error: null };
1828
- } catch (err) {
1829
- return wrapError2(err, "Delete failed");
1830
- }
1831
- }
1832
- async list(opts = {}) {
1833
- try {
1834
- const params = {};
1835
- if (opts.prefix !== void 0) params.prefix = opts.prefix;
1836
- if (opts.limit !== void 0) params.limit = String(opts.limit);
1837
- if (opts.startAfter !== void 0) params.start_after = opts.startAfter;
1838
- const rows = await this.http.get(
1839
- `${this.bucketBase()}/objects`,
1840
- { params }
1841
- );
1842
- return {
1843
- data: rows.map((r) => objectFromWire(r, this.bucketName)),
1844
- error: null
1845
- };
1846
- } catch (err) {
1847
- return wrapError2(err, "List failed");
1848
- }
1849
- }
1850
- async copy(fromKey, toKey, toBucket) {
1851
- try {
1852
- const row = await this.http.post(
1853
- `${this.objectPath(fromKey)}/copy`,
1854
- {
1855
- dest_bucket: toBucket ?? this.bucketName,
1856
- dest_key: toKey
1857
- }
1858
- );
1859
- return {
1860
- data: objectFromWire(row, toBucket ?? this.bucketName),
1861
- error: null
1862
- };
1863
- } catch (err) {
1864
- return wrapError2(err, "Copy failed");
1865
- }
1866
- }
1867
- async move(fromKey, toKey, toBucket) {
1868
- try {
1869
- const row = await this.http.post(
1870
- `${this.objectPath(fromKey)}/move`,
1871
- {
1872
- dest_bucket: toBucket ?? this.bucketName,
1873
- dest_key: toKey
1874
- }
1875
- );
1876
- return {
1877
- data: objectFromWire(row, toBucket ?? this.bucketName),
1878
- error: null
1879
- };
1880
- } catch (err) {
1881
- return wrapError2(err, "Move failed");
1882
- }
1883
- }
1884
- async createSignedUrl(key, opts = {}) {
1885
- try {
1886
- const body = {};
1887
- if (opts.expiresIn !== void 0) body.expires_in = opts.expiresIn;
1888
- const wire = await this.http.post(`${this.objectPath(key)}/sign`, body);
1889
- return {
1890
- data: {
1891
- url: wire.url,
1892
- token: wire.token,
1893
- expiresAt: wire.expiresAt
1894
- },
1895
- error: null
1896
- };
1897
- } catch (err) {
1898
- return wrapError2(err, "Sign failed");
1899
- }
1900
- }
1901
- getPublicUrl(key) {
1902
- const url = `${this.http.baseUrl.replace(/\/$/, "")}${this.objectPath(key)}`;
1903
- return { data: { url } };
1904
- }
1905
- };
1906
- var Storage = class {
1907
- constructor(http) {
1908
- this.http = http;
1909
- }
1910
- http;
1911
- /** Scope subsequent operations to a single bucket. */
1912
- from(bucketName) {
1913
- return new StorageBucketClient(this.http, bucketName);
1914
- }
1915
- // --- Admin (require service_role) ---
1916
- async listBuckets() {
1917
- try {
1918
- const rows = await this.http.get("/api/storage/buckets");
1919
- return { data: rows.map(bucketFromWire), error: null };
1920
- } catch (err) {
1921
- return wrapError2(err, "listBuckets failed");
1922
- }
1923
- }
1924
- async getBucket(name) {
1925
- try {
1926
- const row = await this.http.get(
1927
- `/api/storage/buckets/${encodeURIComponent(name)}`
1928
- );
1929
- return { data: bucketFromWire(row), error: null };
1930
- } catch (err) {
1931
- return wrapError2(err, "getBucket failed");
1932
- }
1933
- }
1934
- async createBucket(name, opts = {}) {
1935
- try {
1936
- const body = { name };
1937
- if (opts.public !== void 0) body.public = opts.public;
1938
- if (opts.fileSizeLimitBytes !== void 0)
1939
- body.file_size_limit_bytes = opts.fileSizeLimitBytes;
1940
- if (opts.allowedMimeTypes !== void 0)
1941
- body.allowed_mime_types = opts.allowedMimeTypes;
1942
- const row = await this.http.post(
1943
- "/api/storage/buckets",
1944
- body
1945
- );
1946
- return { data: bucketFromWire(row), error: null };
1947
- } catch (err) {
1948
- return wrapError2(err, "createBucket failed");
1949
- }
1950
- }
1951
- async updateBucket(name, opts) {
1952
- try {
1953
- const body = {};
1954
- if (opts.public !== void 0) body.public = opts.public;
1955
- if (opts.fileSizeLimitBytes !== void 0)
1956
- body.file_size_limit_bytes = opts.fileSizeLimitBytes;
1957
- if (opts.allowedMimeTypes !== void 0)
1958
- body.allowed_mime_types = opts.allowedMimeTypes;
1959
- const row = await this.http.patch(
1960
- `/api/storage/buckets/${encodeURIComponent(name)}`,
1961
- body
1962
- );
1963
- return { data: bucketFromWire(row), error: null };
1964
- } catch (err) {
1965
- return wrapError2(err, "updateBucket failed");
1966
- }
1967
- }
1968
- async deleteBucket(name) {
1969
- try {
1970
- await this.http.delete(
1971
- `/api/storage/buckets/${encodeURIComponent(name)}`
1972
- );
1973
- return { data: null, error: null };
1974
- } catch (err) {
1975
- return wrapError2(err, "deleteBucket failed");
1976
- }
1977
- }
1978
- async emptyBucket(name) {
1979
- try {
1980
- const result = await this.http.post(
1981
- `/api/storage/buckets/${encodeURIComponent(name)}/empty`,
1982
- {}
1983
- );
1984
- return { data: result, error: null };
1985
- } catch (err) {
1986
- return wrapError2(err, "emptyBucket failed");
1987
- }
1988
- }
1989
- // --- Storage config (service_role) ---
1990
- async getConfig() {
1991
- try {
1992
- const row = await this.http.get("/api/storage/config");
1993
- return { data: configFromWire(row), error: null };
1994
- } catch (err) {
1995
- return wrapError2(err, "getConfig failed");
1996
- }
1997
- }
1998
- };
1999
-
2000
- // src/client.ts
2001
- var MitwayBaasClient = class {
2002
- http;
2003
- tokenManager;
2004
- auth;
2005
- database;
2006
- realtime;
2007
- storage;
2008
- constructor(config = {}) {
2009
- const logger = new Logger(config.debug);
2010
- this.tokenManager = new TokenManager({
2011
- persistSession: config.persistSession,
2012
- storageKey: config.storageKey
2013
- });
2014
- this.http = new HttpClient(config, this.tokenManager, logger);
2015
- this.auth = new Auth(this.http, this.tokenManager);
2016
- this.database = new Database(this.http, this.tokenManager, config.anonKey);
2017
- this.realtime = new Realtime(
2018
- this.http.baseUrl,
2019
- this.tokenManager,
2020
- config.anonKey,
2021
- config.realtime
2022
- );
2023
- this.storage = new Storage(this.http);
2024
- }
2025
- /**
2026
- * Escape hatch for callers that need to make custom requests against the
2027
- * backend without going through `auth` or `database`.
2028
- */
2029
- getHttpClient() {
2030
- return this.http;
2031
- }
2032
- };
2033
-
2034
- // src/index.ts
2035
- function createClient(config) {
2036
- return new MitwayBaasClient(config);
2037
- }
2038
- var index_default = MitwayBaasClient;
2039
- export {
2040
- Auth,
2041
- Database,
2042
- HttpClient,
2043
- Logger,
2044
- MitwayBaasClient,
2045
- MitwayBaasError,
2046
- Realtime,
2047
- RealtimeChannel,
2048
- Storage,
2049
- StorageBucketClient,
2050
- TokenManager,
2051
- createClient,
2052
- index_default as default
2053
- };
2054
- //# sourceMappingURL=index.js.map
1
+ import {PostgrestClient}from'@supabase/postgrest-js';import {io}from'socket.io-client';var c=class n extends Error{statusCode;error;nextActions;constructor(e,t,r,s){super(e),this.name="MitwayBaasError",this.statusCode=t,this.error=r,this.nextActions=s;}static fromApiError(e){return new n(e.message,e.statusCode,e.error,e.nextActions)}};var X=["authorization","x-api-key","cookie","set-cookie"],Z=["password","token","accesstoken","refreshtoken","authorization","secret","apikey","api_key","email","ssn","creditcard","credit_card"];function ee(n){let e={};for(let[t,r]of Object.entries(n))X.includes(t.toLowerCase())?e[t]="***REDACTED***":e[t]=r;return e}function C(n){if(n==null)return n;if(typeof n=="string")try{let e=JSON.parse(n);return C(e)}catch{return n}if(Array.isArray(n))return n.map(C);if(typeof n=="object"){let e={};for(let[t,r]of Object.entries(n))Z.includes(t.toLowerCase().replace(/[-_]/g,""))?e[t]="***REDACTED***":e[t]=C(r);return e}return n}function G(n){if(n==null)return "";if(typeof n=="string")try{return JSON.stringify(JSON.parse(n),null,2)}catch{return n}if(typeof FormData<"u"&&n instanceof FormData)return "[FormData]";try{return JSON.stringify(n,null,2)}catch{return "[Unserializable body]"}}var R=class{enabled;customLog;constructor(e){typeof e=="function"?(this.enabled=true,this.customLog=e):(this.enabled=!!e,this.customLog=null);}log(e,...t){if(!this.enabled)return;let r=`[MITWAY-BaaS Debug] ${e}`;this.customLog?this.customLog(r,...t):console.log(r,...t);}warn(e,...t){if(!this.enabled)return;let r=`[MITWAY-BaaS Debug] ${e}`;this.customLog?this.customLog(r,...t):console.warn(r,...t);}error(e,...t){if(!this.enabled)return;let r=`[MITWAY-BaaS Debug] ${e}`;this.customLog?this.customLog(r,...t):console.error(r,...t);}logRequest(e,t,r,s){if(!this.enabled)return;let i=[`\u2192 ${e} ${t}`];r&&Object.keys(r).length>0&&i.push(` Headers: ${JSON.stringify(ee(r))}`);let a=G(C(s));if(a){let o=a.length>1e3?a.slice(0,1e3)+"... [truncated]":a;i.push(` Body: ${o}`);}this.log(i.join(`
2
+ `));}logResponse(e,t,r,s,i){if(!this.enabled)return;let a=[`\u2190 ${e} ${t} ${r} (${s}ms)`],o=G(C(i));if(o){let g=o.length>1e3?o.slice(0,1e3)+"... [truncated]":o;a.push(` Body: ${g}`);}r>=400?this.error(a.join(`
3
+ `)):this.log(a.join(`
4
+ `));}};var K="mitway_baas_csrf_token",te="mitway_baas_session";function V(){return typeof localStorage<"u"?{getItem:e=>{try{return localStorage.getItem(e)}catch{return null}},setItem:(e,t)=>{try{localStorage.setItem(e,t);}catch{}},removeItem:e=>{try{localStorage.removeItem(e);}catch{}}}:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}}}var re={getItem:()=>null,setItem:()=>{},removeItem:()=>{}};function Y(){if(typeof document>"u")return null;let n=document.cookie.split(";").find(e=>e.trim().startsWith(`${K}=`));return n&&n.split("=")[1]||null}function F(n){if(typeof document>"u")return;let e=10080*60,t=typeof window<"u"&&window.location.protocol==="https:"?"; Secure":"";document.cookie=`${K}=${encodeURIComponent(n)}; path=/; max-age=${e}; SameSite=Lax${t}`;}function D(){if(typeof document>"u")return;let n=typeof window<"u"&&window.location.protocol==="https:"?"; Secure":"";document.cookie=`${K}=; path=/; max-age=0; SameSite=Lax${n}`;}var S=class{accessToken=null;refreshToken=null;user=null;persistSession;storageKey;storage;onTokenChange=null;constructor(e){this.persistSession=e?.persistSession??true,this.storageKey=e?.storageKey??te,this.storage=this.persistSession?e?.storage??V():re,(e?.multiTab??true)&&this.persistSession&&typeof window<"u"&&typeof localStorage<"u"&&window.addEventListener("storage",this.handleStorageEvent);}handleStorageEvent=e=>{e.key===this.storageKey&&(typeof localStorage<"u"&&e.storageArea!==localStorage||this.syncFromStorage(e.newValue));};syncFromStorage(e){let t=this.accessToken;if(e===null)this.accessToken=null,this.refreshToken=null,this.user=null;else try{let r=JSON.parse(e);if(!r.accessToken||!r.user)return;this.accessToken=r.accessToken,this.refreshToken=r.refreshToken??null,this.user=r.user;}catch{return}t!==this.accessToken&&this.onTokenChange&&this.onTokenChange();}saveSession(e){let t=e.accessToken!==this.accessToken;this.accessToken=e.accessToken,this.user=e.user,e.refreshToken!==void 0&&(this.refreshToken=e.refreshToken??null),this.persist(),t&&this.onTokenChange&&this.onTokenChange();}getSession(){return !this.accessToken||!this.user?null:{accessToken:this.accessToken,refreshToken:this.refreshToken??void 0,user:this.user}}getAccessToken(){return this.accessToken}setAccessToken(e){let t=e!==this.accessToken;this.accessToken=e,this.persist(),t&&this.onTokenChange&&this.onTokenChange();}getRefreshToken(){return this.refreshToken}setRefreshToken(e){this.refreshToken=e,this.persist();}getUser(){return this.user}setUser(e){this.user=e,this.persist();}clearSession(){let e=this.accessToken!==null;this.accessToken=null,this.refreshToken=null,this.user=null,this.removePersisted(),e&&this.onTokenChange&&this.onTokenChange();}restoreSession(){if(!this.persistSession)return false;try{let e=this.storage.getItem(this.storageKey);if(!e)return !1;let t=JSON.parse(e);return !t.accessToken||!t.user?!1:(this.accessToken=t.accessToken,this.refreshToken=t.refreshToken??null,this.user=t.user,!0)}catch{return false}}persist(){if(!this.persistSession||!this.accessToken||!this.user)return;let e={accessToken:this.accessToken,user:this.user};this.refreshToken&&(e.refreshToken=this.refreshToken),this.storage.setItem(this.storageKey,JSON.stringify(e));}removePersisted(){this.persistSession&&this.storage.removeItem(this.storageKey);}};function P(n){if(!n||typeof n!="object")return n;let e=n,t={...e},r=false;return "access_token"in e&&!("accessToken"in e)&&(t.accessToken=e.access_token,delete t.access_token,r=true),"csrf_token"in e&&!("csrfToken"in e)&&(t.csrfToken=e.csrf_token,delete t.csrf_token,r=true),"refresh_token"in e&&!("refreshToken"in e)&&(t.refreshToken=e.refresh_token,delete t.refresh_token,r=true),r?t:n}var se=new Set([500,502,503,504]),ne=new Set(["GET","HEAD","PUT","DELETE","OPTIONS"]),_=class{baseUrl;fetch;defaultHeaders;anonKey;userToken=null;logger;autoRefreshToken=true;isRefreshing=false;refreshPromise=null;tokenManager;refreshToken=null;timeout;retryCount;retryDelay;constructor(e,t,r){if(this.baseUrl=e.baseUrl||"http://localhost:7130",this.autoRefreshToken=e.autoRefreshToken??true,this.fetch=e.fetch||(globalThis.fetch?globalThis.fetch.bind(globalThis):void 0),this.anonKey=e.anonKey,this.defaultHeaders={...e.headers},this.tokenManager=t??new S,this.logger=r||new R(false),this.timeout=e.timeout??3e4,this.retryCount=e.retryCount??3,this.retryDelay=e.retryDelay??500,!this.fetch)throw new Error("Fetch is not available. Provide a fetch implementation in the SDK config.")}buildUrl(e,t){let r=new URL(e,this.baseUrl);return t&&Object.entries(t).forEach(([s,i])=>{if(s==="select"){let a=i.replace(/\s+/g," ").trim();a=a.replace(/\s*\(\s*/g,"(").replace(/\s*\)\s*/g,")").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").replace(/,\s+(?=[^()]*\))/g,","),r.searchParams.append(s,a);}else r.searchParams.append(s,i);}),r.toString()}isRetryableStatus(e){return se.has(e)}computeRetryDelay(e){let r=this.retryDelay*Math.pow(2,e-1)*(.85+Math.random()*.3);return Math.round(r)}async handleRequest(e,t,r={}){let{params:s,headers:i={},body:a,signal:o,...g}=r,p=this.buildUrl(t,s),w=Date.now(),T=ne.has(e.toUpperCase())||r.idempotent===true?this.retryCount:0,b={...this.defaultHeaders},J=this.userToken||this.anonKey;J&&(b.Authorization=`Bearer ${J}`);let M;a!==void 0&&(typeof FormData<"u"&&a instanceof FormData?M=a:(e!=="GET"&&(b["Content-Type"]="application/json;charset=UTF-8"),M=JSON.stringify(a))),i instanceof Headers?i.forEach((f,h)=>{b[h]=f;}):Array.isArray(i)?i.forEach(([f,h])=>{b[f]=h;}):Object.assign(b,i),this.logger.logRequest(e,p,b,M);let H;for(let f=0;f<=T;f++){if(f>0){let l=this.computeRetryDelay(f);if(this.logger.warn(`Retry ${f}/${T} for ${e} ${p} in ${l}ms`),o?.aborted)throw o.reason;await new Promise((u,q)=>{let y=()=>{clearTimeout(Q),q(o.reason);},Q=setTimeout(()=>{o&&o.removeEventListener("abort",y),u();},l);o&&o.addEventListener("abort",y,{once:true});});}let h,m;if((this.timeout>0||o)&&(h=new AbortController,this.timeout>0&&(m=setTimeout(()=>h.abort(),this.timeout)),o))if(o.aborted)h.abort(o.reason);else {let l=()=>h.abort(o.reason);o.addEventListener("abort",l,{once:true}),h.signal.addEventListener("abort",()=>{o.removeEventListener("abort",l);},{once:true});}try{let l=await this.fetch(p,{method:e,headers:b,body:M,...g,...h?{signal:h.signal}:{}});if(this.isRetryableStatus(l.status)&&f<T){m!==void 0&&clearTimeout(m),await l.body?.cancel(),H=new c(`Server error: ${l.status} ${l.statusText}`,l.status,"SERVER_ERROR");continue}if(l.status===204){m!==void 0&&clearTimeout(m);return}let u,q=l.headers.get("content-type");try{q?.includes("json")?u=await l.json():u=await l.text();}catch(y){throw m!==void 0&&clearTimeout(m),new c(`Failed to parse response body: ${y?.message||"Unknown error"}`,l.status,l.ok?"PARSE_ERROR":"REQUEST_FAILED")}if(m!==void 0&&clearTimeout(m),!l.ok){if(this.logger.logResponse(e,p,l.status,Date.now()-w,u),u&&typeof u=="object"&&"error"in u&&u.error!==null&&typeof u.error=="object"){let y=u.error;throw new c(y.message||l.statusText||"Request failed",y.statusCode||l.status,y.code||y.error||"REQUEST_FAILED",y.nextActions)}throw new c(`Request failed: ${l.statusText}`,l.status,"REQUEST_FAILED")}return this.logger.logResponse(e,p,l.status,Date.now()-w,u),u&&typeof u=="object"&&"data"in u&&"error"in u&&u.error===null?u.data:u}catch(l){if(m!==void 0&&clearTimeout(m),l?.name==="AbortError")throw h&&h.signal.aborted&&this.timeout>0&&!o?.aborted?new c(`Request timed out after ${this.timeout}ms`,408,"REQUEST_TIMEOUT"):l;if(l instanceof c)throw l;if(f<T){H=l;continue}throw new c(`Network request failed: ${l?.message||"Unknown error"}`,0,"NETWORK_ERROR")}}throw H||new c("Request failed after all retry attempts",0,"NETWORK_ERROR")}async request(e,t,r={}){try{return await this.handleRequest(e,t,{...r})}catch(s){if(s instanceof c&&s.statusCode===401&&s.error==="INVALID_TOKEN"&&this.autoRefreshToken)try{let i=await this.handleTokenRefresh();return this.setAuthToken(i.accessToken),this.tokenManager.saveSession(i),i.csrfToken&&F(i.csrfToken),i.refreshToken&&this.setRefreshToken(i.refreshToken),await this.handleRequest(e,t,{...r})}catch(i){throw this.tokenManager.clearSession(),this.userToken=null,this.refreshToken=null,D(),i}throw s}}async rawFetch(e,t={}){let r=this.buildUrl(e),s=new Headers(t.headers??{});for(let[i,a]of Object.entries(this.defaultHeaders))s.has(i)||s.set(i,a);if(!s.has("Authorization")){let i=this.userToken??this.anonKey;i&&s.set("Authorization",`Bearer ${i}`);}return this.fetch(r,{...t,headers:s})}get(e,t){return this.request("GET",e,t)}post(e,t,r){return this.request("POST",e,{...r,body:t})}put(e,t,r){return this.request("PUT",e,{...r,body:t})}patch(e,t,r){return this.request("PATCH",e,{...r,body:t})}delete(e,t){return this.request("DELETE",e,t)}setAuthToken(e){this.userToken=e;}setRefreshToken(e){this.refreshToken=e;}getHeaders(){let e={...this.defaultHeaders},t=this.userToken||this.anonKey;return t&&(e.Authorization=`Bearer ${t}`),e}async handleTokenRefresh(){return this.isRefreshing?this.refreshPromise:(this.isRefreshing=true,this.refreshPromise=(async()=>{try{let e=Y(),t=this.refreshToken?{refreshToken:this.refreshToken}:void 0,r=await this.handleRequest("POST","/api/auth/refresh",{body:t,headers:e?{"X-CSRF-Token":e}:{},credentials:"include"});return P(r)}finally{this.isRefreshing=false,this.refreshPromise=null;}})(),this.refreshPromise)}};function E(n,e){return n instanceof c?{data:null,error:n}:{data:null,error:new c(n instanceof Error?n.message:e,500,"AUTH_ERROR")}}var A=class{constructor(e,t){this.http=e;this.tokenManager=t;this.tokenManager.onTokenChange=()=>this._emitFromTokenChange();}http;tokenManager;stateChangeListeners=new Set;lastEmittedUserId=null;lastEmittedAccessToken=null;onAuthStateChange(e){return this.stateChangeListeners.add(e),{unsubscribe:()=>{this.stateChangeListeners.delete(e);}}}emit(e,t){for(let r of this.stateChangeListeners)try{r(e,t);}catch{}}_emitFromTokenChange(){let e=this.tokenManager.getSession(),t=this.lastEmittedUserId,r=this.lastEmittedAccessToken;if(!e){t!==null&&(this.lastEmittedUserId=null,this.lastEmittedAccessToken=null,this.emit("SIGNED_OUT",null));return}let s=e.user.id;if(t===null){this.lastEmittedUserId=s,this.lastEmittedAccessToken=e.accessToken,this.emit("SIGNED_IN",e);return}if(t!==s){this.lastEmittedUserId=null,this.lastEmittedAccessToken=null,this.emit("SIGNED_OUT",null),this.lastEmittedUserId=s,this.lastEmittedAccessToken=e.accessToken,this.emit("SIGNED_IN",e);return}r!==e.accessToken&&(this.lastEmittedAccessToken=e.accessToken,this.emit("TOKEN_REFRESHED",e));}saveSessionFromResponse(e){let t={accessToken:e.accessToken,refreshToken:e.refreshToken,user:e.user};e.csrfToken&&F(e.csrfToken),this.tokenManager.saveSession(t),this.http.setAuthToken(e.accessToken),this.http.setRefreshToken(e.refreshToken??null);}async signUp(e){try{let t=await this.http.post("/api/auth/register",e,{credentials:"include"}),r=P(t);return r?.accessToken&&r.user&&this.saveSessionFromResponse(r),{data:r,error:null}}catch(t){return E(t,"Sign up failed")}}async signInWithPassword(e){try{let t=await this.http.post("/api/auth/login",e,{credentials:"include"}),r=P(t);return r?.accessToken&&r.user&&this.saveSessionFromResponse(r),{data:r,error:null}}catch(t){return E(t,"Sign in failed")}}async signOut(){try{try{await this.http.post("/api/auth/logout",void 0,{credentials:"include"});}catch{}return this.tokenManager.clearSession(),this.http.setAuthToken(null),this.http.setRefreshToken(null),D(),{error:null}}catch{return {error:new c("Failed to sign out",500,"SIGNOUT_ERROR")}}}async refreshSession(){try{let e=await this.http.handleTokenRefresh();return e?.accessToken&&e.user&&this.saveSessionFromResponse(e),{data:e,error:null}}catch(e){return E(e,"Session refresh failed")}}async initialize(){if(!this.tokenManager.restoreSession())return this.emit("INITIAL_SESSION",null),{data:null,error:new c("No persisted session",0,"NO_SESSION")};let t=this.tokenManager.getSession();if(!t)return this.emit("INITIAL_SESSION",null),{data:null,error:new c("No persisted session",0,"NO_SESSION")};this.http.setAuthToken(t.accessToken);let r=this.tokenManager.getRefreshToken();r&&this.http.setRefreshToken(r),this.lastEmittedUserId=t.user.id,this.lastEmittedAccessToken=t.accessToken;try{let s=await this.http.get("/api/auth/sessions/current");if(s?.user){this.tokenManager.setUser(s.user);let i=this.tokenManager.getSession();return this.emit("INITIAL_SESSION",i),{data:{user:s.user,accessToken:t.accessToken},error:null}}return this.tokenManager.clearSession(),this.http.setAuthToken(null),this.http.setRefreshToken(null),this.emit("INITIAL_SESSION",null),{data:null,error:new c("Invalid session",401,"INVALID_SESSION")}}catch(s){return this.tokenManager.clearSession(),this.http.setAuthToken(null),this.http.setRefreshToken(null),this.emit("INITIAL_SESSION",null),E(s,"Session restore failed")}}getSession(){return this.tokenManager.getSession()}getUser(){return this.tokenManager.getUser()}async getCurrentUser(){try{let e=await this.http.get("/api/auth/sessions/current");if(e?.user){let t={accessToken:this.tokenManager.getSession()?.accessToken??"",user:e.user};this.tokenManager.saveSession(t);}return {data:e,error:null}}catch(e){return E(e,"Failed to get current user")}}async getProfile(e){try{return {data:await this.http.get(`/api/auth/profiles/${encodeURIComponent(e)}`),error:null}}catch(t){return E(t,"Failed to get profile")}}async setProfile(e){try{let t=await this.http.patch("/api/auth/profiles/current",{profile:e}),r=this.tokenManager.getUser();if(t?.profile&&r){let s={...r,profile:t.profile};this.tokenManager.setUser(s),this.emit("USER_UPDATED",this.tokenManager.getSession());}return {data:t,error:null}}catch(t){return E(t,"Failed to update profile")}}};function oe(n,e,t){return async(r,s)=>{let i=typeof r=="string"?r:r.toString(),a=new URL(i),o=a.pathname.startsWith("/")?a.pathname.slice(1):a.pathname,g=o.match(/^rpc\/(.+)$/),p=g?`/api/database/rpc/${g[1]}`:`/api/database/records/${o}`,w=`${n.baseUrl}${p}${a.search}`,x=new Headers(s?.headers);if(!x.has("Authorization")){let T=e.getAccessToken()??t;T&&x.set("Authorization",`Bearer ${T}`);}return fetch(w,{...s,headers:x})}}var O=class{postgrest;httpClient;constructor(e,t,r){this.httpClient=e,this.postgrest=new PostgrestClient("http://dummy",{fetch:oe(e,t,r),headers:{}});}from(e){if(!e||typeof e!="string")throw new c("Database.from(table) requires a non-empty string",400,"INVALID_TABLE_NAME");return this.postgrest.from(e)}rpc(e,t,r){return this.postgrest.rpc(e,t,r)}getUrl(){return this.httpClient.baseUrl}};var ce=2e4;function z(n,e){let t=new Error(e);return t.code=n,t}var le=1e4,N=class{constructor(e,t,r={}){this.topic=e;this.realtime=t;this.options=r;}topic;realtime;bindings=[];state="closed";statusCallback=null;presence={};trackedState=null;presenceHeartbeat=null;lastBroadcastTimestamp=null;options;get isPrivate(){return this.options.config?.private===true}get presenceKey(){return this.options.config?.presence?.key}_state(){return this.state}async _rejoinAfterReconnect(){if(this.state!=="closed"){for(let e of this.bindings)e.type==="postgres_changes"&&(e.subscriptionId=void 0);this.state="joining";try{if(await this.registerAllBindings(),this.trackedState){let e=this.realtime._getSocket(),t=this.presenceKey;e?.emit("realtime:presence:track",t!==void 0?{channel:this.topic,state:this.trackedState,key:t}:{channel:this.topic,state:this.trackedState});}this.lastBroadcastTimestamp&&this.bindings.some(e=>e.type==="broadcast")&&this.replay({since:this.lastBroadcastTimestamp}).catch(()=>{}),this.state="joined",this.statusCallback?.("SUBSCRIBED");}catch(e){this.state="errored",this.statusCallback?.("CHANNEL_ERROR",z("REJOIN_FAILED",e instanceof Error?e.message:String(e)));}}}on(e,t,r){return e==="postgres_changes"?this.bindings.push({type:"postgres_changes",filter:t,callback:r}):e==="broadcast"?this.bindings.push({type:"broadcast",filter:t,callback:r}):this.bindings.push({type:"presence",filter:t,callback:r}),this}async track(e){let t=this.realtime._getSocket();if(!t)throw new c("Socket not connected",503,"NOT_CONNECTED");this.trackedState=e;let r=this.presenceKey;t.emit("realtime:presence:track",r!==void 0?{channel:this.topic,state:e,key:r}:{channel:this.topic,state:e}),this.presenceHeartbeat||(this.presenceHeartbeat=setInterval(()=>{let i=this.realtime._getSocket();i&&this.trackedState&&i.emit("realtime:presence:track",r!==void 0?{channel:this.topic,state:this.trackedState,key:r}:{channel:this.topic,state:this.trackedState});},ce),this.presenceHeartbeat.unref?.());}untrack(){this.trackedState=null,this.presenceHeartbeat&&(clearInterval(this.presenceHeartbeat),this.presenceHeartbeat=null),this.realtime._getSocket()?.emit("realtime:presence:untrack",{channel:this.topic});}presenceState(){return this.presence}subscribe(e){return this.statusCallback=e??null,this.state==="joining"||this.state==="joined"?this:(this.state="joining",this.realtime.connect().then(async()=>{try{await this.registerAllBindings(),this.state="joined",this.statusCallback?.("SUBSCRIBED");}catch(t){this.state="errored";let r=t instanceof Error?t.message:String(t);this.statusCallback?.("CHANNEL_ERROR",z("SUBSCRIBE_FAILED",r));}},t=>{this.state="errored",this.statusCallback?.("CHANNEL_ERROR",z("CONNECT_FAILED",t.message));}),this)}async unsubscribe(){if(this.state==="closed")return;let e=this.realtime._getSocket();if(this.presenceHeartbeat&&(clearInterval(this.presenceHeartbeat),this.presenceHeartbeat=null),!e){this.trackedState=null,this.state="closed";return}this.trackedState&&(e.emit("realtime:presence:untrack",{channel:this.topic}),this.trackedState=null);let t=this.bindings.filter(r=>r.type==="postgres_changes");for(let r of t)r.subscriptionId&&(e.emit("realtime:postgres_changes:unsubscribe",{subscription_id:r.subscriptionId}),r.subscriptionId=void 0);this.bindings.some(r=>r.type==="broadcast"||r.type==="presence")&&e.emit("realtime:unsubscribe",{channel:this.topic}),this.realtime._detachChannel(this),this.state="closed",this.statusCallback?.("CLOSED");}async send(e){if(e.type!=="broadcast")throw new c('Only "broadcast" sends are supported \u2014 DB changes flow via your DB writes, not channel.send()',400,"UNSUPPORTED_SEND_TYPE");if(new Set(["postgres_changes","presence_state","presence_join","presence_leave"]).has(e.event))throw new c(`"${e.event}" is a reserved event name \u2014 pick a different name for broadcast events`,400,"RESERVED_EVENT_NAME");let r=this.realtime._getSocket();if(!r)throw new c("Socket not connected",503,"NOT_CONNECTED");let s=this.options.config?.broadcast?.self,i={channel:this.topic,event:e.event,payload:e.payload};return s===false&&(i.self=false),await new Promise(a=>{r.emit("realtime:publish",i,o=>{a(o);});})}async replay(e){let t=this.realtime._getSocket();if(!t)throw new c("Socket not connected",503,"NOT_CONNECTED");t.emit("realtime:broadcast:replay",{channel:this.topic,since:e.since,limit:e.limit,private:this.isPrivate});}_dispatch(e,t){if(e==="postgres_changes"){let s=t;for(let i of this.bindings)if(!(i.type!=="postgres_changes"||!i.subscriptionId||!s.ids.includes(i.subscriptionId)||!(i.filter.event==="*"||i.filter.event===s.data.eventType)))try{i.callback(s.data);}catch{}return}if(e==="presence_state"||e==="presence_join"||e==="presence_leave"){let s=t;if(s.channel!==this.topic)return;if(e==="presence_state"&&s.state)this.presence={...s.state},this.firePresence({event:"sync",state:this.presence});else if(e==="presence_join"&&s.joins)Object.assign(this.presence,s.joins),this.firePresence({event:"join",joins:s.joins});else if(e==="presence_leave"&&s.leaves){for(let i of Object.keys(s.leaves))delete this.presence[i];this.firePresence({event:"leave",leaves:s.leaves});}return}let r=t.meta;r?.timestamp&&(!this.lastBroadcastTimestamp||r.timestamp>this.lastBroadcastTimestamp)&&(this.lastBroadcastTimestamp=r.timestamp);for(let s of this.bindings){if(s.type!=="broadcast"||s.filter.event!==e)continue;let{meta:i,...a}=t;try{s.callback({type:"broadcast",event:e,payload:a});}catch{}}}firePresence(e){for(let t of this.bindings)if(t.type==="presence"&&t.filter.event===e.event)try{e.event==="sync"?t.callback():(e.event,t.callback(e));}catch{}}async registerAllBindings(){let e=this.realtime._getSocket();if(!e)throw new Error("Socket not available");this.bindings.some(s=>s.type==="broadcast"||s.type==="presence")&&await new Promise((s,i)=>{e.emit("realtime:subscribe",{channel:this.topic,private:this.isPrivate},a=>{a.status==="ok"?s():i(new Error(a.error?.message??"subscribe failed"));});});let r=this.bindings.filter(s=>s.type==="postgres_changes");await Promise.all(r.map(s=>new Promise((i,a)=>{e.emit("realtime:postgres_changes:subscribe",{event:s.filter.event,schema:s.filter.schema??"public",table:s.filter.table,filter:s.filter.filter},o=>{o.status==="ok"&&o.subscription_id?(s.subscriptionId=o.subscription_id,i()):a(new Error(o.error?.message??"postgres_changes subscribe failed"));});})));}},B=class{socket=null;baseUrl;options;anonKey;tokenManager;channels=new Map;connecting=null;firstConnected=false;constructor(e,t,r,s={}){this.baseUrl=e,this.tokenManager=t,this.anonKey=r,this.options=s;}get isConnected(){return this.socket?.connected===true}get socketId(){return this.socket?.id}channel(e,t){let r=this.channels.get(e);if(r)return r;let s=new N(e,this,t);return this.channels.set(e,s),s}connect(){return this.isConnected?Promise.resolve():this.connecting?this.connecting:(this.connecting=this.openSocket(),this.connecting)}disconnect(){this.socket&&(this.socket.disconnect(),this.socket=null,this.firstConnected=false);}_getSocket(){return this.socket}_detachChannel(e){this.channels.delete(e.topic);}openSocket(){let e=this.tokenManager.getAccessToken()??this.anonKey;if(!e){let s=new c("Realtime requires an access token or anonKey",401,"AUTH_INVALID_API_KEY");return this.connecting=null,Promise.reject(s)}let t=this.options.timeoutMs??le,r=io(this.baseUrl,{path:this.options.path,transports:this.options.transports??["websocket"],auth:{token:e,...this.options.extraAuth??{}},reconnection:true,timeout:t});return this.socket=r,r.onAny((s,...i)=>this.dispatch(s,i)),r.on("connect",()=>{if(this.firstConnected)for(let s of this.channels.values()){let i=s._state();(i==="joined"||i==="errored")&&s._rejoinAfterReconnect();}}),new Promise((s,i)=>{let a=setTimeout(()=>{r.off("connect",g),r.off("connect_error",p),this.connecting=null,i(new c(`Realtime connection timeout after ${t}ms`,408,"CONNECTION_TIMEOUT"));},t),o=()=>{clearTimeout(a),r.off("connect",g),r.off("connect_error",p);},g=()=>{o(),this.connecting=null,this.firstConnected=true,s();},p=w=>{o(),this.connecting=null,i(new c(w.message,0,"CONNECTION_FAILED"));};r.once("connect",g),r.once("connect_error",p);})}dispatch(e,t){if(e==="postgres_changes"){let s=t[0]??{};this.channels.forEach(i=>i._dispatch("postgres_changes",s));return}if(e==="connect"||e==="disconnect"||e==="connect_error"||e==="error"||e==="realtime:error"||e==="realtime:shutdown")return;let r=t[0]??{};this.channels.forEach(s=>s._dispatch(e,r));}};function L(n){return {id:n.id,name:n.name,public:n.public,fileSizeLimitBytes:n.file_size_limit_bytes,allowedMimeTypes:n.allowed_mime_types,createdAt:n.created_at,updatedAt:n.updated_at}}function j(n,e){return {id:n.id,bucket:e,key:n.key,size:n.size,mimeType:n.mime_type,etag:n.etag,cacheControl:n.cache_control,contentDisposition:n.content_disposition,uploadedBy:n.uploaded_by,uploadedAt:n.uploaded_at,updatedAt:n.updated_at}}function ue(n){return {defaultFileSizeLimitBytes:n.default_file_size_limit_bytes,maxFileSizeLimitBytes:n.max_file_size_limit_bytes,tenantStorageQuotaBytes:n.tenant_storage_quota_bytes,reservedSpaceBytes:n.reserved_space_bytes,signedUrlDefaultTtlSec:n.signed_url_default_ttl_sec,signedUrlMaxTtlSec:n.signed_url_max_ttl_sec}}function de(n){return n.split("/").map(encodeURIComponent).join("/")}function d(n,e){return n instanceof c?{data:null,error:n}:{data:null,error:new c(n instanceof Error?n.message:e,0,"STORAGE_ERROR")}}async function W(n){let e="STORAGE_ERROR",t=`HTTP ${n.status}`;try{let r=await n.json();r&&r.error&&(e=r.error.code??e,t=r.error.message??t);}catch{}return new c(t,n.status,e)}var $=class{constructor(e,t){this.http=e;this.bucketName=t;}http;bucketName;bucketBase(){return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`}objectPath(e){return `${this.bucketBase()}/objects/${de(e)}`}async upload(e,t,r={}){try{let s=r.upsert?"PUT":"POST",i={"Content-Type":r.contentType??"application/octet-stream"};r.cacheControl&&(i["Cache-Control"]=r.cacheControl),r.contentDisposition&&(i["Content-Disposition"]=r.contentDisposition);let a=await this.http.rawFetch(this.objectPath(e),{method:s,headers:i,body:t,signal:r.abortSignal});if(!a.ok)return {data:null,error:await W(a)};let o=await a.json();return o.error||!o.data?{data:null,error:new c(o.error?.message??"Upload failed",a.status,o.error?.code??"STORAGE_ERROR")}:{data:j(o.data,this.bucketName),error:null}}catch(s){return d(s,"Upload failed")}}async download(e,t={}){try{let r={};t.range&&(r.Range=`bytes=${t.range.start}-${t.range.end}`);let s=await this.http.rawFetch(this.objectPath(e),{method:"GET",headers:r,signal:t.abortSignal});return s.ok?{data:await s.blob(),error:null}:{data:null,error:await W(s)}}catch(r){return d(r,"Download failed")}}async getStream(e,t={}){try{let r={};t.range&&(r.Range=`bytes=${t.range.start}-${t.range.end}`);let s=await this.http.rawFetch(this.objectPath(e),{method:"GET",headers:r,signal:t.abortSignal});return s.ok?s.body?{data:s.body,error:null}:{data:null,error:new c("Response body is not a stream",s.status,"STORAGE_ERROR")}:{data:null,error:await W(s)}}catch(r){return d(r,"Download failed")}}async remove(e){try{let t=await Promise.allSettled(e.map(i=>this.http.rawFetch(this.objectPath(i),{method:"DELETE"}))),r=[],s=[];for(let i=0;i<e.length;i++){let a=e[i],o=t[i];if(o.status==="fulfilled"&&o.value.ok)r.push(a);else if(o.status==="fulfilled")s.push(`${a}: HTTP ${o.value.status}`);else {let g=o.reason instanceof Error?o.reason.message:String(o.reason);s.push(`${a}: ${g}`);}}return s.length>0?{data:null,error:new c(`Failed to delete some objects: ${s.join("; ")}`,0,"STORAGE_ERROR")}:{data:{removed:r},error:null}}catch(t){return d(t,"Delete failed")}}async list(e={}){try{let t={};return e.prefix!==void 0&&(t.prefix=e.prefix),e.limit!==void 0&&(t.limit=String(e.limit)),e.startAfter!==void 0&&(t.start_after=e.startAfter),{data:(await this.http.get(`${this.bucketBase()}/objects`,{params:t})).map(s=>j(s,this.bucketName)),error:null}}catch(t){return d(t,"List failed")}}async copy(e,t,r){try{let s=await this.http.post(`${this.objectPath(e)}/copy`,{dest_bucket:r??this.bucketName,dest_key:t});return {data:j(s,r??this.bucketName),error:null}}catch(s){return d(s,"Copy failed")}}async move(e,t,r){try{let s=await this.http.post(`${this.objectPath(e)}/move`,{dest_bucket:r??this.bucketName,dest_key:t});return {data:j(s,r??this.bucketName),error:null}}catch(s){return d(s,"Move failed")}}async createSignedUrl(e,t={}){try{let r={};t.expiresIn!==void 0&&(r.expires_in=t.expiresIn);let s=await this.http.post(`${this.objectPath(e)}/sign`,r);return {data:{url:s.url,token:s.token,expiresAt:s.expiresAt},error:null}}catch(r){return d(r,"Sign failed")}}getPublicUrl(e){return {data:{url:`${this.http.baseUrl.replace(/\/$/,"")}${this.objectPath(e)}`}}}},I=class{constructor(e){this.http=e;}http;from(e){return new $(this.http,e)}async listBuckets(){try{return {data:(await this.http.get("/api/storage/buckets")).map(L),error:null}}catch(e){return d(e,"listBuckets failed")}}async getBucket(e){try{let t=await this.http.get(`/api/storage/buckets/${encodeURIComponent(e)}`);return {data:L(t),error:null}}catch(t){return d(t,"getBucket failed")}}async createBucket(e,t={}){try{let r={name:e};t.public!==void 0&&(r.public=t.public),t.fileSizeLimitBytes!==void 0&&(r.file_size_limit_bytes=t.fileSizeLimitBytes),t.allowedMimeTypes!==void 0&&(r.allowed_mime_types=t.allowedMimeTypes);let s=await this.http.post("/api/storage/buckets",r);return {data:L(s),error:null}}catch(r){return d(r,"createBucket failed")}}async updateBucket(e,t){try{let r={};t.public!==void 0&&(r.public=t.public),t.fileSizeLimitBytes!==void 0&&(r.file_size_limit_bytes=t.fileSizeLimitBytes),t.allowedMimeTypes!==void 0&&(r.allowed_mime_types=t.allowedMimeTypes);let s=await this.http.patch(`/api/storage/buckets/${encodeURIComponent(e)}`,r);return {data:L(s),error:null}}catch(r){return d(r,"updateBucket failed")}}async deleteBucket(e){try{return await this.http.delete(`/api/storage/buckets/${encodeURIComponent(e)}`),{data:null,error:null}}catch(t){return d(t,"deleteBucket failed")}}async emptyBucket(e){try{return {data:await this.http.post(`/api/storage/buckets/${encodeURIComponent(e)}/empty`,{}),error:null}}catch(t){return d(t,"emptyBucket failed")}}async getConfig(){try{let e=await this.http.get("/api/storage/config");return {data:ue(e),error:null}}catch(e){return d(e,"getConfig failed")}}};function he(n){return {key:n.key,digest:n.digest,updatedAt:n.updated_at}}function k(n,e){return n instanceof c?{data:null,error:n}:{data:null,error:new c(n instanceof Error?n.message:e,0,"FUNCTIONS_ERROR")}}function ge(n){return typeof n=="object"&&n!==null&&!ArrayBuffer.isView(n)&&!(n instanceof Blob)&&!(n instanceof FormData)&&!(n instanceof URLSearchParams)&&!(n instanceof ReadableStream)}var U=class{constructor(e){this.http=e;}http;async list(){try{return {data:await this.http.get("/api/functions"),error:null}}catch(e){return k(e,"list failed")}}async get(e){try{return {data:await this.http.get(`/api/functions/${encodeURIComponent(e)}`),error:null}}catch(t){return k(t,"get failed")}}async create(e){try{return {data:await this.http.post("/api/functions",e),error:null}}catch(t){return k(t,"create failed")}}async update(e,t){try{return {data:await this.http.put(`/api/functions/${encodeURIComponent(e)}`,t),error:null}}catch(r){return k(r,"update failed")}}async remove(e){try{return await this.http.delete(`/api/functions/${encodeURIComponent(e)}`),{data:{deleted:!0},error:null}}catch(t){return k(t,"remove failed")}}async invoke(e,t={}){try{let r=t.method??"POST",s={...t.headers},i;return t.body!==void 0&&t.body!==null&&(ge(t.body)?(i=JSON.stringify(t.body),s["Content-Type"]||(s["Content-Type"]="application/json")):i=t.body),{data:await this.http.rawFetch(`/api/invoke/${encodeURIComponent(e)}`,{method:r,headers:s,body:i}),error:null}}catch(r){return k(r,"invoke failed")}}async listSecrets(){try{return {data:(await this.http.get("/api/functions/secrets")).secrets.map(he),error:null}}catch(e){return k(e,"listSecrets failed")}}async setSecrets(e){try{return await this.http.put("/api/functions/secrets",{secrets:e}),{data:{saved:!0},error:null}}catch(t){return k(t,"setSecrets failed")}}async deleteSecret(e){try{return await this.http.delete(`/api/functions/secrets/${encodeURIComponent(e)}`),{data:{deleted:!0},error:null}}catch(t){return k(t,"deleteSecret failed")}}};var v=class{http;tokenManager;auth;database;realtime;storage;functions;constructor(e={}){let t=new R(e.debug);this.tokenManager=new S({persistSession:e.persistSession,storageKey:e.storageKey,storage:e.storage,multiTab:e.multiTab}),this.http=new _(e,this.tokenManager,t),this.auth=new A(this.http,this.tokenManager),this.database=new O(this.http,this.tokenManager,e.anonKey),this.realtime=new B(this.http.baseUrl,this.tokenManager,e.anonKey,e.realtime),this.storage=new I(this.http),this.functions=new U(this.http);}getHttpClient(){return this.http}};function Qe(n){return new v(n)}var Xe=v;
5
+ export{A as Auth,O as Database,U as Functions,_ as HttpClient,R as Logger,v as MitwayBaasClient,c as MitwayBaasError,B as Realtime,N as RealtimeChannel,I as Storage,$ as StorageBucketClient,S as TokenManager,Qe as createClient,V as createLocalStorageAdapter,Xe as default};