@mitway/sdk 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -53
- package/dist/index.cjs +5 -2092
- package/dist/index.d.cts +229 -24
- package/dist/index.d.ts +229 -24
- package/dist/index.js +5 -2054
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/lib/logger.ts","../src/lib/token-manager.ts","../src/lib/auth-envelope.ts","../src/lib/http-client.ts","../src/modules/auth.ts","../src/modules/database.ts","../src/modules/realtime.ts","../src/modules/storage.ts","../src/client.ts"],"sourcesContent":["/**\n * @mitway-baas/sdk — TypeScript SDK for the MITWAY-BaaS backend.\n *\n * Currently ships:\n * - auth (signUp, signInWithPassword, signOut, refreshSession, getSession, getUser)\n * - database (PostgREST-backed query builder via @supabase/postgrest-js)\n * - realtime (Socket.IO transport: subscribe / unsubscribe / publish / on)\n *\n * Not yet included (no backend support):\n * - storage\n * - functions\n * - email\n * - ai\n *\n * @packageDocumentation\n */\n\nexport { MitwayBaasClient } from './client';\n\nimport { MitwayBaasClient } from './client';\nimport { MitwayBaasConfig } from './types';\n\n/**\n * Factory function for creating SDK clients.\n *\n * @example\n * ```typescript\n * import { createClient } from '@mitway-baas/sdk';\n *\n * const client = createClient({\n * baseUrl: 'https://acme.api.dev.nttmitway.com',\n * postgrestUrl: 'https://acme.db.dev.nttmitway.com',\n * });\n * ```\n */\nexport function createClient(config: MitwayBaasConfig): MitwayBaasClient {\n return new MitwayBaasClient(config);\n}\n\n// Default export for `import client from '@mitway-baas/sdk'`\nexport default MitwayBaasClient;\n\n// Public types\nexport type {\n MitwayBaasConfig,\n AuthSession,\n AuthRefreshResponse,\n ApiError,\n} from './types';\nexport { MitwayBaasError } from './types';\n\n// Re-export module classes for advanced/TypeScript-friendly usage\nexport { Auth } from './modules/auth';\nexport type { SignUpRequest, SignInRequest, AuthResponse, AuthResult } from './modules/auth';\nexport { Database } from './modules/database';\nexport { Realtime, RealtimeChannel } from './modules/realtime';\nexport type {\n RealtimeOptions,\n RealtimeMessageMeta,\n ChannelOptions,\n ChannelStatus,\n ChannelStatusCallback,\n PostgresChangesEventSelector,\n PostgresChangesFilter,\n PostgresChangesPayload,\n PostgresChangesInsertPayload,\n PostgresChangesUpdatePayload,\n PostgresChangesDeletePayload,\n BroadcastFilter,\n BroadcastPayload,\n PresenceEventSelector,\n PresenceFilter,\n PresenceState,\n PresencePayload,\n PresenceSyncPayload,\n PresenceJoinPayload,\n PresenceLeavePayload,\n} from './modules/realtime';\nexport { Storage, StorageBucketClient } from './modules/storage';\nexport type { StorageResult } from './modules/storage';\nexport type {\n StorageBucket,\n StorageObject,\n StorageConfig,\n CreateBucketOptions,\n UpdateBucketOptions,\n UploadOptions,\n DownloadOptions,\n ListOptions,\n SignedUrlOptions,\n SignedUrlResult,\n UploadBody,\n} from './lib/storage-types';\n\n// Re-export low-level helpers (most consumers won't need these)\nexport { HttpClient } from './lib/http-client';\nexport { TokenManager } from './lib/token-manager';\nexport { Logger } from './lib/logger';\n\n// Public User shape — inlined in lib/user so this package has no\n// MITWAY-BaaS workspace dependencies.\nexport type { User } from './lib/user';\n","/**\n * MITWAY-BaaS SDK types — only SDK-specific shapes live here.\n * The `User` shape is inlined in `./lib/user` so this package has zero\n * MITWAY-BaaS workspace dependencies.\n */\n\nimport type { User } from './lib/user';\nimport type { RealtimeOptions } from './modules/realtime';\n\nexport interface MitwayBaasConfig {\n /**\n * Base URL of the MITWAY-BaaS backend. In production this is typically\n * `https://{cliente}.api.dev.nttmitway.com` (or the equivalent pro\n * domain). The SDK routes all traffic — auth, database (via the\n * backend's PostgREST proxy at `/api/database/records/*`), and future\n * storage/functions — through this one URL.\n *\n * @default \"http://localhost:7130\"\n */\n baseUrl?: string;\n\n /**\n * Anonymous JWT (signed by the tenant's JWT_SECRET, role = \"anon\").\n * Used as a fallback Bearer token for unauthenticated requests when no\n * user session is active.\n */\n anonKey?: string;\n\n /**\n * Custom fetch implementation. Useful in Node < 18 or test environments.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof fetch;\n\n /**\n * Custom default headers included on every request.\n */\n headers?: Record<string, string>;\n\n /**\n * Enable debug logging. `true` logs to console; pass a function to receive\n * log lines instead.\n */\n debug?: boolean | ((message: string, ...args: any[]) => void);\n\n /**\n * Per-request timeout in milliseconds. 0 disables the timeout.\n * @default 30000\n */\n timeout?: number;\n\n /**\n * Number of retry attempts on network errors and 5xx responses. Client\n * errors (4xx) are never retried. 0 disables retries.\n * @default 3\n */\n retryCount?: number;\n\n /**\n * Initial delay before the first retry, in ms. Doubles each attempt with\n * ±15% jitter.\n * @default 500\n */\n retryDelay?: number;\n\n /**\n * Automatically refresh the access token on 401 INVALID_TOKEN responses\n * and retry the original request.\n * @default true\n */\n autoRefreshToken?: boolean;\n\n /**\n * Realtime transport options. See `RealtimeOptions`.\n */\n realtime?: RealtimeOptions;\n\n /**\n * Persist the auth session to `localStorage` so it survives page reloads.\n * Set to `false` for SSR / Node.js environments where `localStorage` is\n * not available.\n * @default true\n */\n persistSession?: boolean;\n\n /**\n * `localStorage` key used to persist the session.\n * @default \"mitway_baas_session\"\n */\n storageKey?: string;\n}\n\n/**\n * Active user session in memory. Mirrors what the auth endpoints return.\n */\nexport interface AuthSession {\n user: User;\n accessToken: string;\n refreshToken?: string;\n expiresAt?: Date;\n}\n\n/**\n * Minimal payload that auth refresh endpoints emit. The SDK uses this to\n * refresh the in-memory session.\n */\nexport interface AuthRefreshResponse {\n user: User;\n accessToken: string;\n refreshToken?: string;\n csrfToken?: string;\n}\n\n/**\n * The `{ data, error }` envelope used by the MITWAY-BaaS backend on every\n * response. The SDK unwraps `data` for happy-path returns and converts\n * `error` into a `MitwayBaasError`.\n */\nexport interface ApiError {\n error: string;\n message: string;\n statusCode: number;\n nextActions?: string;\n}\n\nexport class MitwayBaasError extends Error {\n public statusCode: number;\n public error: string;\n public nextActions?: string;\n\n constructor(\n message: string,\n statusCode: number,\n error: string,\n nextActions?: string,\n ) {\n super(message);\n this.name = 'MitwayBaasError';\n this.statusCode = statusCode;\n this.error = error;\n this.nextActions = nextActions;\n }\n\n static fromApiError(apiError: ApiError): MitwayBaasError {\n return new MitwayBaasError(\n apiError.message,\n apiError.statusCode,\n apiError.error,\n apiError.nextActions,\n );\n }\n}\n","/**\n * Debug logger for the MITWAY-BaaS SDK.\n *\n * Logs HTTP request/response details with automatic redaction of sensitive\n * headers and body fields. Disabled by default; pass `debug: true` (or a\n * custom log function) on the SDK config to enable.\n */\n\ntype LogFunction = (message: string, ...args: any[]) => void;\n\nconst SENSITIVE_HEADERS = ['authorization', 'x-api-key', 'cookie', 'set-cookie'];\n\nconst SENSITIVE_BODY_KEYS = [\n 'password', 'token', 'accesstoken', 'refreshtoken',\n 'authorization', 'secret', 'apikey', 'api_key',\n 'email', 'ssn', 'creditcard', 'credit_card',\n];\n\nfunction redactHeaders(headers: Record<string, string>): Record<string, string> {\n const redacted: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {\n redacted[key] = '***REDACTED***';\n } else {\n redacted[key] = value;\n }\n }\n return redacted;\n}\n\nfunction sanitizeBody(body: any): any {\n if (body === null || body === undefined) return body;\n if (typeof body === 'string') {\n try {\n const parsed = JSON.parse(body);\n return sanitizeBody(parsed);\n } catch {\n return body;\n }\n }\n if (Array.isArray(body)) return body.map(sanitizeBody);\n if (typeof body === 'object') {\n const sanitized: Record<string, any> = {};\n for (const [key, value] of Object.entries(body)) {\n if (SENSITIVE_BODY_KEYS.includes(key.toLowerCase().replace(/[-_]/g, ''))) {\n sanitized[key] = '***REDACTED***';\n } else {\n sanitized[key] = sanitizeBody(value);\n }\n }\n return sanitized;\n }\n return body;\n}\n\nfunction formatBody(body: any): string {\n if (body === undefined || body === null) return '';\n if (typeof body === 'string') {\n try {\n return JSON.stringify(JSON.parse(body), null, 2);\n } catch {\n return body;\n }\n }\n if (typeof FormData !== 'undefined' && body instanceof FormData) {\n return '[FormData]';\n }\n try {\n return JSON.stringify(body, null, 2);\n } catch {\n return '[Unserializable body]';\n }\n}\n\nexport class Logger {\n public enabled: boolean;\n private customLog: LogFunction | null;\n\n constructor(debug?: boolean | LogFunction) {\n if (typeof debug === 'function') {\n this.enabled = true;\n this.customLog = debug;\n } else {\n this.enabled = !!debug;\n this.customLog = null;\n }\n }\n\n log(message: string, ...args: any[]): void {\n if (!this.enabled) return;\n const formatted = `[MITWAY-BaaS Debug] ${message}`;\n if (this.customLog) {\n this.customLog(formatted, ...args);\n } else {\n console.log(formatted, ...args);\n }\n }\n\n warn(message: string, ...args: any[]): void {\n if (!this.enabled) return;\n const formatted = `[MITWAY-BaaS Debug] ${message}`;\n if (this.customLog) {\n this.customLog(formatted, ...args);\n } else {\n console.warn(formatted, ...args);\n }\n }\n\n error(message: string, ...args: any[]): void {\n if (!this.enabled) return;\n const formatted = `[MITWAY-BaaS Debug] ${message}`;\n if (this.customLog) {\n this.customLog(formatted, ...args);\n } else {\n console.error(formatted, ...args);\n }\n }\n\n logRequest(\n method: string,\n url: string,\n headers?: Record<string, string>,\n body?: any\n ): void {\n if (!this.enabled) return;\n const parts: string[] = [`→ ${method} ${url}`];\n if (headers && Object.keys(headers).length > 0) {\n parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);\n }\n const formattedBody = formatBody(sanitizeBody(body));\n if (formattedBody) {\n const truncated = formattedBody.length > 1000\n ? formattedBody.slice(0, 1000) + '... [truncated]'\n : formattedBody;\n parts.push(` Body: ${truncated}`);\n }\n this.log(parts.join('\\n'));\n }\n\n logResponse(\n method: string,\n url: string,\n status: number,\n durationMs: number,\n body?: any\n ): void {\n if (!this.enabled) return;\n const parts: string[] = [\n `← ${method} ${url} ${status} (${durationMs}ms)`,\n ];\n const formattedBody = formatBody(sanitizeBody(body));\n if (formattedBody) {\n const truncated = formattedBody.length > 1000\n ? formattedBody.slice(0, 1000) + '... [truncated]'\n : formattedBody;\n parts.push(` Body: ${truncated}`);\n }\n if (status >= 400) {\n this.error(parts.join('\\n'));\n } else {\n this.log(parts.join('\\n'));\n }\n }\n}\n","/**\n * Token Manager for the MITWAY-BaaS SDK.\n *\n * Stores the access token + user in memory and optionally persists them\n * to `localStorage` so sessions survive page reloads (F5). Browser CSRF\n * token lives in a cookie for the cookie-based refresh flow.\n */\n\nimport type { User } from './user';\nimport type { AuthSession } from '../types';\n\nexport const CSRF_TOKEN_COOKIE = 'mitway_baas_csrf_token';\nconst DEFAULT_STORAGE_KEY = 'mitway_baas_session';\n\nexport interface TokenManagerOptions {\n /** Persist session to localStorage so it survives page reloads. Default: true. */\n persistSession?: boolean;\n /** localStorage key. Default: 'mitway_baas_session'. */\n storageKey?: string;\n}\n\nexport function getCsrfToken(): string | null {\n if (typeof document === 'undefined') return null;\n const match = document.cookie\n .split(';')\n .find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));\n if (!match) return null;\n return match.split('=')[1] || null;\n}\n\nexport function setCsrfToken(token: string): void {\n if (typeof document === 'undefined') return;\n const maxAge = 7 * 24 * 60 * 60; // 7 days\n const secure =\n typeof window !== 'undefined' && window.location.protocol === 'https:'\n ? '; Secure'\n : '';\n document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;\n}\n\nexport function clearCsrfToken(): void {\n if (typeof document === 'undefined') return;\n const secure =\n typeof window !== 'undefined' && window.location.protocol === 'https:'\n ? '; Secure'\n : '';\n document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;\n}\n\nexport class TokenManager {\n private accessToken: string | null = null;\n private refreshToken: string | null = null;\n private user: User | null = null;\n private readonly persistSession: boolean;\n private readonly storageKey: string;\n\n /** Fired when the access token changes (used by long-lived consumers). */\n onTokenChange: (() => void) | null = null;\n\n constructor(opts?: TokenManagerOptions) {\n this.persistSession = opts?.persistSession ?? true;\n this.storageKey = opts?.storageKey ?? DEFAULT_STORAGE_KEY;\n }\n\n saveSession(session: AuthSession): void {\n const tokenChanged = session.accessToken !== this.accessToken;\n this.accessToken = session.accessToken;\n this.user = session.user;\n if (session.refreshToken !== undefined) {\n this.refreshToken = session.refreshToken ?? null;\n }\n this.persist();\n if (tokenChanged && this.onTokenChange) {\n this.onTokenChange();\n }\n }\n\n getSession(): AuthSession | null {\n if (!this.accessToken || !this.user) return null;\n return {\n accessToken: this.accessToken,\n refreshToken: this.refreshToken ?? undefined,\n user: this.user,\n };\n }\n\n getAccessToken(): string | null {\n return this.accessToken;\n }\n\n setAccessToken(token: string): void {\n const tokenChanged = token !== this.accessToken;\n this.accessToken = token;\n this.persist();\n if (tokenChanged && this.onTokenChange) {\n this.onTokenChange();\n }\n }\n\n getRefreshToken(): string | null {\n return this.refreshToken;\n }\n\n setRefreshToken(token: string | null): void {\n this.refreshToken = token;\n this.persist();\n }\n\n getUser(): User | null {\n return this.user;\n }\n\n setUser(user: User): void {\n this.user = user;\n this.persist();\n }\n\n clearSession(): void {\n const hadToken = this.accessToken !== null;\n this.accessToken = null;\n this.refreshToken = null;\n this.user = null;\n this.removePersisted();\n if (hadToken && this.onTokenChange) {\n this.onTokenChange();\n }\n }\n\n /**\n * Restore the session from localStorage. Returns true if a persisted\n * session was found and loaded into memory.\n */\n restoreSession(): boolean {\n if (!this.persistSession || typeof localStorage === 'undefined') return false;\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) return false;\n const stored = JSON.parse(raw) as {\n accessToken: string;\n refreshToken?: string;\n user: User;\n };\n if (!stored.accessToken || !stored.user) return false;\n this.accessToken = stored.accessToken;\n this.refreshToken = stored.refreshToken ?? null;\n this.user = stored.user;\n return true;\n } catch {\n return false;\n }\n }\n\n private persist(): void {\n if (!this.persistSession || typeof localStorage === 'undefined') return;\n if (!this.accessToken || !this.user) return;\n try {\n const data: Record<string, unknown> = {\n accessToken: this.accessToken,\n user: this.user,\n };\n if (this.refreshToken) {\n data.refreshToken = this.refreshToken;\n }\n localStorage.setItem(this.storageKey, JSON.stringify(data));\n } catch {\n // localStorage full or unavailable — degrade silently\n }\n }\n\n private removePersisted(): void {\n if (!this.persistSession || typeof localStorage === 'undefined') return;\n try {\n localStorage.removeItem(this.storageKey);\n } catch {\n // ignore\n }\n }\n}\n","/**\n * Normalize an auth response payload from the MITWAY-BaaS backend into the\n * camelCase shape the SDK exposes to consumers.\n *\n * The backend returns snake_case fields inside the `data` envelope (e.g.\n * `access_token`, `csrf_token`, `refresh_token`) — the SDK's public\n * `AuthResponse` / `AuthRefreshResponse` types use camelCase. This helper\n * maps the three known token fields while leaving everything else intact, so\n * callers can read `accessToken` / `csrfToken` / `refreshToken` regardless of\n * what the wire format looks like.\n *\n * Idempotent: if the payload already uses camelCase (or came from a\n * pre-normalized source), the function returns it unchanged. Safe to call on\n * `null` / `undefined` — returns the value as-is.\n */\nexport function normalizeAuthPayload<T>(raw: T): T {\n if (!raw || typeof raw !== 'object') return raw;\n\n const src = raw as Record<string, unknown>;\n const out: Record<string, unknown> = { ...src };\n let mutated = false;\n\n if ('access_token' in src && !('accessToken' in src)) {\n out.accessToken = src.access_token;\n delete out.access_token;\n mutated = true;\n }\n if ('csrf_token' in src && !('csrfToken' in src)) {\n out.csrfToken = src.csrf_token;\n delete out.csrf_token;\n mutated = true;\n }\n if ('refresh_token' in src && !('refreshToken' in src)) {\n out.refreshToken = src.refresh_token;\n delete out.refresh_token;\n mutated = true;\n }\n\n return (mutated ? out : raw) as T;\n}\n","/**\n * HttpClient with retry, timeout, abort signal composition, and automatic\n * token refresh on 401 INVALID_TOKEN responses.\n *\n * Ported from the InsForge SDK with rebranding plus one route change:\n * the refresh endpoint is `/api/auth/refresh` (MITWAY-BaaS) instead of\n * `/api/auth/sessions/current` (InsForge).\n */\n\nimport {\n MitwayBaasConfig,\n MitwayBaasError,\n AuthRefreshResponse,\n} from '../types';\nimport { Logger } from './logger';\nimport {\n clearCsrfToken,\n getCsrfToken,\n setCsrfToken,\n TokenManager,\n} from './token-manager';\nimport { normalizeAuthPayload } from './auth-envelope';\n\ntype JsonRequestBody = Record<string, unknown> | unknown[] | null;\n\nexport interface RequestOptions extends Omit<RequestInit, 'body'> {\n params?: Record<string, string>;\n body?: RequestInit['body'] | JsonRequestBody;\n /**\n * Allow retrying non-idempotent requests (POST, PATCH). Off by default to\n * prevent duplicate writes on transient server errors.\n */\n idempotent?: boolean;\n}\n\nconst RETRYABLE_STATUS_CODES = new Set([500, 502, 503, 504]);\nconst IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS']);\n\nexport class HttpClient {\n public readonly baseUrl: string;\n public readonly fetch: typeof fetch;\n private defaultHeaders: Record<string, string>;\n private anonKey: string | undefined;\n private userToken: string | null = null;\n private logger: Logger;\n private autoRefreshToken: boolean = true;\n private isRefreshing: boolean = false;\n private refreshPromise: Promise<AuthRefreshResponse> | null = null;\n private tokenManager: TokenManager;\n private refreshToken: string | null = null;\n private timeout: number;\n private retryCount: number;\n private retryDelay: number;\n\n constructor(\n config: MitwayBaasConfig,\n tokenManager?: TokenManager,\n logger?: Logger,\n ) {\n this.baseUrl = config.baseUrl || 'http://localhost:7130';\n this.autoRefreshToken = config.autoRefreshToken ?? true;\n this.fetch =\n config.fetch ||\n (globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : (undefined as any));\n this.anonKey = config.anonKey;\n this.defaultHeaders = {\n ...config.headers,\n };\n this.tokenManager = tokenManager ?? new TokenManager();\n this.logger = logger || new Logger(false);\n this.timeout = config.timeout ?? 30_000;\n this.retryCount = config.retryCount ?? 3;\n this.retryDelay = config.retryDelay ?? 500;\n\n if (!this.fetch) {\n throw new Error(\n 'Fetch is not available. Provide a fetch implementation in the SDK config.',\n );\n }\n }\n\n private buildUrl(path: string, params?: Record<string, string>): string {\n const url = new URL(path, this.baseUrl);\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (key === 'select') {\n // Normalize PostgREST select syntax (whitespace inside relationships).\n let normalizedValue = value.replace(/\\s+/g, ' ').trim();\n normalizedValue = normalizedValue\n .replace(/\\s*\\(\\s*/g, '(')\n .replace(/\\s*\\)\\s*/g, ')')\n .replace(/\\(\\s+/g, '(')\n .replace(/\\s+\\)/g, ')')\n .replace(/,\\s+(?=[^()]*\\))/g, ',');\n url.searchParams.append(key, normalizedValue);\n } else {\n url.searchParams.append(key, value);\n }\n });\n }\n return url.toString();\n }\n\n private isRetryableStatus(status: number): boolean {\n return RETRYABLE_STATUS_CODES.has(status);\n }\n\n private computeRetryDelay(attempt: number): number {\n const base = this.retryDelay * Math.pow(2, attempt - 1);\n const jitter = base * (0.85 + Math.random() * 0.3);\n return Math.round(jitter);\n }\n\n private async handleRequest<T>(\n method: string,\n path: string,\n options: RequestOptions = {},\n ): Promise<T> {\n const {\n params,\n headers = {},\n body,\n signal: callerSignal,\n ...fetchOptions\n } = options as RequestOptions & { signal?: AbortSignal };\n\n const url = this.buildUrl(path, params);\n const startTime = Date.now();\n const canRetry =\n IDEMPOTENT_METHODS.has(method.toUpperCase()) ||\n options.idempotent === true;\n const maxAttempts = canRetry ? this.retryCount : 0;\n\n const requestHeaders: Record<string, string> = {\n ...this.defaultHeaders,\n };\n\n const authToken = this.userToken || this.anonKey;\n if (authToken) {\n requestHeaders['Authorization'] = `Bearer ${authToken}`;\n }\n\n let processedBody: any;\n if (body !== undefined) {\n if (typeof FormData !== 'undefined' && body instanceof FormData) {\n processedBody = body;\n } else {\n if (method !== 'GET') {\n requestHeaders['Content-Type'] = 'application/json;charset=UTF-8';\n }\n processedBody = JSON.stringify(body);\n }\n }\n\n if (headers instanceof Headers) {\n headers.forEach((value, key) => {\n requestHeaders[key] = value;\n });\n } else if (Array.isArray(headers)) {\n headers.forEach(([key, value]) => {\n requestHeaders[key] = value;\n });\n } else {\n Object.assign(requestHeaders, headers);\n }\n\n this.logger.logRequest(method, url, requestHeaders, processedBody);\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxAttempts; attempt++) {\n if (attempt > 0) {\n const delay = this.computeRetryDelay(attempt);\n this.logger.warn(\n `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`,\n );\n if (callerSignal?.aborted) throw callerSignal.reason;\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(callerSignal!.reason);\n };\n const timer = setTimeout(() => {\n if (callerSignal)\n callerSignal.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n if (callerSignal) {\n callerSignal.addEventListener('abort', onAbort, { once: true });\n }\n });\n }\n\n let controller: AbortController | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (this.timeout > 0 || callerSignal) {\n controller = new AbortController();\n\n if (this.timeout > 0) {\n timer = setTimeout(() => controller!.abort(), this.timeout);\n }\n\n if (callerSignal) {\n if (callerSignal.aborted) {\n controller.abort(callerSignal.reason);\n } else {\n const onCallerAbort = () => controller!.abort(callerSignal!.reason);\n callerSignal.addEventListener('abort', onCallerAbort, { once: true });\n controller.signal.addEventListener(\n 'abort',\n () => {\n callerSignal!.removeEventListener('abort', onCallerAbort);\n },\n { once: true },\n );\n }\n }\n }\n\n try {\n const response = await this.fetch(url, {\n method,\n headers: requestHeaders,\n body: processedBody,\n ...fetchOptions,\n ...(controller ? { signal: controller.signal } : {}),\n });\n\n if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {\n if (timer !== undefined) clearTimeout(timer);\n await response.body?.cancel();\n lastError = new MitwayBaasError(\n `Server error: ${response.status} ${response.statusText}`,\n response.status,\n 'SERVER_ERROR',\n );\n continue;\n }\n\n if (response.status === 204) {\n if (timer !== undefined) clearTimeout(timer);\n return undefined as T;\n }\n\n let data: any;\n const contentType = response.headers.get('content-type');\n try {\n if (contentType?.includes('json')) {\n data = await response.json();\n } else {\n data = await response.text();\n }\n } catch (parseErr: any) {\n if (timer !== undefined) clearTimeout(timer);\n throw new MitwayBaasError(\n `Failed to parse response body: ${parseErr?.message || 'Unknown error'}`,\n response.status,\n response.ok ? 'PARSE_ERROR' : 'REQUEST_FAILED',\n );\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n if (!response.ok) {\n this.logger.logResponse(\n method,\n url,\n response.status,\n Date.now() - startTime,\n data,\n );\n // MITWAY-BaaS error envelope: { data: null, error: { code, message, statusCode, nextActions? } }\n if (\n data &&\n typeof data === 'object' &&\n 'error' in data &&\n data.error !== null &&\n typeof data.error === 'object'\n ) {\n const envErr = data.error as {\n code?: string;\n error?: string;\n message?: string;\n statusCode?: number;\n nextActions?: string;\n };\n throw new MitwayBaasError(\n envErr.message || response.statusText || 'Request failed',\n envErr.statusCode || response.status,\n envErr.code || envErr.error || 'REQUEST_FAILED',\n envErr.nextActions,\n );\n }\n throw new MitwayBaasError(\n `Request failed: ${response.statusText}`,\n response.status,\n 'REQUEST_FAILED',\n );\n }\n\n this.logger.logResponse(\n method,\n url,\n response.status,\n Date.now() - startTime,\n data,\n );\n // MITWAY-BaaS success envelope: { data: <payload>, error: null }. The\n // public SDK types describe the unwrapped payload, so peel `.data`\n // here. Responses that are not enveloped (e.g. PostgREST passthroughs\n // or raw text) are returned as-is.\n if (\n data &&\n typeof data === 'object' &&\n 'data' in data &&\n 'error' in data &&\n (data as { error: unknown }).error === null\n ) {\n return (data as { data: T }).data;\n }\n return data as T;\n } catch (err: any) {\n if (timer !== undefined) clearTimeout(timer);\n\n if (err?.name === 'AbortError') {\n if (\n controller &&\n controller.signal.aborted &&\n this.timeout > 0 &&\n !callerSignal?.aborted\n ) {\n throw new MitwayBaasError(\n `Request timed out after ${this.timeout}ms`,\n 408,\n 'REQUEST_TIMEOUT',\n );\n }\n throw err;\n }\n\n if (err instanceof MitwayBaasError) {\n throw err;\n }\n\n if (attempt < maxAttempts) {\n lastError = err;\n continue;\n }\n\n throw new MitwayBaasError(\n `Network request failed: ${err?.message || 'Unknown error'}`,\n 0,\n 'NETWORK_ERROR',\n );\n }\n }\n\n throw (\n lastError ||\n new MitwayBaasError(\n 'Request failed after all retry attempts',\n 0,\n 'NETWORK_ERROR',\n )\n );\n }\n\n async request<T>(\n method: string,\n path: string,\n options: RequestOptions = {},\n ): Promise<T> {\n try {\n return await this.handleRequest<T>(method, path, { ...options });\n } catch (error) {\n if (\n error instanceof MitwayBaasError &&\n error.statusCode === 401 &&\n error.error === 'INVALID_TOKEN' &&\n this.autoRefreshToken\n ) {\n try {\n const newTokenData = await this.handleTokenRefresh();\n this.setAuthToken(newTokenData.accessToken);\n this.tokenManager!.saveSession(newTokenData);\n if (newTokenData.csrfToken) {\n setCsrfToken(newTokenData.csrfToken);\n }\n if (newTokenData.refreshToken) {\n this.setRefreshToken(newTokenData.refreshToken);\n }\n return await this.handleRequest<T>(method, path, { ...options });\n } catch (refreshError) {\n this.tokenManager.clearSession();\n this.userToken = null;\n this.refreshToken = null;\n clearCsrfToken();\n throw refreshError;\n }\n }\n throw error;\n }\n }\n\n /**\n * Low-level fetch helper for binary bodies (uploads) and streamed responses\n * (downloads). Applies the current Bearer token (user session → anon key\n * fallback) plus any configured default headers, resolves `path` against\n * `baseUrl`, and returns the raw `Response` — it does NOT unwrap the\n * `{ data, error }` envelope, so the caller is responsible for status\n * checking and parsing.\n *\n * Used by the storage module for object upload/download paths where the\n * body or response is not JSON.\n */\n public async rawFetch(\n path: string,\n init: RequestInit = {},\n ): Promise<Response> {\n const url = this.buildUrl(path);\n const headers = new Headers(init.headers ?? {});\n\n for (const [k, v] of Object.entries(this.defaultHeaders)) {\n if (!headers.has(k)) headers.set(k, v);\n }\n\n if (!headers.has('Authorization')) {\n const token = this.userToken ?? this.anonKey;\n if (token) headers.set('Authorization', `Bearer ${token}`);\n }\n\n return this.fetch(url, { ...init, headers });\n }\n\n get<T>(path: string, options?: RequestOptions): Promise<T> {\n return this.request<T>('GET', path, options);\n }\n\n post<T>(path: string, body?: any, options?: RequestOptions): Promise<T> {\n return this.request<T>('POST', path, { ...options, body });\n }\n\n put<T>(path: string, body?: any, options?: RequestOptions): Promise<T> {\n return this.request<T>('PUT', path, { ...options, body });\n }\n\n patch<T>(path: string, body?: any, options?: RequestOptions): Promise<T> {\n return this.request<T>('PATCH', path, { ...options, body });\n }\n\n delete<T>(path: string, options?: RequestOptions): Promise<T> {\n return this.request<T>('DELETE', path, options);\n }\n\n setAuthToken(token: string | null) {\n this.userToken = token;\n }\n\n setRefreshToken(token: string | null) {\n this.refreshToken = token;\n }\n\n getHeaders(): Record<string, string> {\n const headers = { ...this.defaultHeaders };\n const authToken = this.userToken || this.anonKey;\n if (authToken) {\n headers['Authorization'] = `Bearer ${authToken}`;\n }\n return headers;\n }\n\n /**\n * Refresh the current session by calling the MITWAY-BaaS refresh endpoint.\n * Note: the route is `/api/auth/refresh` (POST), not the InsForge\n * `/api/auth/sessions/current` route. Returns the new access token + user.\n */\n async handleTokenRefresh(): Promise<AuthRefreshResponse> {\n if (this.isRefreshing) {\n return this.refreshPromise!;\n }\n\n this.isRefreshing = true;\n this.refreshPromise = (async () => {\n try {\n const csrfToken = getCsrfToken();\n const body = this.refreshToken\n ? { refreshToken: this.refreshToken }\n : undefined;\n const response = await this.handleRequest<AuthRefreshResponse>(\n 'POST',\n '/api/auth/refresh',\n {\n body,\n headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {},\n credentials: 'include',\n },\n );\n // Backend returns snake_case (access_token / csrf_token / refresh_token)\n // inside the envelope; our public AuthRefreshResponse is camelCase.\n return normalizeAuthPayload(response);\n } finally {\n this.isRefreshing = false;\n this.refreshPromise = null;\n }\n })();\n\n return this.refreshPromise;\n }\n}\n","/**\n * Auth module — MITWAY-BaaS specific.\n *\n * Targets the routes that the MITWAY-BaaS backend currently exposes:\n * POST /api/auth/register → signUp\n * POST /api/auth/login → signInWithPassword\n * POST /api/auth/logout → signOut\n * POST /api/auth/refresh → refreshSession (also auto-called by HttpClient)\n *\n * OAuth, email verification, password reset, and the InsForge-only\n * profile/sessions admin endpoints are NOT included — the backend does not\n * implement them yet. When it does, port the corresponding methods from the\n * upstream InsForge SDK (`InsForge/InsForge-sdk-js/src/modules/auth/auth.ts`).\n */\n\nimport type { User } from '../lib/user';\nimport { HttpClient } from '../lib/http-client';\nimport { TokenManager, setCsrfToken, clearCsrfToken } from '../lib/token-manager';\nimport { normalizeAuthPayload } from '../lib/auth-envelope';\nimport { AuthSession, MitwayBaasError } from '../types';\n\nexport interface SignUpRequest {\n email: string;\n password: string;\n name?: string;\n}\n\nexport interface SignInRequest {\n email: string;\n password: string;\n}\n\n/**\n * The shape the backend returns from /register, /login, and /refresh. Mirrors\n * what the existing backend handlers send: `{ user, accessToken, refreshToken,\n * csrfToken? }` wrapped in the standard `{ data, error }` envelope. The SDK\n * unwraps the envelope before reaching this type, so consumers see the raw\n * shape.\n */\nexport interface AuthResponse {\n user: User;\n accessToken: string;\n refreshToken?: string;\n csrfToken?: string;\n}\n\nexport type AuthResult<T> = {\n data: T | null;\n error: MitwayBaasError | null;\n};\n\nfunction wrapError<T>(error: unknown, fallbackMessage: string): AuthResult<T> {\n if (error instanceof MitwayBaasError) {\n return { data: null, error };\n }\n return {\n data: null,\n error: new MitwayBaasError(\n error instanceof Error ? error.message : fallbackMessage,\n 500,\n 'AUTH_ERROR',\n ),\n };\n}\n\nexport class Auth {\n constructor(\n private http: HttpClient,\n private tokenManager: TokenManager,\n ) {}\n\n /**\n * Persist the session in memory + HttpClient defaults so subsequent\n * requests carry the new bearer token automatically.\n */\n private saveSessionFromResponse(response: AuthResponse): void {\n const session: AuthSession = {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n user: response.user,\n };\n if (response.csrfToken) {\n setCsrfToken(response.csrfToken);\n }\n this.tokenManager.saveSession(session);\n this.http.setAuthToken(response.accessToken);\n this.http.setRefreshToken(response.refreshToken ?? null);\n }\n\n /**\n * Create a new user account and start a session.\n *\n * @example\n * const { data, error } = await client.auth.signUp({\n * email: 'a@b.com',\n * password: 'a-strong-password',\n * name: 'Alice'\n * });\n */\n async signUp(request: SignUpRequest): Promise<AuthResult<AuthResponse>> {\n try {\n const raw = await this.http.post<AuthResponse>(\n '/api/auth/register',\n request,\n { credentials: 'include' },\n );\n const response = normalizeAuthPayload(raw);\n if (response?.accessToken && response.user) {\n this.saveSessionFromResponse(response);\n }\n return { data: response, error: null };\n } catch (error) {\n return wrapError<AuthResponse>(error, 'Sign up failed');\n }\n }\n\n /**\n * Sign in with email + password and start a session.\n */\n async signInWithPassword(\n request: SignInRequest,\n ): Promise<AuthResult<AuthResponse>> {\n try {\n const raw = await this.http.post<AuthResponse>(\n '/api/auth/login',\n request,\n { credentials: 'include' },\n );\n const response = normalizeAuthPayload(raw);\n if (response?.accessToken && response.user) {\n this.saveSessionFromResponse(response);\n }\n return { data: response, error: null };\n } catch (error) {\n return wrapError<AuthResponse>(error, 'Sign in failed');\n }\n }\n\n /**\n * End the current session. Clears in-memory state even if the backend\n * call fails (network/offline).\n */\n async signOut(): Promise<{ error: MitwayBaasError | null }> {\n try {\n try {\n await this.http.post('/api/auth/logout', undefined, {\n credentials: 'include',\n });\n } catch {\n // Backend logout failure is non-fatal — local state still clears.\n }\n this.tokenManager.clearSession();\n this.http.setAuthToken(null);\n this.http.setRefreshToken(null);\n clearCsrfToken();\n return { error: null };\n } catch {\n return {\n error: new MitwayBaasError('Failed to sign out', 500, 'SIGNOUT_ERROR'),\n };\n }\n }\n\n /**\n * Manually refresh the current session. The HttpClient will call this\n * automatically on 401 INVALID_TOKEN responses; consumers usually do\n * not need to call it directly.\n */\n async refreshSession(): Promise<AuthResult<AuthResponse>> {\n try {\n // handleTokenRefresh already normalizes snake_case → camelCase.\n const response = (await this.http.handleTokenRefresh()) as AuthResponse;\n if (response?.accessToken && response.user) {\n this.saveSessionFromResponse(response);\n }\n return { data: response, error: null };\n } catch (error) {\n return wrapError<AuthResponse>(error, 'Session refresh failed');\n }\n }\n\n /**\n * Restore the session from localStorage and validate it with the backend.\n * Call this once on app startup (e.g. in a React AuthProvider useEffect).\n *\n * Flow:\n * 1. Read persisted session from localStorage.\n * 2. Populate in-memory state (TokenManager + HttpClient).\n * 3. Validate with `GET /api/auth/sessions/current`.\n * - If the access token expired, the HttpClient auto-refresh kicks in\n * using the persisted refresh token (sent in the POST body, not\n * cookies — works cross-site).\n * 4. Return the validated user or an error.\n *\n * If no persisted session exists, returns `{ data: null, error }` — the\n * app should show the login page.\n */\n async initialize(): Promise<AuthResult<AuthResponse>> {\n const restored = this.tokenManager.restoreSession();\n if (!restored) {\n return {\n data: null,\n error: new MitwayBaasError('No persisted session', 0, 'NO_SESSION'),\n };\n }\n\n const session = this.tokenManager.getSession();\n if (!session) {\n return {\n data: null,\n error: new MitwayBaasError('No persisted session', 0, 'NO_SESSION'),\n };\n }\n\n this.http.setAuthToken(session.accessToken);\n const refreshToken = this.tokenManager.getRefreshToken();\n if (refreshToken) {\n this.http.setRefreshToken(refreshToken);\n }\n\n try {\n const response = await this.http.get<{ user: User }>(\n '/api/auth/sessions/current',\n );\n if (response?.user) {\n this.tokenManager.setUser(response.user);\n return {\n data: {\n user: response.user,\n accessToken: session.accessToken,\n } as AuthResponse,\n error: null,\n };\n }\n this.tokenManager.clearSession();\n this.http.setAuthToken(null);\n this.http.setRefreshToken(null);\n return {\n data: null,\n error: new MitwayBaasError('Invalid session', 401, 'INVALID_SESSION'),\n };\n } catch (error) {\n this.tokenManager.clearSession();\n this.http.setAuthToken(null);\n this.http.setRefreshToken(null);\n return wrapError<AuthResponse>(error, 'Session restore failed');\n }\n }\n\n /**\n * Get the current in-memory session, or null if the user is not signed in.\n * Synchronous — does not hit the network.\n */\n getSession(): AuthSession | null {\n return this.tokenManager.getSession();\n }\n\n /**\n * Get the current in-memory user, or null if not signed in.\n */\n getUser(): User | null {\n return this.tokenManager.getUser();\n }\n\n /**\n * Fetch the current user from the backend. Unlike getUser() which reads\n * from memory, this makes a network request and returns the latest data.\n */\n async getCurrentUser(): Promise<AuthResult<{ user: User }>> {\n try {\n const response = await this.http.get<{ user: User }>(\n '/api/auth/sessions/current',\n );\n if (response?.user) {\n const session: AuthSession = {\n accessToken: this.tokenManager.getSession()?.accessToken ?? '',\n user: response.user,\n };\n this.tokenManager.saveSession(session);\n }\n return { data: response, error: null };\n } catch (error) {\n return wrapError<{ user: User }>(error, 'Failed to get current user');\n }\n }\n\n /**\n * Get a user's profile by ID. Requires authentication.\n */\n async getProfile(\n userId: string,\n ): Promise<AuthResult<{ id: string; profile: Record<string, unknown> | null }>> {\n try {\n const response = await this.http.get<{\n id: string;\n profile: Record<string, unknown> | null;\n }>(`/api/auth/profiles/${encodeURIComponent(userId)}`);\n return { data: response, error: null };\n } catch (error) {\n return wrapError<{ id: string; profile: Record<string, unknown> | null }>(\n error,\n 'Failed to get profile',\n );\n }\n }\n\n /**\n * Update the current user's profile. Merges with existing profile data —\n * only the fields you pass are updated, existing fields are preserved.\n */\n async setProfile(\n profile: Record<string, unknown>,\n ): Promise<AuthResult<{ id: string; profile: Record<string, unknown> | null }>> {\n try {\n const response = await this.http.patch<{\n id: string;\n profile: Record<string, unknown> | null;\n }>('/api/auth/profiles/current', { profile });\n return { data: response, error: null };\n } catch (error) {\n return wrapError<{ id: string; profile: Record<string, unknown> | null }>(\n error,\n 'Failed to update profile',\n );\n }\n }\n}\n","/**\n * Database module — wraps `@supabase/postgrest-js` and transforms its\n * outbound URLs so they hit the MITWAY-BaaS backend proxy instead of\n * PostgREST directly.\n *\n * Why via the backend (not direct to PostgREST):\n * PostgREST is deployed as an internal ClusterIP Service per cliente\n * (`mitway-baas-{cliente}-postgrest:3000`), never publicly exposed. The\n * backend forwards incoming `/api/database/records/*` and\n * `/api/database/rpc/*` requests to PostgREST over the cluster network.\n * This collapses the per-cliente public surface into a single URL:\n * `https://{cliente}.api.dev.nttmitway.com`.\n *\n * The pattern is copied from the InsForge SDK's `database-postgrest.ts`:\n * point postgrest-js at a dummy URL, intercept fetch, rewrite.\n *\n * Auth:\n * On every request the fetch wrapper reads the current access token\n * from the TokenManager (or falls back to the SDK's configured\n * `anonKey`) and forwards it as `Authorization: Bearer <jwt>`. The\n * MITWAY-BaaS backend's api-key middleware accepts it, the proxy\n * forwards it to PostgREST, and PostgREST verifies it with the shared\n * tenant JWT_SECRET and enforces RLS policies based on the role claim.\n */\n\nimport { PostgrestClient } from '@supabase/postgrest-js';\nimport { HttpClient } from '../lib/http-client';\nimport { TokenManager } from '../lib/token-manager';\nimport { MitwayBaasError } from '../types';\n\n/**\n * Custom fetch that:\n * 1. Rewrites the URL from postgrest-js's dummy format (`http://dummy/posts`)\n * to the MITWAY-BaaS backend proxy path\n * (`{baseUrl}/api/database/records/posts` or\n * `{baseUrl}/api/database/rpc/{fn}`).\n * 2. Injects the current access token (or anon key fallback) as the\n * `Authorization: Bearer <jwt>` header if one isn't already set.\n */\nfunction createMitwayBaasFetch(\n httpClient: HttpClient,\n tokenManager: TokenManager,\n anonKey: string | undefined,\n): typeof fetch {\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === 'string' ? input : input.toString();\n const urlObj = new URL(url);\n\n // postgrest-js sends: http://dummy/tablename?params for tables\n // http://dummy/rpc/fnname?params for stored fns\n // Rewrite pathname to the backend proxy path. Strip the leading `/`\n // to make the join unambiguous.\n const pathname = urlObj.pathname.startsWith('/') ? urlObj.pathname.slice(1) : urlObj.pathname;\n const rpcMatch = pathname.match(/^rpc\\/(.+)$/);\n const endpoint = rpcMatch\n ? `/api/database/rpc/${rpcMatch[1]}`\n : `/api/database/records/${pathname}`;\n\n const targetUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;\n\n // Inject auth. Prefer the current user token, fall back to anonKey.\n // Don't overwrite Authorization if the caller already set one.\n const headers = new Headers(init?.headers);\n if (!headers.has('Authorization')) {\n const token = tokenManager.getAccessToken() ?? anonKey;\n if (token) {\n headers.set('Authorization', `Bearer ${token}`);\n }\n }\n\n return fetch(targetUrl, { ...init, headers });\n };\n}\n\nexport class Database {\n private postgrest: PostgrestClient<any, any, any>;\n private httpClient: HttpClient;\n\n constructor(\n httpClient: HttpClient,\n tokenManager: TokenManager,\n anonKey: string | undefined,\n ) {\n this.httpClient = httpClient;\n // The URL we pass to PostgrestClient is a dummy — the fetch wrapper\n // above rewrites every outgoing URL before it reaches the network.\n // Keeping it as `http://dummy` makes it obvious in stack traces that\n // nothing should ever actually dial this address.\n this.postgrest = new PostgrestClient<any, any, any>('http://dummy', {\n fetch: createMitwayBaasFetch(httpClient, tokenManager, anonKey),\n headers: {},\n });\n }\n\n /**\n * Build a PostgREST query against a table.\n *\n * @example\n * const { data, error } = await client.database\n * .from('posts')\n * .select('*')\n * .eq('user_id', userId)\n * .order('created_at', { ascending: false })\n * .limit(10);\n *\n * @example\n * const { data, error } = await client.database\n * .from('posts')\n * .insert({ title: 'Hello', content: 'World' })\n * .select()\n * .single();\n */\n from(table: string) {\n if (!table || typeof table !== 'string') {\n throw new MitwayBaasError(\n 'Database.from(table) requires a non-empty string',\n 400,\n 'INVALID_TABLE_NAME',\n );\n }\n return this.postgrest.from(table);\n }\n\n /**\n * Call a PostgreSQL stored function (RPC).\n *\n * @example\n * const { data, error } = await client.database\n * .rpc('get_user_stats', { user_id: 123 });\n */\n rpc(\n fn: string,\n args?: Record<string, unknown>,\n options?: { head?: boolean; get?: boolean; count?: 'exact' | 'planned' | 'estimated' },\n ) {\n return this.postgrest.rpc(fn, args, options);\n }\n\n /**\n * The backend base URL the database client is targeting. Useful for\n * debugging — the actual PostgREST instance is internal and not\n * reachable by the SDK directly.\n */\n getUrl(): string {\n return this.httpClient.baseUrl;\n }\n}\n","/**\n * Realtime module — Socket.IO client exposing the channel API.\n *\n * Design goals:\n * 1. `client.realtime.channel(topic).on(type, filter, cb).subscribe()` is\n * the single entry point for every primitive.\n * 2. Three binding types coexist on a channel:\n * * `postgres_changes` — WAL-based auto-broadcast of DB events,\n * gated by per-subscriber RLS replay in the backend.\n * * `broadcast` — fire-and-forget custom events published\n * via `channel.send({...})` (in-memory fan-out) or via the\n * server-side `realtime.send()` SQL helper.\n * * `presence` — per-channel live \"who's here\" state.\n * 3. One socket per `Realtime` instance. Multiple channels share it.\n *\n * The wire protocol is defined in the backend's socket.manager.ts.\n */\nimport { io, type Socket } from 'socket.io-client';\nimport { TokenManager } from '../lib/token-manager';\nimport { MitwayBaasError } from '../types';\n\n// ---------------------------------------------------------------------------\n// Shared wire types (kept in sync with\n// @mitway-baas/shared-schemas/realtime-postgres-changes.schema.ts — the SDK\n// intentionally has zero workspace dependencies so these are inlined.)\n// ---------------------------------------------------------------------------\n\nexport type PostgresChangesEventSelector = 'INSERT' | 'UPDATE' | 'DELETE' | '*';\n\n/** Filter for any event (including the `*` catch-all). */\nexport interface PostgresChangesFilter {\n event: PostgresChangesEventSelector;\n schema?: string; // defaults to 'public' on the server\n table: string;\n /** PostgREST-style filter: `column=op.value` or `column=in.(a,b,c)`. */\n filter?: string;\n}\n\n/** Common shape shared by every postgres_changes payload variant. */\ninterface PostgresChangesPayloadBase {\n schema: string;\n table: string;\n commit_timestamp: string;\n columns: Array<{ name: string; type: string }>;\n errors?: string[];\n}\n\n/** INSERT: the new row is in `new`. `old` is kept as an empty object so\n * code that accesses both fields never needs optional chaining. */\nexport interface PostgresChangesInsertPayload<T = Record<string, unknown>>\n extends PostgresChangesPayloadBase {\n eventType: 'INSERT';\n new: T;\n old: Record<string, never>;\n}\n\n/** UPDATE: both `new` and `old` are populated. `old` is `Partial<T>`\n * because RLS / REPLICA IDENTITY may strip columns the subscriber can't\n * see. */\nexport interface PostgresChangesUpdatePayload<T = Record<string, unknown>>\n extends PostgresChangesPayloadBase {\n eventType: 'UPDATE';\n new: T;\n old: Partial<T>;\n}\n\n/** DELETE: the deleted row is in `old`. On RLS-enabled tables only\n * primary-key columns are populated. `new` is always an empty object. */\nexport interface PostgresChangesDeletePayload<T = Record<string, unknown>>\n extends PostgresChangesPayloadBase {\n eventType: 'DELETE';\n new: Record<string, never>;\n old: Partial<T>;\n}\n\n/** Discriminated union — what the `'*'` overload of `on('postgres_changes',\n * ...)` passes to the callback. */\nexport type PostgresChangesPayload<T = Record<string, unknown>> =\n | PostgresChangesInsertPayload<T>\n | PostgresChangesUpdatePayload<T>\n | PostgresChangesDeletePayload<T>;\n\ninterface PostgresChangesSocketEvent {\n ids: string[];\n data: PostgresChangesPayload;\n}\n\nexport interface BroadcastFilter {\n event: string;\n}\n\nexport interface BroadcastPayload<T = Record<string, unknown>> {\n type: 'broadcast';\n event: string;\n payload: T;\n}\n\n// --- Presence -----------------------------------------------------------\n\n/** Presence event selector used with `.on('presence', { event }, cb)`. */\nexport type PresenceEventSelector = 'sync' | 'join' | 'leave';\n\nexport interface PresenceFilter {\n event: PresenceEventSelector;\n}\n\n/** State map keyed by opaque client key (the socket id on our server side).\n * The client treats these keys as blackboxes — just compare across sync /\n * join / leave deltas to track which participants appeared or departed. */\nexport type PresenceState<T = Record<string, unknown>> = Record<string, T>;\n\nexport interface PresenceSyncPayload<T = Record<string, unknown>> {\n event: 'sync';\n state: PresenceState<T>;\n}\n\nexport interface PresenceJoinPayload<T = Record<string, unknown>> {\n event: 'join';\n /** One-entry map `{ [key]: state }` with the new or updated state. */\n joins: PresenceState<T>;\n}\n\nexport interface PresenceLeavePayload<T = Record<string, unknown>> {\n event: 'leave';\n /** One-entry map `{ [key]: state }` with the state just before leaving. */\n leaves: PresenceState<T>;\n}\n\nexport type PresencePayload<T = Record<string, unknown>> =\n | PresenceSyncPayload<T>\n | PresenceJoinPayload<T>\n | PresenceLeavePayload<T>;\n\n/** Presence entries are refreshed by the SDK at TTL/2. Must match the\n * backend's `presenceTtlSeconds` (shared-schemas). */\nconst PRESENCE_HEARTBEAT_MS = 20_000;\n\nexport interface RealtimeMessageMeta {\n channel?: string;\n message_id: string;\n sender_id?: string;\n timestamp: string;\n}\n\n/**\n * Channel configuration — passed to `client.realtime.channel(topic, opts)`.\n * Every option has a safe default so apps can omit `opts` entirely for the\n * common case.\n */\nexport interface ChannelOptions {\n config?: {\n /** When true, subscribe is gated on `realtime.authorize_subscribe(role,\n * claims, topic)` at the server side. Replay is also gated if\n * requested on a private channel. Default: `false` (open topic). */\n private?: boolean;\n broadcast?: {\n /** When `false`, the sending socket is excluded from the fan-out.\n * Default: `true` (sender also receives its own broadcasts). */\n self?: boolean;\n };\n presence?: {\n /** Optional stable key that replaces the socket id in the presence\n * hash. Useful to group multiple tabs of the same user under one\n * entry. Default: socket id (each tab is a separate entry). */\n key?: string;\n };\n };\n}\n\nexport type ChannelStatus =\n | 'SUBSCRIBED'\n | 'CHANNEL_ERROR'\n | 'TIMED_OUT'\n | 'CLOSED';\n\n/** Callback invoked for every channel status transition. The second\n * argument is a standard `Error` (so consumers can annotate it as\n * `err: Error` out of the box) with a non-standard `.code` string\n * attached for programmatic discrimination (`SUBSCRIBE_FAILED`,\n * `CONNECT_FAILED`, `REJOIN_FAILED`, …). */\nexport type ChannelStatusCallback = (\n status: ChannelStatus,\n error?: Error & { code?: string }\n) => void;\n\n/** Internal helper: build the Error-shaped object we hand to the status\n * callback. Stays in SDK-only land. */\nfunction makeChannelError(code: string, message: string): Error & { code: string } {\n const err = new Error(message) as Error & { code: string };\n err.code = code;\n return err;\n}\n\nexport interface RealtimeOptions {\n path?: string;\n transports?: Array<'websocket' | 'polling'>;\n timeoutMs?: number;\n extraAuth?: Record<string, string>;\n}\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\n\n// ---------------------------------------------------------------------------\n// Internal binding representation\n// ---------------------------------------------------------------------------\n\ntype PostgresChangesCallback<T = Record<string, unknown>> = (\n payload: PostgresChangesPayload<T>\n) => void;\ntype BroadcastCallback<T = Record<string, unknown>> = (\n payload: BroadcastPayload<T>\n) => void;\ntype PresenceSyncCallback = () => void;\ntype PresenceJoinCallback<T = Record<string, unknown>> = (\n payload: PresenceJoinPayload<T>\n) => void;\ntype PresenceLeaveCallback<T = Record<string, unknown>> = (\n payload: PresenceLeavePayload<T>\n) => void;\ntype PresenceCallback<T = Record<string, unknown>> =\n | PresenceSyncCallback\n | PresenceJoinCallback<T>\n | PresenceLeaveCallback<T>;\n\ninterface PostgresChangesBinding {\n type: 'postgres_changes';\n filter: PostgresChangesFilter;\n callback: PostgresChangesCallback;\n /** Assigned by the backend on subscribe; used to dispatch incoming events. */\n subscriptionId?: string;\n}\n\ninterface BroadcastBinding {\n type: 'broadcast';\n filter: BroadcastFilter;\n callback: BroadcastCallback;\n}\n\ninterface PresenceBinding {\n type: 'presence';\n filter: PresenceFilter;\n callback: PresenceCallback;\n}\n\ntype Binding = PostgresChangesBinding | BroadcastBinding | PresenceBinding;\n\n// ---------------------------------------------------------------------------\n// RealtimeChannel — one topic, many bindings\n// ---------------------------------------------------------------------------\n\nexport class RealtimeChannel {\n private bindings: Binding[] = [];\n private state: 'closed' | 'joining' | 'joined' | 'errored' = 'closed';\n private statusCallback: ChannelStatusCallback | null = null;\n\n /** Local presence state mirror — populated from `presence_state` /\n * `presence_join` / `presence_leave` events. Read via `presenceState()`. */\n private presence: Record<string, Record<string, unknown>> = {};\n /** Latest state this client has tracked. Non-null means the heartbeat\n * timer is active and we'll re-emit this state every TTL/2. */\n private trackedState: Record<string, unknown> | null = null;\n private presenceHeartbeat: ReturnType<typeof setInterval> | null = null;\n\n /** Timestamp of the most recently received broadcast on this channel —\n * used as the `since` anchor on replay after reconnect. ISO-8601. */\n private lastBroadcastTimestamp: string | null = null;\n /** Configuration from `channel(topic, opts)`. Frozen at construction. */\n private readonly options: ChannelOptions;\n\n constructor(\n public readonly topic: string,\n private readonly realtime: Realtime,\n options: ChannelOptions = {}\n ) {\n this.options = options;\n }\n\n /** Whether this channel was opened as `private: true`. */\n private get isPrivate(): boolean {\n return this.options.config?.private === true;\n }\n\n /** The user-supplied presence key, if any. */\n private get presenceKey(): string | undefined {\n return this.options.config?.presence?.key;\n }\n\n /** Internal — exposed for Realtime to drive resubscription after a\n * network hiccup. Returns the current lifecycle state. */\n _state(): 'closed' | 'joining' | 'joined' | 'errored' {\n return this.state;\n }\n\n /** Internal — called by `Realtime` when Socket.IO reconnects after a\n * drop. Re-runs the registration flow; the backend assigns fresh\n * subscription_ids and Socket.IO rejoins the per-subscription rooms,\n * so events resume without developer intervention. The user-provided\n * statusCallback (from the original subscribe()) fires again with\n * 'SUBSCRIBED' or 'CHANNEL_ERROR' so the app can reflect state. */\n async _rejoinAfterReconnect(): Promise<void> {\n if (this.state === 'closed') {\n return;\n }\n // Clear any stale subscription_ids from the previous connection —\n // the backend cleaned them up on disconnect, they no longer map to\n // any row in realtime.subscription.\n for (const b of this.bindings) {\n if (b.type === 'postgres_changes') {\n b.subscriptionId = undefined;\n }\n }\n this.state = 'joining';\n try {\n await this.registerAllBindings();\n // Re-track the previous presence state on the fresh connection so\n // the UI doesn't lose the entry silently after a network blip.\n if (this.trackedState) {\n const socket = this.realtime._getSocket();\n const key = this.presenceKey;\n socket?.emit(\n 'realtime:presence:track',\n key !== undefined\n ? { channel: this.topic, state: this.trackedState, key }\n : { channel: this.topic, state: this.trackedState }\n );\n }\n // Replay any SQL-originated broadcasts we missed while the socket\n // was gone. Bounded at 24 h by the backend regardless of what we\n // send as `since`.\n if (this.lastBroadcastTimestamp && this.bindings.some((b) => b.type === 'broadcast')) {\n void this.replay({ since: this.lastBroadcastTimestamp }).catch(() => undefined);\n }\n this.state = 'joined';\n this.statusCallback?.('SUBSCRIBED');\n } catch (err) {\n this.state = 'errored';\n this.statusCallback?.(\n 'CHANNEL_ERROR',\n makeChannelError('REJOIN_FAILED', err instanceof Error ? err.message : String(err))\n );\n }\n }\n\n // ---- `on()` overloads -----------------------------------------------\n //\n // Each overload narrows the callback parameter type based on the filter\n // literal, so destructuring variant-specific fields (`new` on INSERT,\n // `joins` on presence 'join', …) type-checks against the right shape.\n //\n // ── postgres_changes: one overload per event ──\n on<T = Record<string, unknown>>(\n type: 'postgres_changes',\n filter: { event: 'INSERT'; schema?: string; table: string; filter?: string },\n callback: (payload: PostgresChangesInsertPayload<T>) => void\n ): this;\n on<T = Record<string, unknown>>(\n type: 'postgres_changes',\n filter: { event: 'UPDATE'; schema?: string; table: string; filter?: string },\n callback: (payload: PostgresChangesUpdatePayload<T>) => void\n ): this;\n on<T = Record<string, unknown>>(\n type: 'postgres_changes',\n filter: { event: 'DELETE'; schema?: string; table: string; filter?: string },\n callback: (payload: PostgresChangesDeletePayload<T>) => void\n ): this;\n on<T = Record<string, unknown>>(\n type: 'postgres_changes',\n filter: { event: '*'; schema?: string; table: string; filter?: string },\n callback: (payload: PostgresChangesPayload<T>) => void\n ): this;\n // ── broadcast: single signature, callback narrow already ──\n on<T = Record<string, unknown>>(\n type: 'broadcast',\n filter: BroadcastFilter,\n callback: BroadcastCallback<T>\n ): this;\n // ── presence: one overload per event ──\n on(\n type: 'presence',\n filter: { event: 'sync' },\n callback: PresenceSyncCallback\n ): this;\n on<T = Record<string, unknown>>(\n type: 'presence',\n filter: { event: 'join' },\n callback: PresenceJoinCallback<T>\n ): this;\n on<T = Record<string, unknown>>(\n type: 'presence',\n filter: { event: 'leave' },\n callback: PresenceLeaveCallback<T>\n ): this;\n // ── implementation signature (not in public type surface).\n // The callback type is intentionally broad: each overload above pins a\n // specific payload shape, but TypeScript overload resolution needs the\n // implementation to accept the union of every narrow callback without\n // the contravariance conflict (TS2394). `any` is the standard escape\n // hatch for this exact pattern and is confined to this one line — the\n // public surface users see is strictly typed via the overloads.\n on(\n type: 'postgres_changes' | 'broadcast' | 'presence',\n filter: PostgresChangesFilter | BroadcastFilter | PresenceFilter,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n callback: (payload: any) => void\n ): this {\n if (type === 'postgres_changes') {\n this.bindings.push({\n type: 'postgres_changes',\n filter: filter as PostgresChangesFilter,\n callback: callback as PostgresChangesCallback,\n });\n } else if (type === 'broadcast') {\n this.bindings.push({\n type: 'broadcast',\n filter: filter as BroadcastFilter,\n callback: callback as BroadcastCallback,\n });\n } else {\n this.bindings.push({\n type: 'presence',\n filter: filter as PresenceFilter,\n callback: callback as PresenceCallback,\n });\n }\n return this;\n }\n\n // -------------------------------------------------------------------------\n // Presence — track / untrack / state accessor\n // -------------------------------------------------------------------------\n\n /**\n * Register or refresh this client's presence entry on the channel. Safe\n * to call many times — state replaces (not merges). Starts a heartbeat\n * timer at TTL/2 so the entry stays alive while the socket is open.\n * The channel must be `subscribe()`d first (the server enforces this).\n */\n async track(state: Record<string, unknown>): Promise<void> {\n const socket = this.realtime._getSocket();\n if (!socket) {\n throw new MitwayBaasError('Socket not connected', 503, 'NOT_CONNECTED');\n }\n this.trackedState = state;\n const key = this.presenceKey;\n socket.emit(\n 'realtime:presence:track',\n key !== undefined\n ? { channel: this.topic, state, key }\n : { channel: this.topic, state }\n );\n if (!this.presenceHeartbeat) {\n this.presenceHeartbeat = setInterval(() => {\n const s = this.realtime._getSocket();\n if (s && this.trackedState) {\n s.emit(\n 'realtime:presence:track',\n key !== undefined\n ? { channel: this.topic, state: this.trackedState, key }\n : { channel: this.topic, state: this.trackedState }\n );\n }\n }, PRESENCE_HEARTBEAT_MS);\n // Don't keep the event loop alive just for the heartbeat — if Node\n // has no other work, let it exit.\n const h = this.presenceHeartbeat as unknown as { unref?: () => void };\n h.unref?.();\n }\n }\n\n /**\n * Remove this client's presence entry immediately, stopping the\n * heartbeat. Safe if the client never called `track`.\n */\n untrack(): void {\n this.trackedState = null;\n if (this.presenceHeartbeat) {\n clearInterval(this.presenceHeartbeat);\n this.presenceHeartbeat = null;\n }\n const socket = this.realtime._getSocket();\n socket?.emit('realtime:presence:untrack', { channel: this.topic });\n }\n\n /** Snapshot of the current presence state on this channel. Keys are\n * opaque client identifiers (server-assigned). Re-read inside your\n * `.on('presence', { event: 'sync' }, ...)` handler. */\n presenceState<T = Record<string, unknown>>(): PresenceState<T> {\n return this.presence as PresenceState<T>;\n }\n\n /**\n * Register all bindings with the server:\n * * For each `broadcast` binding, ensure we're subscribed to the topic\n * (one `realtime:subscribe` for the channel as a whole).\n * * For each `postgres_changes` binding, emit\n * `realtime:postgres_changes:subscribe` and record the assigned\n * subscription_id.\n *\n * `statusCallback` is invoked with `'SUBSCRIBED'` when every binding\n * has ack'd, or with `'CHANNEL_ERROR' | 'TIMED_OUT'` on failure.\n */\n subscribe(statusCallback?: ChannelStatusCallback): this {\n this.statusCallback = statusCallback ?? null;\n if (this.state === 'joining' || this.state === 'joined') {\n return this;\n }\n this.state = 'joining';\n\n void this.realtime.connect().then(\n async () => {\n try {\n await this.registerAllBindings();\n this.state = 'joined';\n this.statusCallback?.('SUBSCRIBED');\n } catch (err) {\n this.state = 'errored';\n const message = err instanceof Error ? err.message : String(err);\n this.statusCallback?.(\n 'CHANNEL_ERROR',\n makeChannelError('SUBSCRIBE_FAILED', message)\n );\n }\n },\n (err: Error) => {\n this.state = 'errored';\n this.statusCallback?.(\n 'CHANNEL_ERROR',\n makeChannelError('CONNECT_FAILED', err.message)\n );\n }\n );\n return this;\n }\n\n /**\n * Tear down every binding: unsubscribe from the broadcast topic (if\n * any broadcast bindings exist) and remove every postgres_changes\n * subscription by id. Safe to call when state is already closed.\n */\n async unsubscribe(): Promise<void> {\n if (this.state === 'closed') {\n return;\n }\n const socket = this.realtime._getSocket();\n // Clear the presence heartbeat regardless of socket state so we stop\n // re-tracking in the background even if the socket is already gone.\n if (this.presenceHeartbeat) {\n clearInterval(this.presenceHeartbeat);\n this.presenceHeartbeat = null;\n }\n if (!socket) {\n this.trackedState = null;\n this.state = 'closed';\n return;\n }\n\n // Explicit presence untrack so the server broadcasts `presence_leave`\n // immediately (disconnect cleanup would eventually fire it too, but\n // on an explicit unsubscribe we want the delta promptly).\n if (this.trackedState) {\n socket.emit('realtime:presence:untrack', { channel: this.topic });\n this.trackedState = null;\n }\n\n // Remove postgres_changes subs individually — each has its own uuid.\n const pcBindings = this.bindings.filter(\n (b): b is PostgresChangesBinding => b.type === 'postgres_changes'\n );\n for (const b of pcBindings) {\n if (b.subscriptionId) {\n socket.emit('realtime:postgres_changes:unsubscribe', {\n subscription_id: b.subscriptionId,\n });\n b.subscriptionId = undefined;\n }\n }\n\n // Broadcast + presence share a single room-per-topic subscribe.\n if (this.bindings.some((b) => b.type === 'broadcast' || b.type === 'presence')) {\n socket.emit('realtime:unsubscribe', { channel: this.topic });\n }\n\n this.realtime._detachChannel(this);\n this.state = 'closed';\n this.statusCallback?.('CLOSED');\n }\n\n /**\n * Publish a broadcast event to the topic. Broadcasts are always\n * ephemeral — the server fans out to every subscribed socket in memory,\n * with no DB persistence or webhook fan-out. For audited / durable\n * events, write to your own application table and enable\n * `postgres_changes` on it; the SDK will surface the INSERT as a\n * `postgres_changes` event without a separate channel.send call.\n *\n * The returned promise always resolves with the server ack, so callers\n * can `await channel.send(...)` to confirm delivery + get the server-\n * assigned `message_id`. There's no performance cost — Socket.IO piggy-\n * backs the ack on the same frame. Callers that don't need it just\n * don't await.\n */\n async send<T extends Record<string, unknown>>(args: {\n type: 'broadcast';\n event: string;\n payload: T;\n }): Promise<\n | { status: 'ok'; message_id: string }\n | { status: 'error'; error: { code: string; message: string } }\n > {\n if (args.type !== 'broadcast') {\n throw new MitwayBaasError(\n 'Only \"broadcast\" sends are supported — DB changes flow via your DB writes, not channel.send()',\n 400,\n 'UNSUPPORTED_SEND_TYPE'\n );\n }\n // Reserved event names — these would route as something other than a\n // broadcast on the client side, breaking dispatch semantics.\n const RESERVED_BROADCAST_NAMES = new Set([\n 'postgres_changes',\n 'presence_state',\n 'presence_join',\n 'presence_leave',\n ]);\n if (RESERVED_BROADCAST_NAMES.has(args.event)) {\n throw new MitwayBaasError(\n `\"${args.event}\" is a reserved event name — pick a different name for broadcast events`,\n 400,\n 'RESERVED_EVENT_NAME'\n );\n }\n const socket = this.realtime._getSocket();\n if (!socket) {\n throw new MitwayBaasError('Socket not connected', 503, 'NOT_CONNECTED');\n }\n\n const self = this.options.config?.broadcast?.self;\n\n const wirePayload: {\n channel: string;\n event: string;\n payload: T;\n self?: boolean;\n } = {\n channel: this.topic,\n event: args.event,\n payload: args.payload,\n };\n // Only include `self` on the wire when the caller set it explicitly —\n // keeps backward compat on the server (the default is true).\n if (self === false) {\n wirePayload.self = false;\n }\n\n return await new Promise((resolve) => {\n socket.emit(\n 'realtime:publish',\n wirePayload,\n (\n ack:\n | { status: 'ok'; message_id: string }\n | { status: 'error'; error: { code: string; message: string } }\n ) => {\n resolve(ack);\n }\n );\n });\n }\n\n /**\n * Replay SQL-originated broadcasts on this topic since the given\n * timestamp. Delivered only to this socket (same envelope format as\n * live broadcasts; the SDK routes them through `.on('broadcast', ...)`\n * bindings just like the real-time path). Backend caps the window at\n * 24 h and the limit at 1000.\n */\n async replay(args: { since: string; limit?: number }): Promise<void> {\n const socket = this.realtime._getSocket();\n if (!socket) {\n throw new MitwayBaasError('Socket not connected', 503, 'NOT_CONNECTED');\n }\n socket.emit('realtime:broadcast:replay', {\n channel: this.topic,\n since: args.since,\n limit: args.limit,\n private: this.isPrivate,\n });\n }\n\n /** Internal — called by Realtime's event router on every incoming event. */\n _dispatch(event: string, envelope: Record<string, unknown>): void {\n if (event === 'postgres_changes') {\n const pcEvent = envelope as unknown as PostgresChangesSocketEvent;\n for (const b of this.bindings) {\n if (b.type !== 'postgres_changes') {\n continue;\n }\n if (!b.subscriptionId || !pcEvent.ids.includes(b.subscriptionId)) {\n continue;\n }\n const matchesEvent =\n b.filter.event === '*' || b.filter.event === pcEvent.data.eventType;\n if (!matchesEvent) {\n continue;\n }\n try {\n b.callback(pcEvent.data);\n } catch {\n /* user callback error — swallow to keep the socket healthy */\n }\n }\n return;\n }\n\n // Presence: three wire events, each an envelope keyed by topic +\n // state/joins/leaves. We scope the channel match via `envelope.channel`\n // to avoid cross-topic bleed when one Realtime holds multiple channels\n // listening on the same socket.\n if (\n event === 'presence_state' ||\n event === 'presence_join' ||\n event === 'presence_leave'\n ) {\n const e = envelope as {\n channel?: string;\n state?: Record<string, Record<string, unknown>>;\n joins?: Record<string, Record<string, unknown>>;\n leaves?: Record<string, Record<string, unknown>>;\n };\n if (e.channel !== this.topic) {\n return;\n }\n if (event === 'presence_state' && e.state) {\n // Replace the local state entirely — this is the hydration point.\n this.presence = { ...e.state };\n this.firePresence({ event: 'sync', state: this.presence });\n } else if (event === 'presence_join' && e.joins) {\n Object.assign(this.presence, e.joins);\n this.firePresence({ event: 'join', joins: e.joins });\n } else if (event === 'presence_leave' && e.leaves) {\n for (const key of Object.keys(e.leaves)) {\n delete this.presence[key];\n }\n this.firePresence({ event: 'leave', leaves: e.leaves });\n }\n return;\n }\n\n // Broadcast: the server emits with the user-defined event name plus\n // our meta envelope. We match against broadcast bindings whose\n // `filter.event` equals the wire event. The envelope carries `meta`\n // plus the developer-defined payload fields (flattened).\n const meta = (envelope as { meta?: RealtimeMessageMeta }).meta;\n // Track the most recent broadcast timestamp so we can replay from\n // here after a reconnect.\n if (meta?.timestamp) {\n if (\n !this.lastBroadcastTimestamp ||\n meta.timestamp > this.lastBroadcastTimestamp\n ) {\n this.lastBroadcastTimestamp = meta.timestamp;\n }\n }\n for (const b of this.bindings) {\n if (b.type !== 'broadcast') {\n continue;\n }\n if (b.filter.event !== event) {\n continue;\n }\n // Strip meta, pass the rest as payload.\n const { meta: _meta, ...payload } = envelope as {\n meta?: RealtimeMessageMeta;\n } & Record<string, unknown>;\n try {\n b.callback({ type: 'broadcast', event, payload });\n } catch {\n /* user callback error */\n }\n }\n }\n\n private firePresence(payload: PresencePayload): void {\n for (const b of this.bindings) {\n if (b.type !== 'presence') {\n continue;\n }\n if (b.filter.event !== payload.event) {\n continue;\n }\n try {\n // The overload split guarantees each binding's callback matches\n // the payload variant for its filter.event, but the implementation\n // signature stores them as a union — cast the call site so the\n // exact callback shape is honoured.\n if (payload.event === 'sync') {\n (b.callback as PresenceSyncCallback)();\n } else if (payload.event === 'join') {\n (b.callback as PresenceJoinCallback)(payload);\n } else {\n (b.callback as PresenceLeaveCallback)(payload);\n }\n } catch {\n /* user callback error */\n }\n }\n }\n\n private async registerAllBindings(): Promise<void> {\n const socket = this.realtime._getSocket();\n if (!socket) {\n throw new Error('Socket not available');\n }\n\n // Broadcast AND presence bindings both need the socket joined to\n // the `realtime:<topic>` room — broadcast events + presence deltas\n // are routed there. One subscribe per channel regardless of binding\n // count.\n const needsRoomJoin = this.bindings.some(\n (b) => b.type === 'broadcast' || b.type === 'presence'\n );\n if (needsRoomJoin) {\n await new Promise<void>((resolve, reject) => {\n socket.emit(\n 'realtime:subscribe',\n { channel: this.topic, private: this.isPrivate },\n (ack: {\n status: 'ok' | 'error';\n channel: string;\n error?: { code: string; message: string };\n }) => {\n if (ack.status === 'ok') {\n resolve();\n } else {\n reject(new Error(ack.error?.message ?? 'subscribe failed'));\n }\n }\n );\n });\n }\n\n // postgres_changes bindings: one round-trip each. Run in parallel so\n // `channel.subscribe()` latency is bounded by the slowest one.\n const pcBindings = this.bindings.filter(\n (b): b is PostgresChangesBinding => b.type === 'postgres_changes'\n );\n await Promise.all(\n pcBindings.map(\n (b) =>\n new Promise<void>((resolve, reject) => {\n socket.emit(\n 'realtime:postgres_changes:subscribe',\n {\n event: b.filter.event,\n schema: b.filter.schema ?? 'public',\n table: b.filter.table,\n filter: b.filter.filter,\n },\n (ack: {\n status: 'ok' | 'error';\n subscription_id?: string;\n error?: { code: string; message: string };\n }) => {\n if (ack.status === 'ok' && ack.subscription_id) {\n b.subscriptionId = ack.subscription_id;\n resolve();\n } else {\n reject(\n new Error(ack.error?.message ?? 'postgres_changes subscribe failed')\n );\n }\n }\n );\n })\n )\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Realtime — socket owner + channel registry\n// ---------------------------------------------------------------------------\n\nexport class Realtime {\n private socket: Socket | null = null;\n private readonly baseUrl: string;\n private readonly options: RealtimeOptions;\n private readonly anonKey: string | undefined;\n private readonly tokenManager: TokenManager;\n\n private channels = new Map<string, RealtimeChannel>();\n private connecting: Promise<void> | null = null;\n /** Flips to `true` once the initial handshake resolves. Differentiates\n * the first `connect` event (part of `openSocket`) from subsequent\n * reconnect events (which should trigger auto-resubscribe). */\n private firstConnected = false;\n\n constructor(\n baseUrl: string,\n tokenManager: TokenManager,\n anonKey: string | undefined,\n options: RealtimeOptions = {}\n ) {\n this.baseUrl = baseUrl;\n this.tokenManager = tokenManager;\n this.anonKey = anonKey;\n this.options = options;\n }\n\n get isConnected(): boolean {\n return this.socket?.connected === true;\n }\n\n get socketId(): string | undefined {\n return this.socket?.id;\n }\n\n /**\n * Get (or create) a channel for `topic`. Channels are cached so multiple\n * `.channel('same')` calls return the same instance.\n *\n * The optional `opts` argument lets the caller configure the channel:\n * * `config.private` — enable subscribe-side authorization against\n * `realtime.authorize_subscribe(...)` on the tenant DB.\n * * `config.broadcast.self` — `false` excludes the sender from the\n * fan-out (defaults to `true`).\n * * `config.presence.key` — stable presence key to group multiple\n * tabs of the same user under one entry.\n *\n * `channel.send()` always resolves with the server ack (see its own\n * docstring); there is no separate opt-in needed.\n *\n * Options are locked in when the channel is first created; subsequent\n * `.channel('same')` calls with different opts are ignored. Pass a\n * different topic to get a different-configured channel.\n */\n channel(topic: string, opts?: ChannelOptions): RealtimeChannel {\n const existing = this.channels.get(topic);\n if (existing) {\n return existing;\n }\n const channel = new RealtimeChannel(topic, this, opts);\n this.channels.set(topic, channel);\n return channel;\n }\n\n connect(): Promise<void> {\n if (this.isConnected) {\n return Promise.resolve();\n }\n if (this.connecting) {\n return this.connecting;\n }\n this.connecting = this.openSocket();\n return this.connecting;\n }\n\n /**\n * Close the socket. Channels are left as-is so they can re-subscribe\n * on the next `connect()` — useful for auth token refresh flows.\n */\n disconnect(): void {\n if (!this.socket) {\n return;\n }\n this.socket.disconnect();\n this.socket = null;\n this.firstConnected = false;\n }\n\n // -------------------------------------------------------------------------\n // internals used by RealtimeChannel\n // -------------------------------------------------------------------------\n\n /* istanbul ignore next — tested via channel integration */\n _getSocket(): Socket | null {\n return this.socket;\n }\n\n _detachChannel(channel: RealtimeChannel): void {\n this.channels.delete(channel.topic);\n }\n\n private openSocket(): Promise<void> {\n const token = this.tokenManager.getAccessToken() ?? this.anonKey;\n if (!token) {\n const err = new MitwayBaasError(\n 'Realtime requires an access token or anonKey',\n 401,\n 'AUTH_INVALID_API_KEY'\n );\n this.connecting = null;\n return Promise.reject(err);\n }\n\n const timeoutMs = this.options.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n const socket = io(this.baseUrl, {\n path: this.options.path,\n transports: this.options.transports ?? ['websocket'],\n auth: { token, ...(this.options.extraAuth ?? {}) },\n reconnection: true,\n timeout: timeoutMs,\n });\n this.socket = socket;\n\n socket.onAny((event: string, ...args: unknown[]) => this.dispatch(event, args));\n\n // Auto-resubscribe on reconnect: Socket.IO gives each reconnect a new\n // session on the server side, which means our subscription rows were\n // cleaned up by the backend's disconnect handler. Re-register every\n // joined channel so events resume without the developer re-calling\n // `.subscribe()`. The first `connect` (during handshake) is NOT a\n // reconnect — we set this.firstConnected after resolve() below.\n socket.on('connect', () => {\n if (!this.firstConnected) {\n return;\n }\n for (const channel of this.channels.values()) {\n const state = channel._state();\n if (state === 'joined' || state === 'errored') {\n void channel._rejoinAfterReconnect();\n }\n }\n });\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n socket.off('connect', onConnect);\n socket.off('connect_error', onConnectError);\n this.connecting = null;\n reject(\n new MitwayBaasError(\n `Realtime connection timeout after ${timeoutMs}ms`,\n 408,\n 'CONNECTION_TIMEOUT'\n )\n );\n }, timeoutMs);\n const clear = () => {\n clearTimeout(timer);\n socket.off('connect', onConnect);\n socket.off('connect_error', onConnectError);\n };\n const onConnect = () => {\n clear();\n this.connecting = null;\n this.firstConnected = true;\n resolve();\n };\n const onConnectError = (err: Error) => {\n clear();\n this.connecting = null;\n reject(new MitwayBaasError(err.message, 0, 'CONNECTION_FAILED'));\n };\n socket.once('connect', onConnect);\n socket.once('connect_error', onConnectError);\n });\n }\n\n private dispatch(event: string, args: unknown[]): void {\n // Broadcast events are emitted with the user-defined name; dispatch\n // through every channel so the right binding picks them up.\n if (event === 'postgres_changes') {\n const envelope = (args[0] ?? {}) as Record<string, unknown>;\n // Each channel checks its bindings' subscription_ids against\n // envelope.ids and only fires matching callbacks. One emit, many\n // bindings.\n this.channels.forEach((ch) => ch._dispatch('postgres_changes', envelope));\n return;\n }\n\n // socket.io lifecycle events — nothing to do here, channels don't\n // react to them directly (the status callback reports channel-level\n // lifecycle instead).\n if (\n event === 'connect' ||\n event === 'disconnect' ||\n event === 'connect_error' ||\n event === 'error' ||\n event === 'realtime:error' ||\n event === 'realtime:shutdown'\n ) {\n return;\n }\n\n // Everything else is a broadcast event keyed on the user-defined\n // event name. Route via the socket's room membership: the server\n // only delivers to sockets in the broadcast room, so any channel\n // whose broadcast binding matches the event name on this socket\n // should fire.\n const envelope = (args[0] ?? {}) as Record<string, unknown>;\n this.channels.forEach((ch) => ch._dispatch(event, envelope));\n }\n}\n","/**\n * Storage module — thin wrapper over the /api/storage/* REST endpoints\n * exposed by MITWAY-BaaS.\n *\n * Surface mirrors what coding agents expect from a Supabase-style storage\n * SDK but routes through the Mitway backend (no direct S3 calls). Binary\n * upload/download uses `HttpClient.rawFetch`; JSON operations use the\n * standard typed `request<T>` path.\n */\n\nimport { HttpClient } from '../lib/http-client';\nimport { MitwayBaasError } from '../types';\nimport type {\n CreateBucketOptions,\n DownloadOptions,\n ListOptions,\n SignedUrlOptions,\n SignedUrlResult,\n StorageBucket,\n StorageConfig,\n StorageObject,\n UpdateBucketOptions,\n UploadBody,\n UploadOptions,\n} from '../lib/storage-types';\n\nexport type StorageResult<T> = {\n data: T | null;\n error: MitwayBaasError | null;\n};\n\n// Wire-format rows returned by the backend (snake_case).\ninterface BucketWire {\n id: string;\n name: string;\n public: boolean;\n file_size_limit_bytes: number | null;\n allowed_mime_types: string[] | null;\n created_at: string;\n updated_at: string;\n}\n\ninterface ObjectWire {\n id: string;\n bucket_id: string;\n key: string;\n size: number;\n mime_type: string | null;\n etag: string;\n cache_control: string | null;\n content_disposition: string | null;\n uploaded_by: string | null;\n uploaded_at: string;\n updated_at: string;\n}\n\ninterface ConfigWire {\n default_file_size_limit_bytes: number;\n max_file_size_limit_bytes: number;\n tenant_storage_quota_bytes: number;\n reserved_space_bytes: number;\n signed_url_default_ttl_sec: number;\n signed_url_max_ttl_sec: number;\n}\n\nfunction bucketFromWire(row: BucketWire): StorageBucket {\n return {\n id: row.id,\n name: row.name,\n public: row.public,\n fileSizeLimitBytes: row.file_size_limit_bytes,\n allowedMimeTypes: row.allowed_mime_types,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nfunction objectFromWire(row: ObjectWire, bucketName: string): StorageObject {\n return {\n id: row.id,\n bucket: bucketName,\n key: row.key,\n size: row.size,\n mimeType: row.mime_type,\n etag: row.etag,\n cacheControl: row.cache_control,\n contentDisposition: row.content_disposition,\n uploadedBy: row.uploaded_by,\n uploadedAt: row.uploaded_at,\n updatedAt: row.updated_at,\n };\n}\n\nfunction configFromWire(row: ConfigWire): StorageConfig {\n return {\n defaultFileSizeLimitBytes: row.default_file_size_limit_bytes,\n maxFileSizeLimitBytes: row.max_file_size_limit_bytes,\n tenantStorageQuotaBytes: row.tenant_storage_quota_bytes,\n reservedSpaceBytes: row.reserved_space_bytes,\n signedUrlDefaultTtlSec: row.signed_url_default_ttl_sec,\n signedUrlMaxTtlSec: row.signed_url_max_ttl_sec,\n };\n}\n\nfunction encodeKey(key: string): string {\n // Preserve `/` as the folder separator; encode each segment individually.\n return key.split('/').map(encodeURIComponent).join('/');\n}\n\nfunction wrapError<T>(err: unknown, fallback: string): StorageResult<T> {\n if (err instanceof MitwayBaasError) return { data: null, error: err };\n return {\n data: null,\n error: new MitwayBaasError(\n err instanceof Error ? err.message : fallback,\n 0,\n 'STORAGE_ERROR',\n ),\n };\n}\n\nasync function readEnvelopeError(response: Response): Promise<MitwayBaasError> {\n let code = 'STORAGE_ERROR';\n let message = `HTTP ${response.status}`;\n try {\n const body = (await response.json()) as {\n error?: { code?: string; message?: string } | null;\n };\n if (body && body.error) {\n code = body.error.code ?? code;\n message = body.error.message ?? message;\n }\n } catch {\n /* no-op: body wasn't JSON */\n }\n return new MitwayBaasError(message, response.status, code);\n}\n\n// -----------------------------------------------------------------------------\n// Per-bucket client\n// -----------------------------------------------------------------------------\n\nexport class StorageBucketClient {\n constructor(\n private readonly http: HttpClient,\n private readonly bucketName: string,\n ) {}\n\n private bucketBase(): string {\n return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`;\n }\n\n private objectPath(key: string): string {\n return `${this.bucketBase()}/objects/${encodeKey(key)}`;\n }\n\n async upload(\n key: string,\n body: UploadBody,\n opts: UploadOptions = {},\n ): Promise<StorageResult<StorageObject>> {\n try {\n const method = opts.upsert ? 'PUT' : 'POST';\n const headers: Record<string, string> = {\n 'Content-Type': opts.contentType ?? 'application/octet-stream',\n };\n if (opts.cacheControl) headers['Cache-Control'] = opts.cacheControl;\n if (opts.contentDisposition)\n headers['Content-Disposition'] = opts.contentDisposition;\n\n const response = await this.http.rawFetch(this.objectPath(key), {\n method,\n headers,\n body: body as BodyInit,\n signal: opts.abortSignal,\n });\n if (!response.ok) {\n return { data: null, error: await readEnvelopeError(response) };\n }\n const parsed = (await response.json()) as {\n data: ObjectWire | null;\n error: { code: string; message: string } | null;\n };\n if (parsed.error || !parsed.data) {\n return {\n data: null,\n error: new MitwayBaasError(\n parsed.error?.message ?? 'Upload failed',\n response.status,\n parsed.error?.code ?? 'STORAGE_ERROR',\n ),\n };\n }\n return { data: objectFromWire(parsed.data, this.bucketName), error: null };\n } catch (err) {\n return wrapError<StorageObject>(err, 'Upload failed');\n }\n }\n\n async download(\n key: string,\n opts: DownloadOptions = {},\n ): Promise<StorageResult<Blob>> {\n try {\n const headers: Record<string, string> = {};\n if (opts.range) {\n headers['Range'] = `bytes=${opts.range.start}-${opts.range.end}`;\n }\n const response = await this.http.rawFetch(this.objectPath(key), {\n method: 'GET',\n headers,\n signal: opts.abortSignal,\n });\n if (!response.ok) {\n return { data: null, error: await readEnvelopeError(response) };\n }\n const blob = await response.blob();\n return { data: blob, error: null };\n } catch (err) {\n return wrapError<Blob>(err, 'Download failed');\n }\n }\n\n async getStream(\n key: string,\n opts: DownloadOptions = {},\n ): Promise<StorageResult<ReadableStream<Uint8Array>>> {\n try {\n const headers: Record<string, string> = {};\n if (opts.range) {\n headers['Range'] = `bytes=${opts.range.start}-${opts.range.end}`;\n }\n const response = await this.http.rawFetch(this.objectPath(key), {\n method: 'GET',\n headers,\n signal: opts.abortSignal,\n });\n if (!response.ok) {\n return { data: null, error: await readEnvelopeError(response) };\n }\n if (!response.body) {\n return {\n data: null,\n error: new MitwayBaasError(\n 'Response body is not a stream',\n response.status,\n 'STORAGE_ERROR',\n ),\n };\n }\n return {\n data: response.body as ReadableStream<Uint8Array>,\n error: null,\n };\n } catch (err) {\n return wrapError<ReadableStream<Uint8Array>>(err, 'Download failed');\n }\n }\n\n async remove(keys: string[]): Promise<StorageResult<{ removed: string[] }>> {\n try {\n // Fire deletes in parallel; collect per-key results independently so\n // one failure doesn't mask the rest. Order of `removed` follows the\n // input array.\n const results = await Promise.allSettled(\n keys.map((key) =>\n this.http.rawFetch(this.objectPath(key), { method: 'DELETE' }),\n ),\n );\n const removed: string[] = [];\n const errors: string[] = [];\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const r = results[i];\n if (r.status === 'fulfilled' && r.value.ok) {\n removed.push(key);\n } else if (r.status === 'fulfilled') {\n errors.push(`${key}: HTTP ${r.value.status}`);\n } else {\n const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);\n errors.push(`${key}: ${msg}`);\n }\n }\n if (errors.length > 0) {\n return {\n data: null,\n error: new MitwayBaasError(\n `Failed to delete some objects: ${errors.join('; ')}`,\n 0,\n 'STORAGE_ERROR',\n ),\n };\n }\n return { data: { removed }, error: null };\n } catch (err) {\n return wrapError<{ removed: string[] }>(err, 'Delete failed');\n }\n }\n\n async list(\n opts: ListOptions = {},\n ): Promise<StorageResult<StorageObject[]>> {\n try {\n const params: Record<string, string> = {};\n if (opts.prefix !== undefined) params.prefix = opts.prefix;\n if (opts.limit !== undefined) params.limit = String(opts.limit);\n if (opts.startAfter !== undefined) params.start_after = opts.startAfter;\n const rows = await this.http.get<ObjectWire[]>(\n `${this.bucketBase()}/objects`,\n { params },\n );\n return {\n data: rows.map((r) => objectFromWire(r, this.bucketName)),\n error: null,\n };\n } catch (err) {\n return wrapError<StorageObject[]>(err, 'List failed');\n }\n }\n\n async copy(\n fromKey: string,\n toKey: string,\n toBucket?: string,\n ): Promise<StorageResult<StorageObject>> {\n try {\n const row = await this.http.post<ObjectWire>(\n `${this.objectPath(fromKey)}/copy`,\n {\n dest_bucket: toBucket ?? this.bucketName,\n dest_key: toKey,\n },\n );\n return {\n data: objectFromWire(row, toBucket ?? this.bucketName),\n error: null,\n };\n } catch (err) {\n return wrapError<StorageObject>(err, 'Copy failed');\n }\n }\n\n async move(\n fromKey: string,\n toKey: string,\n toBucket?: string,\n ): Promise<StorageResult<StorageObject>> {\n try {\n const row = await this.http.post<ObjectWire>(\n `${this.objectPath(fromKey)}/move`,\n {\n dest_bucket: toBucket ?? this.bucketName,\n dest_key: toKey,\n },\n );\n return {\n data: objectFromWire(row, toBucket ?? this.bucketName),\n error: null,\n };\n } catch (err) {\n return wrapError<StorageObject>(err, 'Move failed');\n }\n }\n\n async createSignedUrl(\n key: string,\n opts: SignedUrlOptions = {},\n ): Promise<StorageResult<SignedUrlResult>> {\n try {\n const body: Record<string, number> = {};\n if (opts.expiresIn !== undefined) body.expires_in = opts.expiresIn;\n const wire = await this.http.post<{\n url: string;\n token: string;\n expiresAt: string;\n }>(`${this.objectPath(key)}/sign`, body);\n return {\n data: {\n url: wire.url,\n token: wire.token,\n expiresAt: wire.expiresAt,\n },\n error: null,\n };\n } catch (err) {\n return wrapError<SignedUrlResult>(err, 'Sign failed');\n }\n }\n\n getPublicUrl(key: string): { data: { url: string } } {\n const url = `${this.http.baseUrl.replace(/\\/$/, '')}${this.objectPath(key)}`;\n return { data: { url } };\n }\n}\n\n// -----------------------------------------------------------------------------\n// Top-level storage (admin + bucket factory)\n// -----------------------------------------------------------------------------\n\nexport class Storage {\n constructor(private readonly http: HttpClient) {}\n\n /** Scope subsequent operations to a single bucket. */\n from(bucketName: string): StorageBucketClient {\n return new StorageBucketClient(this.http, bucketName);\n }\n\n // --- Admin (require service_role) ---\n\n async listBuckets(): Promise<StorageResult<StorageBucket[]>> {\n try {\n const rows = await this.http.get<BucketWire[]>('/api/storage/buckets');\n return { data: rows.map(bucketFromWire), error: null };\n } catch (err) {\n return wrapError<StorageBucket[]>(err, 'listBuckets failed');\n }\n }\n\n async getBucket(name: string): Promise<StorageResult<StorageBucket>> {\n try {\n const row = await this.http.get<BucketWire>(\n `/api/storage/buckets/${encodeURIComponent(name)}`,\n );\n return { data: bucketFromWire(row), error: null };\n } catch (err) {\n return wrapError<StorageBucket>(err, 'getBucket failed');\n }\n }\n\n async createBucket(\n name: string,\n opts: CreateBucketOptions = {},\n ): Promise<StorageResult<StorageBucket>> {\n try {\n const body: Record<string, unknown> = { name };\n if (opts.public !== undefined) body.public = opts.public;\n if (opts.fileSizeLimitBytes !== undefined)\n body.file_size_limit_bytes = opts.fileSizeLimitBytes;\n if (opts.allowedMimeTypes !== undefined)\n body.allowed_mime_types = opts.allowedMimeTypes;\n const row = await this.http.post<BucketWire>(\n '/api/storage/buckets',\n body,\n );\n return { data: bucketFromWire(row), error: null };\n } catch (err) {\n return wrapError<StorageBucket>(err, 'createBucket failed');\n }\n }\n\n async updateBucket(\n name: string,\n opts: UpdateBucketOptions,\n ): Promise<StorageResult<StorageBucket>> {\n try {\n const body: Record<string, unknown> = {};\n if (opts.public !== undefined) body.public = opts.public;\n if (opts.fileSizeLimitBytes !== undefined)\n body.file_size_limit_bytes = opts.fileSizeLimitBytes;\n if (opts.allowedMimeTypes !== undefined)\n body.allowed_mime_types = opts.allowedMimeTypes;\n const row = await this.http.patch<BucketWire>(\n `/api/storage/buckets/${encodeURIComponent(name)}`,\n body,\n );\n return { data: bucketFromWire(row), error: null };\n } catch (err) {\n return wrapError<StorageBucket>(err, 'updateBucket failed');\n }\n }\n\n async deleteBucket(name: string): Promise<StorageResult<null>> {\n try {\n await this.http.delete<null>(\n `/api/storage/buckets/${encodeURIComponent(name)}`,\n );\n return { data: null, error: null };\n } catch (err) {\n return wrapError<null>(err, 'deleteBucket failed');\n }\n }\n\n async emptyBucket(\n name: string,\n ): Promise<StorageResult<{ removed: number }>> {\n try {\n const result = await this.http.post<{ removed: number }>(\n `/api/storage/buckets/${encodeURIComponent(name)}/empty`,\n {},\n );\n return { data: result, error: null };\n } catch (err) {\n return wrapError<{ removed: number }>(err, 'emptyBucket failed');\n }\n }\n\n // --- Storage config (service_role) ---\n\n async getConfig(): Promise<StorageResult<StorageConfig>> {\n try {\n const row = await this.http.get<ConfigWire>('/api/storage/config');\n return { data: configFromWire(row), error: null };\n } catch (err) {\n return wrapError<StorageConfig>(err, 'getConfig failed');\n }\n }\n}\n","import { MitwayBaasConfig } from './types';\nimport { HttpClient } from './lib/http-client';\nimport { Logger } from './lib/logger';\nimport { TokenManager } from './lib/token-manager';\nimport { Auth } from './modules/auth';\nimport { Database } from './modules/database';\nimport { Realtime, type RealtimeOptions } from './modules/realtime';\nimport { Storage } from './modules/storage';\n\n/**\n * MITWAY-BaaS SDK client.\n *\n * @example\n * ```typescript\n * import { createClient } from '@mitway/sdk';\n *\n * const client = createClient({\n * baseUrl: 'https://acme.api.dev.nttmitway.com',\n * anonKey: 'eyJhbGciOiJIUzI1NiIs...', // optional\n * });\n *\n * // Auth\n * const { data: session, error } = await client.auth.signInWithPassword({\n * email: 'a@b.com',\n * password: 'pw',\n * });\n *\n * // Database — transparently routed through the backend proxy at\n * // /api/database/records/* (no separate PostgREST URL).\n * const { data, error: dbError } = await client.database\n * .from('posts')\n * .select('*')\n * .eq('user_id', session.user.id)\n * .order('created_at', { ascending: false });\n * ```\n */\nexport class MitwayBaasClient {\n private http: HttpClient;\n private tokenManager: TokenManager;\n public readonly auth: Auth;\n public readonly database: Database;\n public readonly realtime: Realtime;\n public readonly storage: Storage;\n\n constructor(config: MitwayBaasConfig = {}) {\n const logger = new Logger(config.debug);\n this.tokenManager = new TokenManager({\n persistSession: config.persistSession,\n storageKey: config.storageKey,\n });\n this.http = new HttpClient(config, this.tokenManager, logger);\n this.auth = new Auth(this.http, this.tokenManager);\n this.database = new Database(this.http, this.tokenManager, config.anonKey);\n this.realtime = new Realtime(\n this.http.baseUrl,\n this.tokenManager,\n config.anonKey,\n config.realtime,\n );\n this.storage = new Storage(this.http);\n }\n\n /**\n * Escape hatch for callers that need to make custom requests against the\n * backend without going through `auth` or `database`.\n */\n getHttpClient(): HttpClient {\n return this.http;\n }\n}\n"],"mappings":"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","names":["timer","envelope","wrapError"]}
|