@ragable/sdk 0.6.14 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -947,26 +947,86 @@ declare class RagableBrowserDatabaseClient<Database extends RagableDatabase = De
947
947
  * Postgres `LISTEN` / `NOTIFY` realtime via server-proxied SSE.
948
948
  * Channels must be lowercase identifiers: `[a-z_][a-z0-9_]*` (max 63 chars).
949
949
  */
950
+ /**
951
+ * Postgres `LISTEN` / `NOTIFY` realtime via server-proxied SSE.
952
+ *
953
+ * Returns a `BrowserRealtimeSubscription` with:
954
+ * - `unsubscribe()` — permanently close the subscription (stops reconnects).
955
+ * - `status` — current connection state: `"connecting"` | `"connected"` | `"reconnecting"` | `"disconnected"`.
956
+ *
957
+ * The subscription automatically reconnects with exponential backoff when the
958
+ * stream drops. Server heartbeats (every 15 s) are monitored — if none arrive
959
+ * within `heartbeatTimeoutMs` (default 45 s), the connection is treated as dead
960
+ * and a reconnect is triggered. Auth errors (401/403/404) are non-retryable.
961
+ *
962
+ * Channel names must be lowercase identifiers: `[a-z_][a-z0-9_]*` (max 63 chars).
963
+ */
950
964
  realtime: {
951
- subscribe: (params: BrowserRealtimeSubscribeParams) => Promise<{
952
- unsubscribe: () => void;
953
- }>;
965
+ subscribe: (params: BrowserRealtimeSubscribeParams) => Promise<BrowserRealtimeSubscription>;
954
966
  };
955
967
  }
968
+ /**
969
+ * A single NOTIFY message received from a Postgres channel.
970
+ *
971
+ * - `channel` — the lowercase channel name that fired.
972
+ * - `payload` — the text payload (may be null if NOTIFY was sent without one).
973
+ * - `processId` — the PID of the Postgres backend that called NOTIFY.
974
+ */
956
975
  interface BrowserRealtimeNotification {
957
976
  channel: string;
958
977
  payload: string | null;
959
978
  processId: number;
960
979
  }
980
+ /**
981
+ * Connection status of a realtime subscription.
982
+ *
983
+ * - `"connecting"` — establishing or re-establishing the SSE stream.
984
+ * - `"connected"` — stream is open, heartbeats are arriving, NOTIFY events are flowing.
985
+ * - `"reconnecting"` — the stream dropped and the SDK is waiting before the next retry.
986
+ * - `"disconnected"` — permanently stopped (via `unsubscribe()`, `signal` abort, or max retries exceeded).
987
+ */
988
+ type BrowserRealtimeStatus = "connecting" | "connected" | "reconnecting" | "disconnected";
961
989
  interface BrowserRealtimeSubscribeParams {
962
990
  databaseInstanceId?: string;
963
991
  /** Channel names (normalized to lowercase on the server). */
964
992
  channels: string[];
965
- /** When aborted, the subscription stops (in addition to `unsubscribe()`). */
993
+ /** When aborted, the subscription stops permanently (equivalent to calling `unsubscribe()`). */
966
994
  signal?: AbortSignal;
995
+ /** Called each time a Postgres NOTIFY message arrives. */
967
996
  onNotify?: (msg: BrowserRealtimeNotification) => void;
997
+ /** Called once per connection attempt when the server confirms LISTEN is active. */
968
998
  onReady?: (channels: string[]) => void;
999
+ /** Called when the stream encounters a non-retryable error or the server sends an error event. */
969
1000
  onError?: (message: string) => void;
1001
+ /** Called whenever the connection status changes (connecting → connected → reconnecting → …). */
1002
+ onStatusChange?: (status: BrowserRealtimeStatus) => void;
1003
+ /** Called when the stream drops and the SDK will attempt to reconnect. `attempt` is 1-indexed. */
1004
+ onDisconnect?: (info: {
1005
+ attempt: number;
1006
+ retryInMs: number;
1007
+ }) => void;
1008
+ /** Called when a reconnect attempt succeeds (stream is open again). */
1009
+ onReconnect?: (info: {
1010
+ attempt: number;
1011
+ }) => void;
1012
+ /** Maximum number of reconnect attempts before giving up. Set `0` to disable reconnect. Default: **Infinity** (never stops). */
1013
+ maxReconnectAttempts?: number;
1014
+ /** Base delay for exponential backoff (ms). Default: **1000**. */
1015
+ reconnectBaseDelayMs?: number;
1016
+ /** Maximum delay between reconnect attempts (ms). Default: **30000**. */
1017
+ reconnectMaxDelayMs?: number;
1018
+ /** How long to wait without a heartbeat before considering the connection dead (ms). Default: **45000** (3× the server's 15 s heartbeat interval). */
1019
+ heartbeatTimeoutMs?: number;
1020
+ }
1021
+ /**
1022
+ * Handle returned by `database.realtime.subscribe()`.
1023
+ *
1024
+ * - Call `unsubscribe()` to permanently close the subscription (no more reconnects).
1025
+ * - Read `status` to check the current connection state at any time.
1026
+ */
1027
+ interface BrowserRealtimeSubscription {
1028
+ unsubscribe: () => void;
1029
+ readonly status: BrowserRealtimeStatus;
970
1030
  }
971
1031
  declare class RagableBrowserAgentsClient {
972
1032
  private readonly options;
@@ -1068,4 +1128,4 @@ declare function createClient(options: RagableClientOptions): Ragable;
1068
1128
  declare function createClient<Database extends RagableDatabase = DefaultRagableDatabase, AuthUser extends Record<string, unknown> = Record<string, unknown>>(options: RagableBrowserClientOptions): RagableBrowser<Database, AuthUser>;
1069
1129
  declare function createRagableServerClient(options: RagableClientOptions): Ragable;
1070
1130
 
1071
- export { type AgentChatMessage, type AgentChatParams, type AgentChatResult, type AgentPublicChatParams, type AgentStreamEvent, type AgentSummary, AgentsClient, AuthBroadcastChannel, type AuthBroadcastMessage, type AuthChangeEvent, type AuthOptions, type AuthSession, type BrowserAuthSession, type BrowserAuthTokens, type BrowserDataAuthMode, type BrowserRealtimeNotification, type BrowserRealtimeSubscribeParams, type BrowserSqlExecParams, type BrowserSqlExecResult, type BrowserSqlQueryParams, type BrowserSqlQueryResult, type ColumnName, type ColumnValue, CookieStorageAdapter, DEFAULT_RAGABLE_API_BASE, type DefaultRagableDatabase, type FormatContextOptions, type HttpMethod, type Json, LocalStorageAdapter, MemoryStorageAdapter, type PostgRESTFetch, type PostgRESTFetchParams, PostgrestDeleteReturningBuilder, PostgrestDeleteRootBuilder, PostgrestInsertReturningBuilder, PostgrestInsertRootBuilder, PostgrestInsertSdkErrorReturning, PostgrestInsertSdkErrorRoot, type PostgrestResult, PostgrestSelectBuilder, PostgrestTableApi, PostgrestUpdateReturningBuilder, PostgrestUpdateRootBuilder, type PostgrestUpsertOptions, PostgrestUpsertReturningBuilder, PostgrestUpsertRootBuilder, type RagClientForPipeline, type RagPipeline, type RagPipelineOptions, Ragable, RagableAbortError, RagableAuth, type RagableAuthConfig, RagableBrowser, RagableBrowserAgentsClient, RagableBrowserAuthClient, type RagableBrowserClientOptions, RagableBrowserDatabaseClient, type RagableClientOptions, type RagableDatabase, RagableError, RagableNetworkError, RagableRequestClient, RagableSdkError, type RagableTableDefinition, type RagableTableNames, RagableTimeoutError, type RequestOptions, type RetrieveParams, type RetryOptions, type RunQuery, type SessionStorage, SessionStorageAdapter, type ShiftAddDocumentParams, ShiftClient, type ShiftCreateIndexParams, type ShiftEntry, type ShiftIndex, type ShiftIngestResponse, type ShiftListEntriesParams, type ShiftListEntriesResponse, type ShiftSearchParams, type ShiftSearchResult, type ShiftUpdateIndexParams, type ShiftUploadFileParams, type ShiftUploadableFile, type SseJsonEvent, type SupabaseCompatSession, type TableInsertRow, type TableRow, type TableUpdatePatch, type Tables, type TablesInsert, type TablesUpdate, Transport, type TransportOptions, type TransportRequest, asPostgrestResponse, bindFetch, createBrowserClient, createClient, createRagPipeline, createRagableBrowserClient, createRagableServerClient, detectStorage, effectiveDataAuth, extractErrorMessage, formatPostgrestError, formatRetrievalContext, formatSdkError, generateIdempotencyKey, normalizeBrowserApiBase, parseSseDataLine, parseTransportResponse, readSseStream };
1131
+ export { type AgentChatMessage, type AgentChatParams, type AgentChatResult, type AgentPublicChatParams, type AgentStreamEvent, type AgentSummary, AgentsClient, AuthBroadcastChannel, type AuthBroadcastMessage, type AuthChangeEvent, type AuthOptions, type AuthSession, type BrowserAuthSession, type BrowserAuthTokens, type BrowserDataAuthMode, type BrowserRealtimeNotification, type BrowserRealtimeStatus, type BrowserRealtimeSubscribeParams, type BrowserRealtimeSubscription, type BrowserSqlExecParams, type BrowserSqlExecResult, type BrowserSqlQueryParams, type BrowserSqlQueryResult, type ColumnName, type ColumnValue, CookieStorageAdapter, DEFAULT_RAGABLE_API_BASE, type DefaultRagableDatabase, type FormatContextOptions, type HttpMethod, type Json, LocalStorageAdapter, MemoryStorageAdapter, type PostgRESTFetch, type PostgRESTFetchParams, PostgrestDeleteReturningBuilder, PostgrestDeleteRootBuilder, PostgrestInsertReturningBuilder, PostgrestInsertRootBuilder, PostgrestInsertSdkErrorReturning, PostgrestInsertSdkErrorRoot, type PostgrestResult, PostgrestSelectBuilder, PostgrestTableApi, PostgrestUpdateReturningBuilder, PostgrestUpdateRootBuilder, type PostgrestUpsertOptions, PostgrestUpsertReturningBuilder, PostgrestUpsertRootBuilder, type RagClientForPipeline, type RagPipeline, type RagPipelineOptions, Ragable, RagableAbortError, RagableAuth, type RagableAuthConfig, RagableBrowser, RagableBrowserAgentsClient, RagableBrowserAuthClient, type RagableBrowserClientOptions, RagableBrowserDatabaseClient, type RagableClientOptions, type RagableDatabase, RagableError, RagableNetworkError, RagableRequestClient, RagableSdkError, type RagableTableDefinition, type RagableTableNames, RagableTimeoutError, type RequestOptions, type RetrieveParams, type RetryOptions, type RunQuery, type SessionStorage, SessionStorageAdapter, type ShiftAddDocumentParams, ShiftClient, type ShiftCreateIndexParams, type ShiftEntry, type ShiftIndex, type ShiftIngestResponse, type ShiftListEntriesParams, type ShiftListEntriesResponse, type ShiftSearchParams, type ShiftSearchResult, type ShiftUpdateIndexParams, type ShiftUploadFileParams, type ShiftUploadableFile, type SseJsonEvent, type SupabaseCompatSession, type TableInsertRow, type TableRow, type TableUpdatePatch, type Tables, type TablesInsert, type TablesUpdate, Transport, type TransportOptions, type TransportRequest, asPostgrestResponse, bindFetch, createBrowserClient, createClient, createRagPipeline, createRagableBrowserClient, createRagableServerClient, detectStorage, effectiveDataAuth, extractErrorMessage, formatPostgrestError, formatRetrievalContext, formatSdkError, generateIdempotencyKey, normalizeBrowserApiBase, parseSseDataLine, parseTransportResponse, readSseStream };
package/dist/index.d.ts CHANGED
@@ -947,26 +947,86 @@ declare class RagableBrowserDatabaseClient<Database extends RagableDatabase = De
947
947
  * Postgres `LISTEN` / `NOTIFY` realtime via server-proxied SSE.
948
948
  * Channels must be lowercase identifiers: `[a-z_][a-z0-9_]*` (max 63 chars).
949
949
  */
950
+ /**
951
+ * Postgres `LISTEN` / `NOTIFY` realtime via server-proxied SSE.
952
+ *
953
+ * Returns a `BrowserRealtimeSubscription` with:
954
+ * - `unsubscribe()` — permanently close the subscription (stops reconnects).
955
+ * - `status` — current connection state: `"connecting"` | `"connected"` | `"reconnecting"` | `"disconnected"`.
956
+ *
957
+ * The subscription automatically reconnects with exponential backoff when the
958
+ * stream drops. Server heartbeats (every 15 s) are monitored — if none arrive
959
+ * within `heartbeatTimeoutMs` (default 45 s), the connection is treated as dead
960
+ * and a reconnect is triggered. Auth errors (401/403/404) are non-retryable.
961
+ *
962
+ * Channel names must be lowercase identifiers: `[a-z_][a-z0-9_]*` (max 63 chars).
963
+ */
950
964
  realtime: {
951
- subscribe: (params: BrowserRealtimeSubscribeParams) => Promise<{
952
- unsubscribe: () => void;
953
- }>;
965
+ subscribe: (params: BrowserRealtimeSubscribeParams) => Promise<BrowserRealtimeSubscription>;
954
966
  };
955
967
  }
968
+ /**
969
+ * A single NOTIFY message received from a Postgres channel.
970
+ *
971
+ * - `channel` — the lowercase channel name that fired.
972
+ * - `payload` — the text payload (may be null if NOTIFY was sent without one).
973
+ * - `processId` — the PID of the Postgres backend that called NOTIFY.
974
+ */
956
975
  interface BrowserRealtimeNotification {
957
976
  channel: string;
958
977
  payload: string | null;
959
978
  processId: number;
960
979
  }
980
+ /**
981
+ * Connection status of a realtime subscription.
982
+ *
983
+ * - `"connecting"` — establishing or re-establishing the SSE stream.
984
+ * - `"connected"` — stream is open, heartbeats are arriving, NOTIFY events are flowing.
985
+ * - `"reconnecting"` — the stream dropped and the SDK is waiting before the next retry.
986
+ * - `"disconnected"` — permanently stopped (via `unsubscribe()`, `signal` abort, or max retries exceeded).
987
+ */
988
+ type BrowserRealtimeStatus = "connecting" | "connected" | "reconnecting" | "disconnected";
961
989
  interface BrowserRealtimeSubscribeParams {
962
990
  databaseInstanceId?: string;
963
991
  /** Channel names (normalized to lowercase on the server). */
964
992
  channels: string[];
965
- /** When aborted, the subscription stops (in addition to `unsubscribe()`). */
993
+ /** When aborted, the subscription stops permanently (equivalent to calling `unsubscribe()`). */
966
994
  signal?: AbortSignal;
995
+ /** Called each time a Postgres NOTIFY message arrives. */
967
996
  onNotify?: (msg: BrowserRealtimeNotification) => void;
997
+ /** Called once per connection attempt when the server confirms LISTEN is active. */
968
998
  onReady?: (channels: string[]) => void;
999
+ /** Called when the stream encounters a non-retryable error or the server sends an error event. */
969
1000
  onError?: (message: string) => void;
1001
+ /** Called whenever the connection status changes (connecting → connected → reconnecting → …). */
1002
+ onStatusChange?: (status: BrowserRealtimeStatus) => void;
1003
+ /** Called when the stream drops and the SDK will attempt to reconnect. `attempt` is 1-indexed. */
1004
+ onDisconnect?: (info: {
1005
+ attempt: number;
1006
+ retryInMs: number;
1007
+ }) => void;
1008
+ /** Called when a reconnect attempt succeeds (stream is open again). */
1009
+ onReconnect?: (info: {
1010
+ attempt: number;
1011
+ }) => void;
1012
+ /** Maximum number of reconnect attempts before giving up. Set `0` to disable reconnect. Default: **Infinity** (never stops). */
1013
+ maxReconnectAttempts?: number;
1014
+ /** Base delay for exponential backoff (ms). Default: **1000**. */
1015
+ reconnectBaseDelayMs?: number;
1016
+ /** Maximum delay between reconnect attempts (ms). Default: **30000**. */
1017
+ reconnectMaxDelayMs?: number;
1018
+ /** How long to wait without a heartbeat before considering the connection dead (ms). Default: **45000** (3× the server's 15 s heartbeat interval). */
1019
+ heartbeatTimeoutMs?: number;
1020
+ }
1021
+ /**
1022
+ * Handle returned by `database.realtime.subscribe()`.
1023
+ *
1024
+ * - Call `unsubscribe()` to permanently close the subscription (no more reconnects).
1025
+ * - Read `status` to check the current connection state at any time.
1026
+ */
1027
+ interface BrowserRealtimeSubscription {
1028
+ unsubscribe: () => void;
1029
+ readonly status: BrowserRealtimeStatus;
970
1030
  }
971
1031
  declare class RagableBrowserAgentsClient {
972
1032
  private readonly options;
@@ -1068,4 +1128,4 @@ declare function createClient(options: RagableClientOptions): Ragable;
1068
1128
  declare function createClient<Database extends RagableDatabase = DefaultRagableDatabase, AuthUser extends Record<string, unknown> = Record<string, unknown>>(options: RagableBrowserClientOptions): RagableBrowser<Database, AuthUser>;
1069
1129
  declare function createRagableServerClient(options: RagableClientOptions): Ragable;
1070
1130
 
1071
- export { type AgentChatMessage, type AgentChatParams, type AgentChatResult, type AgentPublicChatParams, type AgentStreamEvent, type AgentSummary, AgentsClient, AuthBroadcastChannel, type AuthBroadcastMessage, type AuthChangeEvent, type AuthOptions, type AuthSession, type BrowserAuthSession, type BrowserAuthTokens, type BrowserDataAuthMode, type BrowserRealtimeNotification, type BrowserRealtimeSubscribeParams, type BrowserSqlExecParams, type BrowserSqlExecResult, type BrowserSqlQueryParams, type BrowserSqlQueryResult, type ColumnName, type ColumnValue, CookieStorageAdapter, DEFAULT_RAGABLE_API_BASE, type DefaultRagableDatabase, type FormatContextOptions, type HttpMethod, type Json, LocalStorageAdapter, MemoryStorageAdapter, type PostgRESTFetch, type PostgRESTFetchParams, PostgrestDeleteReturningBuilder, PostgrestDeleteRootBuilder, PostgrestInsertReturningBuilder, PostgrestInsertRootBuilder, PostgrestInsertSdkErrorReturning, PostgrestInsertSdkErrorRoot, type PostgrestResult, PostgrestSelectBuilder, PostgrestTableApi, PostgrestUpdateReturningBuilder, PostgrestUpdateRootBuilder, type PostgrestUpsertOptions, PostgrestUpsertReturningBuilder, PostgrestUpsertRootBuilder, type RagClientForPipeline, type RagPipeline, type RagPipelineOptions, Ragable, RagableAbortError, RagableAuth, type RagableAuthConfig, RagableBrowser, RagableBrowserAgentsClient, RagableBrowserAuthClient, type RagableBrowserClientOptions, RagableBrowserDatabaseClient, type RagableClientOptions, type RagableDatabase, RagableError, RagableNetworkError, RagableRequestClient, RagableSdkError, type RagableTableDefinition, type RagableTableNames, RagableTimeoutError, type RequestOptions, type RetrieveParams, type RetryOptions, type RunQuery, type SessionStorage, SessionStorageAdapter, type ShiftAddDocumentParams, ShiftClient, type ShiftCreateIndexParams, type ShiftEntry, type ShiftIndex, type ShiftIngestResponse, type ShiftListEntriesParams, type ShiftListEntriesResponse, type ShiftSearchParams, type ShiftSearchResult, type ShiftUpdateIndexParams, type ShiftUploadFileParams, type ShiftUploadableFile, type SseJsonEvent, type SupabaseCompatSession, type TableInsertRow, type TableRow, type TableUpdatePatch, type Tables, type TablesInsert, type TablesUpdate, Transport, type TransportOptions, type TransportRequest, asPostgrestResponse, bindFetch, createBrowserClient, createClient, createRagPipeline, createRagableBrowserClient, createRagableServerClient, detectStorage, effectiveDataAuth, extractErrorMessage, formatPostgrestError, formatRetrievalContext, formatSdkError, generateIdempotencyKey, normalizeBrowserApiBase, parseSseDataLine, parseTransportResponse, readSseStream };
1131
+ export { type AgentChatMessage, type AgentChatParams, type AgentChatResult, type AgentPublicChatParams, type AgentStreamEvent, type AgentSummary, AgentsClient, AuthBroadcastChannel, type AuthBroadcastMessage, type AuthChangeEvent, type AuthOptions, type AuthSession, type BrowserAuthSession, type BrowserAuthTokens, type BrowserDataAuthMode, type BrowserRealtimeNotification, type BrowserRealtimeStatus, type BrowserRealtimeSubscribeParams, type BrowserRealtimeSubscription, type BrowserSqlExecParams, type BrowserSqlExecResult, type BrowserSqlQueryParams, type BrowserSqlQueryResult, type ColumnName, type ColumnValue, CookieStorageAdapter, DEFAULT_RAGABLE_API_BASE, type DefaultRagableDatabase, type FormatContextOptions, type HttpMethod, type Json, LocalStorageAdapter, MemoryStorageAdapter, type PostgRESTFetch, type PostgRESTFetchParams, PostgrestDeleteReturningBuilder, PostgrestDeleteRootBuilder, PostgrestInsertReturningBuilder, PostgrestInsertRootBuilder, PostgrestInsertSdkErrorReturning, PostgrestInsertSdkErrorRoot, type PostgrestResult, PostgrestSelectBuilder, PostgrestTableApi, PostgrestUpdateReturningBuilder, PostgrestUpdateRootBuilder, type PostgrestUpsertOptions, PostgrestUpsertReturningBuilder, PostgrestUpsertRootBuilder, type RagClientForPipeline, type RagPipeline, type RagPipelineOptions, Ragable, RagableAbortError, RagableAuth, type RagableAuthConfig, RagableBrowser, RagableBrowserAgentsClient, RagableBrowserAuthClient, type RagableBrowserClientOptions, RagableBrowserDatabaseClient, type RagableClientOptions, type RagableDatabase, RagableError, RagableNetworkError, RagableRequestClient, RagableSdkError, type RagableTableDefinition, type RagableTableNames, RagableTimeoutError, type RequestOptions, type RetrieveParams, type RetryOptions, type RunQuery, type SessionStorage, SessionStorageAdapter, type ShiftAddDocumentParams, ShiftClient, type ShiftCreateIndexParams, type ShiftEntry, type ShiftIndex, type ShiftIngestResponse, type ShiftListEntriesParams, type ShiftListEntriesResponse, type ShiftSearchParams, type ShiftSearchResult, type ShiftUpdateIndexParams, type ShiftUploadFileParams, type ShiftUploadableFile, type SseJsonEvent, type SupabaseCompatSession, type TableInsertRow, type TableRow, type TableUpdatePatch, type Tables, type TablesInsert, type TablesUpdate, Transport, type TransportOptions, type TransportRequest, asPostgrestResponse, bindFetch, createBrowserClient, createClient, createRagPipeline, createRagableBrowserClient, createRagableServerClient, detectStorage, effectiveDataAuth, extractErrorMessage, formatPostgrestError, formatRetrievalContext, formatSdkError, generateIdempotencyKey, normalizeBrowserApiBase, parseSseDataLine, parseTransportResponse, readSseStream };
package/dist/index.js CHANGED
@@ -2372,6 +2372,20 @@ var RagableBrowserDatabaseClient = class {
2372
2372
  * Postgres `LISTEN` / `NOTIFY` realtime via server-proxied SSE.
2373
2373
  * Channels must be lowercase identifiers: `[a-z_][a-z0-9_]*` (max 63 chars).
2374
2374
  */
2375
+ /**
2376
+ * Postgres `LISTEN` / `NOTIFY` realtime via server-proxied SSE.
2377
+ *
2378
+ * Returns a `BrowserRealtimeSubscription` with:
2379
+ * - `unsubscribe()` — permanently close the subscription (stops reconnects).
2380
+ * - `status` — current connection state: `"connecting"` | `"connected"` | `"reconnecting"` | `"disconnected"`.
2381
+ *
2382
+ * The subscription automatically reconnects with exponential backoff when the
2383
+ * stream drops. Server heartbeats (every 15 s) are monitored — if none arrive
2384
+ * within `heartbeatTimeoutMs` (default 45 s), the connection is treated as dead
2385
+ * and a reconnect is triggered. Auth errors (401/403/404) are non-retryable.
2386
+ *
2387
+ * Channel names must be lowercase identifiers: `[a-z_][a-z0-9_]*` (max 63 chars).
2388
+ */
2375
2389
  __publicField(this, "realtime", {
2376
2390
  subscribe: (params) => subscribeBrowserRealtime(
2377
2391
  this.options,
@@ -2401,9 +2415,13 @@ function followAbortSignal(parent, child) {
2401
2415
  }
2402
2416
  parent.addEventListener("abort", () => child.abort(), { once: true });
2403
2417
  }
2418
+ function backoffDelay(attempt, baseMs, maxMs) {
2419
+ const exp = Math.min(baseMs * 2 ** (attempt - 1), maxMs);
2420
+ const jitter = exp * (0.5 + Math.random() * 0.5);
2421
+ return Math.round(jitter);
2422
+ }
2404
2423
  async function subscribeBrowserRealtime(options, ragableAuth, fetchImpl, params) {
2405
2424
  const gid = requireAuthGroupId(options);
2406
- const token = await resolveDatabaseAuthBearer(options, ragableAuth);
2407
2425
  const databaseInstanceId = params.databaseInstanceId?.trim() || options.databaseInstanceId?.trim();
2408
2426
  if (!databaseInstanceId) {
2409
2427
  throw new RagableError(
@@ -2419,41 +2437,75 @@ async function subscribeBrowserRealtime(options, ragableAuth, fetchImpl, params)
2419
2437
  { code: "SDK_REALTIME_CHANNELS_REQUIRED" }
2420
2438
  );
2421
2439
  }
2422
- const ac = new AbortController();
2423
- followAbortSignal(params.signal, ac);
2424
- const headers = new Headers(options.headers);
2425
- headers.set("Authorization", `Bearer ${token}`);
2426
- headers.set("Content-Type", "application/json");
2427
- const response = await fetchImpl(
2428
- `${normalizeBrowserApiBase()}/auth-groups/${gid}/data/realtime/stream`,
2429
- {
2430
- method: "POST",
2431
- headers,
2432
- body: JSON.stringify({
2433
- databaseInstanceId,
2434
- channels: params.channels
2435
- }),
2436
- signal: ac.signal
2440
+ const maxAttempts = params.maxReconnectAttempts ?? Infinity;
2441
+ const baseDelay = params.reconnectBaseDelayMs ?? 1e3;
2442
+ const maxDelay = params.reconnectMaxDelayMs ?? 3e4;
2443
+ const heartbeatTimeout = params.heartbeatTimeoutMs ?? 45e3;
2444
+ const lifecycleAc = new AbortController();
2445
+ followAbortSignal(params.signal, lifecycleAc);
2446
+ let currentStatus = "connecting";
2447
+ const setStatus = (s) => {
2448
+ if (s === currentStatus) return;
2449
+ currentStatus = s;
2450
+ params.onStatusChange?.(s);
2451
+ };
2452
+ const subscription = {
2453
+ unsubscribe: () => lifecycleAc.abort(),
2454
+ get status() {
2455
+ return currentStatus;
2437
2456
  }
2438
- );
2439
- const payload = await parseMaybeJsonBody(response);
2440
- if (!response.ok) {
2441
- const message = extractErrorMessage(payload, response.statusText);
2442
- throw new RagableError(message, response.status, payload);
2443
- }
2444
- const streamBody = response.body;
2445
- if (!streamBody) {
2446
- throw new RagableError(
2447
- "Realtime stream has no body",
2448
- 502,
2449
- { code: "SDK_REALTIME_NO_BODY" }
2457
+ };
2458
+ setStatus("connecting");
2459
+ async function connectOnce(signal) {
2460
+ const token = await resolveDatabaseAuthBearer(options, ragableAuth);
2461
+ const headers = new Headers(options.headers);
2462
+ headers.set("Authorization", `Bearer ${token}`);
2463
+ headers.set("Content-Type", "application/json");
2464
+ const response = await fetchImpl(
2465
+ `${normalizeBrowserApiBase()}/auth-groups/${gid}/data/realtime/stream`,
2466
+ {
2467
+ method: "POST",
2468
+ headers,
2469
+ body: JSON.stringify({
2470
+ databaseInstanceId,
2471
+ channels: params.channels
2472
+ }),
2473
+ signal
2474
+ }
2450
2475
  );
2451
- }
2452
- void (async () => {
2476
+ if (!response.ok) {
2477
+ const payload = await parseMaybeJsonBody(response);
2478
+ const message = extractErrorMessage(payload, response.statusText);
2479
+ throw new RagableError(message, response.status, payload);
2480
+ }
2481
+ const streamBody = response.body;
2482
+ if (!streamBody) {
2483
+ throw new RagableError("Realtime stream has no body", 502, {
2484
+ code: "SDK_REALTIME_NO_BODY"
2485
+ });
2486
+ }
2487
+ let heartbeatTimer = null;
2488
+ const resetHeartbeatTimer = () => {
2489
+ if (heartbeatTimer) clearTimeout(heartbeatTimer);
2490
+ heartbeatTimer = setTimeout(() => {
2491
+ streamReader?.cancel().catch(() => void 0);
2492
+ }, heartbeatTimeout);
2493
+ };
2494
+ let streamReader = null;
2453
2495
  try {
2454
- for await (const evt of readSseStream(streamBody)) {
2496
+ streamReader = streamBody.getReader();
2497
+ const decoder = new TextDecoder();
2498
+ let buffer = "";
2499
+ resetHeartbeatTimer();
2500
+ const processEvent = (evt) => {
2501
+ if (evt.type === "realtime:heartbeat") {
2502
+ resetHeartbeatTimer();
2503
+ return;
2504
+ }
2505
+ resetHeartbeatTimer();
2455
2506
  if (evt.type === "realtime:ready") {
2456
2507
  const ch = evt.channels;
2508
+ setStatus("connected");
2457
2509
  params.onReady?.(
2458
2510
  Array.isArray(ch) ? ch.map((c) => String(c)) : []
2459
2511
  );
@@ -2466,15 +2518,78 @@ async function subscribeBrowserRealtime(options, ragableAuth, fetchImpl, params)
2466
2518
  } else if (evt.type === "realtime:error") {
2467
2519
  params.onError?.(String(evt.message ?? "Realtime error"));
2468
2520
  }
2521
+ };
2522
+ while (true) {
2523
+ const { done, value } = await streamReader.read();
2524
+ if (done) break;
2525
+ buffer += decoder.decode(value, { stream: true });
2526
+ let boundary = buffer.indexOf("\n\n");
2527
+ while (boundary !== -1) {
2528
+ const block = buffer.slice(0, boundary);
2529
+ buffer = buffer.slice(boundary + 2);
2530
+ for (const line of block.split("\n")) {
2531
+ const dataPrefix = "data: ";
2532
+ if (!line.startsWith(dataPrefix)) continue;
2533
+ const json = line.slice(dataPrefix.length).trim();
2534
+ if (!json || json === "[DONE]") continue;
2535
+ try {
2536
+ processEvent(JSON.parse(json));
2537
+ } catch {
2538
+ }
2539
+ }
2540
+ boundary = buffer.indexOf("\n\n");
2541
+ }
2469
2542
  }
2470
- } catch (e) {
2471
- if (e.name === "AbortError") return;
2472
- params.onError?.(e.message);
2543
+ return "stream_ended";
2544
+ } finally {
2545
+ if (heartbeatTimer) clearTimeout(heartbeatTimer);
2546
+ streamReader?.releaseLock();
2473
2547
  }
2548
+ }
2549
+ void (async () => {
2550
+ let attempt = 0;
2551
+ while (!lifecycleAc.signal.aborted) {
2552
+ const iterAc = new AbortController();
2553
+ followAbortSignal(lifecycleAc.signal, iterAc);
2554
+ try {
2555
+ const result = await connectOnce(iterAc.signal);
2556
+ if (lifecycleAc.signal.aborted) break;
2557
+ if (result === "stream_ended") {
2558
+ attempt++;
2559
+ }
2560
+ } catch (e) {
2561
+ if (lifecycleAc.signal.aborted) break;
2562
+ if (e.name === "AbortError") break;
2563
+ const status = e.status;
2564
+ if (status === 400 || status === 401 || status === 403 || status === 404) {
2565
+ params.onError?.(e.message);
2566
+ break;
2567
+ }
2568
+ attempt++;
2569
+ }
2570
+ if (lifecycleAc.signal.aborted) break;
2571
+ if (attempt > maxAttempts) {
2572
+ params.onError?.(`Realtime: gave up after ${maxAttempts} reconnect attempts`);
2573
+ break;
2574
+ }
2575
+ const delay = backoffDelay(attempt, baseDelay, maxDelay);
2576
+ setStatus("reconnecting");
2577
+ params.onDisconnect?.({ attempt, retryInMs: delay });
2578
+ await new Promise((r) => {
2579
+ const timer = setTimeout(r, delay);
2580
+ const onAbort = () => {
2581
+ clearTimeout(timer);
2582
+ r();
2583
+ };
2584
+ lifecycleAc.signal.addEventListener("abort", onAbort, { once: true });
2585
+ });
2586
+ if (lifecycleAc.signal.aborted) break;
2587
+ setStatus("connecting");
2588
+ params.onReconnect?.({ attempt });
2589
+ }
2590
+ setStatus("disconnected");
2474
2591
  })();
2475
- return {
2476
- unsubscribe: () => ac.abort()
2477
- };
2592
+ return subscription;
2478
2593
  }
2479
2594
  var RagableBrowserAgentsClient = class {
2480
2595
  constructor(options) {