@mitway/sdk 0.2.2 → 0.2.3

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.cts CHANGED
@@ -29,6 +29,135 @@ interface User {
29
29
  updated_at: string;
30
30
  }
31
31
 
32
+ /**
33
+ * Token Manager for the MITWAY-BaaS SDK.
34
+ *
35
+ * In-memory storage for the access token + user. Browser CSRF token lives
36
+ * in a cookie so the cookie-based refresh flow works across page reloads.
37
+ */
38
+
39
+ declare class TokenManager {
40
+ private accessToken;
41
+ private user;
42
+ /** Fired when the access token changes (used by long-lived consumers). */
43
+ onTokenChange: (() => void) | null;
44
+ saveSession(session: AuthSession): void;
45
+ getSession(): AuthSession | null;
46
+ getAccessToken(): string | null;
47
+ setAccessToken(token: string): void;
48
+ getUser(): User | null;
49
+ setUser(user: User): void;
50
+ clearSession(): void;
51
+ }
52
+
53
+ /**
54
+ * Realtime module — Socket.IO client wrapper for MITWAY-BaaS.
55
+ *
56
+ * Provides a thin, typed layer over `socket.io-client` so app code can
57
+ * subscribe / publish / listen without dealing with the underlying
58
+ * transport details. The MITWAY-BaaS backend handles auth (JWT / API
59
+ * key), RLS, rate limiting, and fan-out across replicas — the SDK just
60
+ * opens one socket per `MitwayBaasClient` instance and routes events.
61
+ */
62
+
63
+ /** Standardized meta envelope emitted by the server on every push. */
64
+ interface RealtimeMessageMeta {
65
+ channel?: string;
66
+ message_id: string;
67
+ sender_type: 'system' | 'user';
68
+ sender_id?: string;
69
+ timestamp: string;
70
+ }
71
+ /** Subscribe ack returned by the server. */
72
+ type SubscribeResult = {
73
+ ok: true;
74
+ channel: string;
75
+ } | {
76
+ ok: false;
77
+ channel: string;
78
+ error: {
79
+ code: string;
80
+ message: string;
81
+ };
82
+ };
83
+ /** Server-pushed unsolicited error. */
84
+ interface RealtimeErrorPayload {
85
+ channel?: string;
86
+ code: string;
87
+ message: string;
88
+ }
89
+ interface RealtimeListener<T = unknown> {
90
+ (payload: T, meta: RealtimeMessageMeta): void;
91
+ }
92
+ type ConnectionListener = () => void;
93
+ type DisconnectListener = (reason: string) => void;
94
+ type ConnectErrorListener = (error: Error) => void;
95
+ interface RealtimeOptions {
96
+ /** Override the path on the server. Defaults to the Socket.IO default. */
97
+ path?: string;
98
+ /** Transport strategy. Defaults to `['websocket']` — modern browsers
99
+ * and Node always support it, no need for the long-polling fallback. */
100
+ transports?: Array<'websocket' | 'polling'>;
101
+ /** Handshake timeout in ms. */
102
+ timeoutMs?: number;
103
+ /** Extra fields merged into socket.handshake.auth. Advanced usage. */
104
+ extraAuth?: Record<string, string>;
105
+ }
106
+ /**
107
+ * MITWAY-BaaS realtime client.
108
+ *
109
+ * Public API mirrors the InsForge realtime SDK for familiarity, but the
110
+ * wire protocol follows our backend (see
111
+ * `MITWAY-BaaS/backend/src/infra/socket/socket.manager.ts`):
112
+ *
113
+ * - Handshake auth uses `auth.token` containing the JWT (preferred)
114
+ * or an opaque API key string.
115
+ * - `realtime:subscribe` / `realtime:unsubscribe` / `realtime:publish`
116
+ * are the client-to-server events.
117
+ * - Server pushes the user-defined `event` name with `{ ...payload,
118
+ * meta }` shape.
119
+ * - `realtime:error` is the unsolicited error channel for publish
120
+ * failures and similar.
121
+ */
122
+ declare class Realtime {
123
+ private socket;
124
+ private baseUrl;
125
+ private options;
126
+ private anonKey;
127
+ private tokenManager;
128
+ private listeners;
129
+ private reserved;
130
+ private connecting;
131
+ private subscribedChannels;
132
+ constructor(baseUrl: string, tokenManager: TokenManager, anonKey: string | undefined, options?: RealtimeOptions);
133
+ get isConnected(): boolean;
134
+ get socketId(): string | undefined;
135
+ /** Explicitly open the connection. Safe to call multiple times; only
136
+ * opens one socket per instance. Subsequent calls during connection
137
+ * return the same in-flight promise. */
138
+ connect(): Promise<void>;
139
+ private openSocket;
140
+ /** Close the socket and clear in-memory subscription state. Reserved
141
+ * listeners survive so callers can reconnect later via `connect()`. */
142
+ disconnect(): void;
143
+ subscribe(channel: string): Promise<SubscribeResult>;
144
+ /** Fire-and-forget. No ack from the server. */
145
+ unsubscribe(channel: string): void;
146
+ /** Publish via the Socket.IO transport. Subject to RLS INSERT policy
147
+ * on `realtime.messages` (disabled by default — the developer must
148
+ * add a policy before clients can publish). Returns immediately; any
149
+ * server rejection comes through the `error` reserved event. */
150
+ publish(channel: string, event: string, payload: Record<string, unknown>): void;
151
+ on(event: 'connect', cb: ConnectionListener): void;
152
+ on(event: 'disconnect', cb: DisconnectListener): void;
153
+ on(event: 'connect_error', cb: ConnectErrorListener): void;
154
+ on(event: 'error', cb: RealtimeListener<RealtimeErrorPayload>): void;
155
+ on<T = unknown>(event: string, cb: RealtimeListener<T>): void;
156
+ off(event: string, cb: (...args: any[]) => void): void;
157
+ private dispatch;
158
+ private emitReserved;
159
+ }
160
+
32
161
  /**
33
162
  * MITWAY-BaaS SDK types — only SDK-specific shapes live here.
34
163
  * The `User` shape is inlined in `./lib/user` so this package has zero
@@ -89,6 +218,10 @@ interface MitwayBaasConfig {
89
218
  * @default true
90
219
  */
91
220
  autoRefreshToken?: boolean;
221
+ /**
222
+ * Realtime transport options. See `RealtimeOptions`.
223
+ */
224
+ realtime?: RealtimeOptions;
92
225
  }
93
226
  /**
94
227
  * Active user session in memory. Mirrors what the auth endpoints return.
@@ -146,27 +279,6 @@ declare class Logger {
146
279
  logResponse(method: string, url: string, status: number, durationMs: number, body?: any): void;
147
280
  }
148
281
 
149
- /**
150
- * Token Manager for the MITWAY-BaaS SDK.
151
- *
152
- * In-memory storage for the access token + user. Browser CSRF token lives
153
- * in a cookie so the cookie-based refresh flow works across page reloads.
154
- */
155
-
156
- declare class TokenManager {
157
- private accessToken;
158
- private user;
159
- /** Fired when the access token changes (used by long-lived consumers). */
160
- onTokenChange: (() => void) | null;
161
- saveSession(session: AuthSession): void;
162
- getSession(): AuthSession | null;
163
- getAccessToken(): string | null;
164
- setAccessToken(token: string): void;
165
- getUser(): User | null;
166
- setUser(user: User): void;
167
- clearSession(): void;
168
- }
169
-
170
282
  /**
171
283
  * HttpClient with retry, timeout, abort signal composition, and automatic
172
284
  * token refresh on 401 INVALID_TOKEN responses.
@@ -409,6 +521,7 @@ declare class MitwayBaasClient {
409
521
  private tokenManager;
410
522
  readonly auth: Auth;
411
523
  readonly database: Database;
524
+ readonly realtime: Realtime;
412
525
  constructor(config?: MitwayBaasConfig);
413
526
  /**
414
527
  * Escape hatch for callers that need to make custom requests against the
@@ -423,13 +536,13 @@ declare class MitwayBaasClient {
423
536
  * Currently ships:
424
537
  * - auth (signUp, signInWithPassword, signOut, refreshSession, getSession, getUser)
425
538
  * - database (PostgREST-backed query builder via @supabase/postgrest-js)
539
+ * - realtime (Socket.IO transport: subscribe / unsubscribe / publish / on)
426
540
  *
427
541
  * Not yet included (no backend support):
428
542
  * - storage
429
543
  * - functions
430
544
  * - email
431
545
  * - ai
432
- * - realtime
433
546
  *
434
547
  * @packageDocumentation
435
548
  */
@@ -449,4 +562,4 @@ declare class MitwayBaasClient {
449
562
  */
450
563
  declare function createClient(config: MitwayBaasConfig): MitwayBaasClient;
451
564
 
452
- export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, Database, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, type SignInRequest, type SignUpRequest, TokenManager, type User, createClient, MitwayBaasClient as default };
565
+ export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, type ConnectErrorListener, type ConnectionListener, Database, type DisconnectListener, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, Realtime, type RealtimeErrorPayload, type RealtimeListener, type RealtimeMessageMeta, type RealtimeOptions, type SignInRequest, type SignUpRequest, type SubscribeResult, TokenManager, type User, createClient, MitwayBaasClient as default };
package/dist/index.d.ts CHANGED
@@ -29,6 +29,135 @@ interface User {
29
29
  updated_at: string;
30
30
  }
31
31
 
32
+ /**
33
+ * Token Manager for the MITWAY-BaaS SDK.
34
+ *
35
+ * In-memory storage for the access token + user. Browser CSRF token lives
36
+ * in a cookie so the cookie-based refresh flow works across page reloads.
37
+ */
38
+
39
+ declare class TokenManager {
40
+ private accessToken;
41
+ private user;
42
+ /** Fired when the access token changes (used by long-lived consumers). */
43
+ onTokenChange: (() => void) | null;
44
+ saveSession(session: AuthSession): void;
45
+ getSession(): AuthSession | null;
46
+ getAccessToken(): string | null;
47
+ setAccessToken(token: string): void;
48
+ getUser(): User | null;
49
+ setUser(user: User): void;
50
+ clearSession(): void;
51
+ }
52
+
53
+ /**
54
+ * Realtime module — Socket.IO client wrapper for MITWAY-BaaS.
55
+ *
56
+ * Provides a thin, typed layer over `socket.io-client` so app code can
57
+ * subscribe / publish / listen without dealing with the underlying
58
+ * transport details. The MITWAY-BaaS backend handles auth (JWT / API
59
+ * key), RLS, rate limiting, and fan-out across replicas — the SDK just
60
+ * opens one socket per `MitwayBaasClient` instance and routes events.
61
+ */
62
+
63
+ /** Standardized meta envelope emitted by the server on every push. */
64
+ interface RealtimeMessageMeta {
65
+ channel?: string;
66
+ message_id: string;
67
+ sender_type: 'system' | 'user';
68
+ sender_id?: string;
69
+ timestamp: string;
70
+ }
71
+ /** Subscribe ack returned by the server. */
72
+ type SubscribeResult = {
73
+ ok: true;
74
+ channel: string;
75
+ } | {
76
+ ok: false;
77
+ channel: string;
78
+ error: {
79
+ code: string;
80
+ message: string;
81
+ };
82
+ };
83
+ /** Server-pushed unsolicited error. */
84
+ interface RealtimeErrorPayload {
85
+ channel?: string;
86
+ code: string;
87
+ message: string;
88
+ }
89
+ interface RealtimeListener<T = unknown> {
90
+ (payload: T, meta: RealtimeMessageMeta): void;
91
+ }
92
+ type ConnectionListener = () => void;
93
+ type DisconnectListener = (reason: string) => void;
94
+ type ConnectErrorListener = (error: Error) => void;
95
+ interface RealtimeOptions {
96
+ /** Override the path on the server. Defaults to the Socket.IO default. */
97
+ path?: string;
98
+ /** Transport strategy. Defaults to `['websocket']` — modern browsers
99
+ * and Node always support it, no need for the long-polling fallback. */
100
+ transports?: Array<'websocket' | 'polling'>;
101
+ /** Handshake timeout in ms. */
102
+ timeoutMs?: number;
103
+ /** Extra fields merged into socket.handshake.auth. Advanced usage. */
104
+ extraAuth?: Record<string, string>;
105
+ }
106
+ /**
107
+ * MITWAY-BaaS realtime client.
108
+ *
109
+ * Public API mirrors the InsForge realtime SDK for familiarity, but the
110
+ * wire protocol follows our backend (see
111
+ * `MITWAY-BaaS/backend/src/infra/socket/socket.manager.ts`):
112
+ *
113
+ * - Handshake auth uses `auth.token` containing the JWT (preferred)
114
+ * or an opaque API key string.
115
+ * - `realtime:subscribe` / `realtime:unsubscribe` / `realtime:publish`
116
+ * are the client-to-server events.
117
+ * - Server pushes the user-defined `event` name with `{ ...payload,
118
+ * meta }` shape.
119
+ * - `realtime:error` is the unsolicited error channel for publish
120
+ * failures and similar.
121
+ */
122
+ declare class Realtime {
123
+ private socket;
124
+ private baseUrl;
125
+ private options;
126
+ private anonKey;
127
+ private tokenManager;
128
+ private listeners;
129
+ private reserved;
130
+ private connecting;
131
+ private subscribedChannels;
132
+ constructor(baseUrl: string, tokenManager: TokenManager, anonKey: string | undefined, options?: RealtimeOptions);
133
+ get isConnected(): boolean;
134
+ get socketId(): string | undefined;
135
+ /** Explicitly open the connection. Safe to call multiple times; only
136
+ * opens one socket per instance. Subsequent calls during connection
137
+ * return the same in-flight promise. */
138
+ connect(): Promise<void>;
139
+ private openSocket;
140
+ /** Close the socket and clear in-memory subscription state. Reserved
141
+ * listeners survive so callers can reconnect later via `connect()`. */
142
+ disconnect(): void;
143
+ subscribe(channel: string): Promise<SubscribeResult>;
144
+ /** Fire-and-forget. No ack from the server. */
145
+ unsubscribe(channel: string): void;
146
+ /** Publish via the Socket.IO transport. Subject to RLS INSERT policy
147
+ * on `realtime.messages` (disabled by default — the developer must
148
+ * add a policy before clients can publish). Returns immediately; any
149
+ * server rejection comes through the `error` reserved event. */
150
+ publish(channel: string, event: string, payload: Record<string, unknown>): void;
151
+ on(event: 'connect', cb: ConnectionListener): void;
152
+ on(event: 'disconnect', cb: DisconnectListener): void;
153
+ on(event: 'connect_error', cb: ConnectErrorListener): void;
154
+ on(event: 'error', cb: RealtimeListener<RealtimeErrorPayload>): void;
155
+ on<T = unknown>(event: string, cb: RealtimeListener<T>): void;
156
+ off(event: string, cb: (...args: any[]) => void): void;
157
+ private dispatch;
158
+ private emitReserved;
159
+ }
160
+
32
161
  /**
33
162
  * MITWAY-BaaS SDK types — only SDK-specific shapes live here.
34
163
  * The `User` shape is inlined in `./lib/user` so this package has zero
@@ -89,6 +218,10 @@ interface MitwayBaasConfig {
89
218
  * @default true
90
219
  */
91
220
  autoRefreshToken?: boolean;
221
+ /**
222
+ * Realtime transport options. See `RealtimeOptions`.
223
+ */
224
+ realtime?: RealtimeOptions;
92
225
  }
93
226
  /**
94
227
  * Active user session in memory. Mirrors what the auth endpoints return.
@@ -146,27 +279,6 @@ declare class Logger {
146
279
  logResponse(method: string, url: string, status: number, durationMs: number, body?: any): void;
147
280
  }
148
281
 
149
- /**
150
- * Token Manager for the MITWAY-BaaS SDK.
151
- *
152
- * In-memory storage for the access token + user. Browser CSRF token lives
153
- * in a cookie so the cookie-based refresh flow works across page reloads.
154
- */
155
-
156
- declare class TokenManager {
157
- private accessToken;
158
- private user;
159
- /** Fired when the access token changes (used by long-lived consumers). */
160
- onTokenChange: (() => void) | null;
161
- saveSession(session: AuthSession): void;
162
- getSession(): AuthSession | null;
163
- getAccessToken(): string | null;
164
- setAccessToken(token: string): void;
165
- getUser(): User | null;
166
- setUser(user: User): void;
167
- clearSession(): void;
168
- }
169
-
170
282
  /**
171
283
  * HttpClient with retry, timeout, abort signal composition, and automatic
172
284
  * token refresh on 401 INVALID_TOKEN responses.
@@ -409,6 +521,7 @@ declare class MitwayBaasClient {
409
521
  private tokenManager;
410
522
  readonly auth: Auth;
411
523
  readonly database: Database;
524
+ readonly realtime: Realtime;
412
525
  constructor(config?: MitwayBaasConfig);
413
526
  /**
414
527
  * Escape hatch for callers that need to make custom requests against the
@@ -423,13 +536,13 @@ declare class MitwayBaasClient {
423
536
  * Currently ships:
424
537
  * - auth (signUp, signInWithPassword, signOut, refreshSession, getSession, getUser)
425
538
  * - database (PostgREST-backed query builder via @supabase/postgrest-js)
539
+ * - realtime (Socket.IO transport: subscribe / unsubscribe / publish / on)
426
540
  *
427
541
  * Not yet included (no backend support):
428
542
  * - storage
429
543
  * - functions
430
544
  * - email
431
545
  * - ai
432
- * - realtime
433
546
  *
434
547
  * @packageDocumentation
435
548
  */
@@ -449,4 +562,4 @@ declare class MitwayBaasClient {
449
562
  */
450
563
  declare function createClient(config: MitwayBaasConfig): MitwayBaasClient;
451
564
 
452
- export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, Database, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, type SignInRequest, type SignUpRequest, TokenManager, type User, createClient, MitwayBaasClient as default };
565
+ export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, type ConnectErrorListener, type ConnectionListener, Database, type DisconnectListener, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, Realtime, type RealtimeErrorPayload, type RealtimeListener, type RealtimeMessageMeta, type RealtimeOptions, type SignInRequest, type SignUpRequest, type SubscribeResult, TokenManager, type User, createClient, MitwayBaasClient as default };
package/dist/index.js CHANGED
@@ -854,18 +854,239 @@ var Database = class {
854
854
  }
855
855
  };
856
856
 
857
+ // src/modules/realtime.ts
858
+ import { io } from "socket.io-client";
859
+ var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
860
+ var Realtime = class {
861
+ socket = null;
862
+ baseUrl;
863
+ options;
864
+ anonKey;
865
+ tokenManager;
866
+ listeners = /* @__PURE__ */ new Map();
867
+ reserved = {
868
+ connect: /* @__PURE__ */ new Set(),
869
+ disconnect: /* @__PURE__ */ new Set(),
870
+ connect_error: /* @__PURE__ */ new Set(),
871
+ error: /* @__PURE__ */ new Set()
872
+ };
873
+ connecting = null;
874
+ subscribedChannels = /* @__PURE__ */ new Set();
875
+ constructor(baseUrl, tokenManager, anonKey, options = {}) {
876
+ this.baseUrl = baseUrl;
877
+ this.tokenManager = tokenManager;
878
+ this.anonKey = anonKey;
879
+ this.options = options;
880
+ }
881
+ // -----------------------------------------------------------------
882
+ // Connection lifecycle
883
+ // -----------------------------------------------------------------
884
+ get isConnected() {
885
+ return this.socket?.connected === true;
886
+ }
887
+ get socketId() {
888
+ return this.socket?.id;
889
+ }
890
+ /** Explicitly open the connection. Safe to call multiple times; only
891
+ * opens one socket per instance. Subsequent calls during connection
892
+ * return the same in-flight promise. */
893
+ connect() {
894
+ if (this.isConnected) {
895
+ return Promise.resolve();
896
+ }
897
+ if (this.connecting) {
898
+ return this.connecting;
899
+ }
900
+ this.connecting = this.openSocket();
901
+ return this.connecting;
902
+ }
903
+ openSocket() {
904
+ const token = this.tokenManager.getAccessToken() ?? this.anonKey;
905
+ if (!token) {
906
+ const err = new MitwayBaasError(
907
+ "Realtime requires an access token or anonKey",
908
+ 401,
909
+ "AUTH_INVALID_API_KEY"
910
+ );
911
+ this.connecting = null;
912
+ return Promise.reject(err);
913
+ }
914
+ const timeoutMs = this.options.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
915
+ const socket = io(this.baseUrl, {
916
+ path: this.options.path,
917
+ transports: this.options.transports ?? ["websocket"],
918
+ auth: { token, ...this.options.extraAuth ?? {} },
919
+ reconnection: true,
920
+ timeout: timeoutMs
921
+ });
922
+ this.socket = socket;
923
+ socket.onAny((event, ...args) => this.dispatch(event, args));
924
+ socket.on("connect", () => this.emitReserved("connect"));
925
+ socket.on("disconnect", (reason) => this.emitReserved("disconnect", reason));
926
+ socket.on("connect_error", (err) => this.emitReserved("connect_error", err));
927
+ return new Promise((resolve, reject) => {
928
+ const timer = setTimeout(() => {
929
+ socket.off("connect", onConnect);
930
+ socket.off("connect_error", onConnectError);
931
+ reject(
932
+ new MitwayBaasError(
933
+ `Realtime connection timeout after ${timeoutMs}ms`,
934
+ 408,
935
+ "CONNECTION_TIMEOUT"
936
+ )
937
+ );
938
+ this.connecting = null;
939
+ }, timeoutMs);
940
+ const clear = () => {
941
+ clearTimeout(timer);
942
+ socket.off("connect", onConnect);
943
+ socket.off("connect_error", onConnectError);
944
+ };
945
+ const onConnect = () => {
946
+ clear();
947
+ this.connecting = null;
948
+ resolve();
949
+ };
950
+ const onConnectError = (err) => {
951
+ clear();
952
+ this.connecting = null;
953
+ reject(
954
+ new MitwayBaasError(err.message, 0, "CONNECTION_FAILED")
955
+ );
956
+ };
957
+ socket.once("connect", onConnect);
958
+ socket.once("connect_error", onConnectError);
959
+ });
960
+ }
961
+ /** Close the socket and clear in-memory subscription state. Reserved
962
+ * listeners survive so callers can reconnect later via `connect()`. */
963
+ disconnect() {
964
+ if (!this.socket) {
965
+ return;
966
+ }
967
+ this.socket.disconnect();
968
+ this.socket = null;
969
+ this.subscribedChannels.clear();
970
+ }
971
+ // -----------------------------------------------------------------
972
+ // Subscribe / Unsubscribe / Publish
973
+ // -----------------------------------------------------------------
974
+ async subscribe(channel) {
975
+ await this.connect();
976
+ const socket = this.socket;
977
+ if (!socket) {
978
+ return {
979
+ ok: false,
980
+ channel,
981
+ error: { code: "NOT_CONNECTED", message: "Socket is not connected" }
982
+ };
983
+ }
984
+ const result = await new Promise((resolve) => {
985
+ socket.emit("realtime:subscribe", { channel }, (ack) => {
986
+ resolve(ack);
987
+ });
988
+ });
989
+ if (result.ok) {
990
+ this.subscribedChannels.add(channel);
991
+ }
992
+ return result;
993
+ }
994
+ /** Fire-and-forget. No ack from the server. */
995
+ unsubscribe(channel) {
996
+ this.subscribedChannels.delete(channel);
997
+ this.socket?.emit("realtime:unsubscribe", { channel });
998
+ }
999
+ /** Publish via the Socket.IO transport. Subject to RLS INSERT policy
1000
+ * on `realtime.messages` (disabled by default — the developer must
1001
+ * add a policy before clients can publish). Returns immediately; any
1002
+ * server rejection comes through the `error` reserved event. */
1003
+ publish(channel, event, payload) {
1004
+ this.socket?.emit("realtime:publish", { channel, event, payload });
1005
+ }
1006
+ // TypeScript overload impl signature must be assignable from every
1007
+ // public overload. The public overloads take arg lists of different
1008
+ // shapes (ConnectionListener: 0 args, DisconnectListener: 1 string
1009
+ // arg, RealtimeListener: 2 args), so the implementation uses the
1010
+ // widest possible signature. This matches the pattern in socket.io
1011
+ // itself and is the standard TypeScript overload idiom.
1012
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1013
+ on(event, cb) {
1014
+ if (isReserved(event)) {
1015
+ this.reserved[event].add(cb);
1016
+ return;
1017
+ }
1018
+ if (!this.listeners.has(event)) {
1019
+ this.listeners.set(event, /* @__PURE__ */ new Set());
1020
+ }
1021
+ this.listeners.get(event).add(cb);
1022
+ }
1023
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1024
+ off(event, cb) {
1025
+ if (isReserved(event)) {
1026
+ this.reserved[event].delete(cb);
1027
+ return;
1028
+ }
1029
+ this.listeners.get(event)?.delete(cb);
1030
+ }
1031
+ // -----------------------------------------------------------------
1032
+ // Internals
1033
+ // -----------------------------------------------------------------
1034
+ dispatch(event, args) {
1035
+ if (isReserved(event)) {
1036
+ return;
1037
+ }
1038
+ if (event === "realtime:error") {
1039
+ const err = args[0] ?? {};
1040
+ this.reserved.error.forEach(
1041
+ (cb) => cb(err, {
1042
+ message_id: "",
1043
+ sender_type: "system",
1044
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1045
+ })
1046
+ );
1047
+ return;
1048
+ }
1049
+ const set = this.listeners.get(event);
1050
+ if (!set || set.size === 0) {
1051
+ return;
1052
+ }
1053
+ const envelope = args[0] ?? {};
1054
+ const { meta, ...payload } = envelope;
1055
+ const metaOrStub = meta ?? {
1056
+ message_id: "",
1057
+ sender_type: "system",
1058
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1059
+ };
1060
+ set.forEach((cb) => cb(payload, metaOrStub));
1061
+ }
1062
+ emitReserved(event, ...args) {
1063
+ const set = this.reserved[event];
1064
+ set.forEach((cb) => cb(...args));
1065
+ }
1066
+ };
1067
+ function isReserved(event) {
1068
+ return event === "connect" || event === "disconnect" || event === "connect_error" || event === "error";
1069
+ }
1070
+
857
1071
  // src/client.ts
858
1072
  var MitwayBaasClient = class {
859
1073
  http;
860
1074
  tokenManager;
861
1075
  auth;
862
1076
  database;
1077
+ realtime;
863
1078
  constructor(config = {}) {
864
1079
  const logger = new Logger(config.debug);
865
1080
  this.tokenManager = new TokenManager();
866
1081
  this.http = new HttpClient(config, this.tokenManager, logger);
867
1082
  this.auth = new Auth(this.http, this.tokenManager);
868
1083
  this.database = new Database(this.http, this.tokenManager, config.anonKey);
1084
+ this.realtime = new Realtime(
1085
+ this.http.baseUrl,
1086
+ this.tokenManager,
1087
+ config.anonKey,
1088
+ config.realtime
1089
+ );
869
1090
  }
870
1091
  /**
871
1092
  * Escape hatch for callers that need to make custom requests against the
@@ -888,6 +1109,7 @@ export {
888
1109
  Logger,
889
1110
  MitwayBaasClient,
890
1111
  MitwayBaasError,
1112
+ Realtime,
891
1113
  TokenManager,
892
1114
  createClient,
893
1115
  index_default as default