@aurabase/aurabase-js 0.3.0 → 0.3.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.
Files changed (258) hide show
  1. package/package.json +4 -4
  2. package/packages/ai/node_modules/.bin/esbuild +0 -19
  3. package/packages/ai/node_modules/.bin/tsc +0 -22
  4. package/packages/ai/node_modules/.bin/tsserver +0 -22
  5. package/packages/ai/node_modules/.bin/tsup +0 -22
  6. package/packages/ai/node_modules/.bin/tsup-node +0 -22
  7. package/packages/ai/node_modules/.bin/vite +0 -22
  8. package/packages/ai/node_modules/.bin/vitest +0 -22
  9. package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  10. package/packages/ai/src/AiService.ts +0 -226
  11. package/packages/ai/src/index.ts +0 -18
  12. package/packages/ai/src/sse.ts +0 -141
  13. package/packages/ai/src/types.ts +0 -30
  14. package/packages/ai/tests/chat-stream.test.ts +0 -151
  15. package/packages/ai/tests/rag-namespaces.test.ts +0 -54
  16. package/packages/ai/tests/sse.test.ts +0 -69
  17. package/packages/ai/tsconfig.json +0 -5
  18. package/packages/ai/tsup.config.ts +0 -11
  19. package/packages/aurabase/node_modules/.bin/esbuild +0 -19
  20. package/packages/aurabase/node_modules/.bin/tsc +0 -22
  21. package/packages/aurabase/node_modules/.bin/tsserver +0 -22
  22. package/packages/aurabase/node_modules/.bin/tsup +0 -22
  23. package/packages/aurabase/node_modules/.bin/tsup-node +0 -22
  24. package/packages/aurabase/node_modules/.bin/vite +0 -22
  25. package/packages/aurabase/node_modules/.bin/vitest +0 -22
  26. package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  27. package/packages/aurabase/src/AuraClient.ts +0 -237
  28. package/packages/aurabase/src/index.ts +0 -152
  29. package/packages/aurabase/src/ssr.ts +0 -156
  30. package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +0 -82
  31. package/packages/aurabase/tests/ai.test.ts +0 -111
  32. package/packages/aurabase/tests/auth-admin.test.ts +0 -515
  33. package/packages/aurabase/tests/auth-e2e.test.ts +0 -135
  34. package/packages/aurabase/tests/auth-hardening.test.ts +0 -206
  35. package/packages/aurabase/tests/auth.test.ts +0 -947
  36. package/packages/aurabase/tests/database.test.ts +0 -230
  37. package/packages/aurabase/tests/db-types-e2e.test.ts +0 -217
  38. package/packages/aurabase/tests/dispose.test.ts +0 -23
  39. package/packages/aurabase/tests/foreign-token.e2e.test.ts +0 -129
  40. package/packages/aurabase/tests/functions.test.ts +0 -107
  41. package/packages/aurabase/tests/integration.test.ts +0 -413
  42. package/packages/aurabase/tests/live-production-validation.js +0 -241
  43. package/packages/aurabase/tests/live-sdk-production-validation.js +0 -293
  44. package/packages/aurabase/tests/mocks.ts +0 -139
  45. package/packages/aurabase/tests/notifications.test.ts +0 -314
  46. package/packages/aurabase/tests/observability-backup.test.ts +0 -186
  47. package/packages/aurabase/tests/observability-hook.test.ts +0 -130
  48. package/packages/aurabase/tests/production-robustness.test.ts +0 -268
  49. package/packages/aurabase/tests/public-api-surface.test.ts +0 -53
  50. package/packages/aurabase/tests/query-builder.test.ts +0 -329
  51. package/packages/aurabase/tests/readme-smoke-e2e.test.ts +0 -231
  52. package/packages/aurabase/tests/realtime-filter-e2e.test.ts +0 -344
  53. package/packages/aurabase/tests/refresh-locks.test.ts +0 -210
  54. package/packages/aurabase/tests/retry-option.test.ts +0 -17
  55. package/packages/aurabase/tests/rpc-e2e.test.ts +0 -254
  56. package/packages/aurabase/tests/rs256-local-e2e.test.ts +0 -114
  57. package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +0 -235
  58. package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +0 -124
  59. package/packages/aurabase/tests/service-role-guard.test.ts +0 -99
  60. package/packages/aurabase/tests/ssr.test.ts +0 -91
  61. package/packages/aurabase/tests/storage-backend-e2e.test.ts +0 -481
  62. package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +0 -104
  63. package/packages/aurabase/tests/storage-transform-e2e.test.ts +0 -684
  64. package/packages/aurabase/tests/storage.test.ts +0 -55
  65. package/packages/aurabase/tests/types/client-generic.test-d.ts +0 -79
  66. package/packages/aurabase/tests/types/docs-snippets.test-d.ts +0 -571
  67. package/packages/aurabase/tests/types/error-code.test-d.ts +0 -53
  68. package/packages/aurabase/tsconfig.json +0 -5
  69. package/packages/aurabase/tsconfig.test-d.json +0 -5
  70. package/packages/aurabase/tsup.config.ts +0 -11
  71. package/packages/aurabase/vitest.config.ts +0 -16
  72. package/packages/auth/node_modules/.bin/esbuild +0 -19
  73. package/packages/auth/node_modules/.bin/tsc +0 -22
  74. package/packages/auth/node_modules/.bin/tsserver +0 -22
  75. package/packages/auth/node_modules/.bin/tsup +0 -22
  76. package/packages/auth/node_modules/.bin/tsup-node +0 -22
  77. package/packages/auth/node_modules/.bin/vite +0 -22
  78. package/packages/auth/node_modules/.bin/vitest +0 -22
  79. package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  80. package/packages/auth/src/AuthAdminService.ts +0 -149
  81. package/packages/auth/src/AuthService.ts +0 -1166
  82. package/packages/auth/src/SessionManager.ts +0 -92
  83. package/packages/auth/src/index.ts +0 -17
  84. package/packages/auth/src/jwks.ts +0 -337
  85. package/packages/auth/src/pkce.ts +0 -27
  86. package/packages/auth/src/types.ts +0 -89
  87. package/packages/auth/tests/auth-lifecycle.test.ts +0 -149
  88. package/packages/auth/tests/detect-session.test.ts +0 -361
  89. package/packages/auth/tests/getclaims.test.ts +0 -780
  90. package/packages/auth/tests/is-mfa-challenge.test.ts +0 -27
  91. package/packages/auth/tests/password-recovery.test.ts +0 -124
  92. package/packages/auth/tests/pkce.test.ts +0 -192
  93. package/packages/auth/tests/session-manager.test.ts +0 -103
  94. package/packages/auth/tests/set-session.test.ts +0 -139
  95. package/packages/auth/tests/verify-otp.test.ts +0 -153
  96. package/packages/auth/tsconfig.json +0 -5
  97. package/packages/auth/tsup.config.ts +0 -11
  98. package/packages/core/node_modules/.bin/esbuild +0 -22
  99. package/packages/core/node_modules/.bin/tsc +0 -22
  100. package/packages/core/node_modules/.bin/tsserver +0 -22
  101. package/packages/core/node_modules/.bin/tsup +0 -22
  102. package/packages/core/node_modules/.bin/tsup-node +0 -22
  103. package/packages/core/node_modules/.bin/vite +0 -22
  104. package/packages/core/node_modules/.bin/vitest +0 -22
  105. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  106. package/packages/core/src/AuraError.ts +0 -89
  107. package/packages/core/src/BaseService.ts +0 -44
  108. package/packages/core/src/client.ts +0 -34
  109. package/packages/core/src/cookie-utils.ts +0 -42
  110. package/packages/core/src/fetch.ts +0 -435
  111. package/packages/core/src/index.ts +0 -22
  112. package/packages/core/src/retry.ts +0 -63
  113. package/packages/core/src/serialize.ts +0 -6
  114. package/packages/core/src/stores/BaseAuthStore.ts +0 -164
  115. package/packages/core/src/stores/LocalAuthStore.ts +0 -199
  116. package/packages/core/src/types.ts +0 -1042
  117. package/packages/core/tests/auth-store.test.ts +0 -300
  118. package/packages/core/tests/error-code.test.ts +0 -61
  119. package/packages/core/tests/error.test.ts +0 -24
  120. package/packages/core/tests/fetch.test.ts +0 -286
  121. package/packages/core/tests/mocks.ts +0 -139
  122. package/packages/core/tests/observability.test.ts +0 -86
  123. package/packages/core/tests/refresh-hardening.test.ts +0 -164
  124. package/packages/core/tests/refresh-observability.test.ts +0 -111
  125. package/packages/core/tests/retry.test.ts +0 -106
  126. package/packages/core/tests/serialize.test.ts +0 -12
  127. package/packages/core/tests/timeout.test.ts +0 -79
  128. package/packages/core/tsconfig.json +0 -5
  129. package/packages/core/tsup.config.ts +0 -11
  130. package/packages/db/node_modules/.bin/esbuild +0 -19
  131. package/packages/db/node_modules/.bin/tsc +0 -22
  132. package/packages/db/node_modules/.bin/tsserver +0 -22
  133. package/packages/db/node_modules/.bin/tsup +0 -22
  134. package/packages/db/node_modules/.bin/tsup-node +0 -22
  135. package/packages/db/node_modules/.bin/vite +0 -22
  136. package/packages/db/node_modules/.bin/vitest +0 -22
  137. package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  138. package/packages/db/src/DatabaseService.ts +0 -170
  139. package/packages/db/src/QueryBuilder.ts +0 -830
  140. package/packages/db/src/index.ts +0 -2
  141. package/packages/db/src/pgrst-types/common/common.ts +0 -86
  142. package/packages/db/src/pgrst-types/common/rpc.ts +0 -151
  143. package/packages/db/src/pgrst-types/feature-flags.ts +0 -20
  144. package/packages/db/src/pgrst-types/types.ts +0 -134
  145. package/packages/db/src/select-query-parser/parser.ts +0 -479
  146. package/packages/db/src/select-query-parser/result.ts +0 -553
  147. package/packages/db/src/select-query-parser/types.ts +0 -132
  148. package/packages/db/src/select-query-parser/utils.ts +0 -707
  149. package/packages/db/tests/filters.test.ts +0 -81
  150. package/packages/db/tests/mutations-filtered.e2e.test.ts +0 -132
  151. package/packages/db/tests/mutations-filtered.test.ts +0 -188
  152. package/packages/db/tests/order.test.ts +0 -45
  153. package/packages/db/tests/rpc.test.ts +0 -41
  154. package/packages/db/tests/scale/build-large-schema.ts +0 -94
  155. package/packages/db/tests/scale/scale-compile.test.ts +0 -167
  156. package/packages/db/tests/schema.test.ts +0 -77
  157. package/packages/db/tests/single-accept.e2e.test.ts +0 -123
  158. package/packages/db/tests/single.test.ts +0 -211
  159. package/packages/db/tests/types/filters.test-d.ts +0 -52
  160. package/packages/db/tests/types/fixtures.ts +0 -40
  161. package/packages/db/tests/types/from-views.test-d.ts +0 -18
  162. package/packages/db/tests/types/mutations.test-d.ts +0 -23
  163. package/packages/db/tests/types/rpc.test-d.ts +0 -60
  164. package/packages/db/tests/types/schema-shape.test-d.ts +0 -8
  165. package/packages/db/tests/types/select-inference.test-d.ts +0 -38
  166. package/packages/db/tests/vendored/fixtures.ts +0 -83
  167. package/packages/db/tests/vendored/get-result.test-d.ts +0 -75
  168. package/packages/db/tsconfig.json +0 -5
  169. package/packages/db/tsconfig.test-d.json +0 -5
  170. package/packages/db/tsup.config.ts +0 -11
  171. package/packages/db/vitest.config.ts +0 -8
  172. package/packages/functions/node_modules/.bin/esbuild +0 -19
  173. package/packages/functions/node_modules/.bin/tsc +0 -22
  174. package/packages/functions/node_modules/.bin/tsserver +0 -22
  175. package/packages/functions/node_modules/.bin/tsup +0 -22
  176. package/packages/functions/node_modules/.bin/tsup-node +0 -22
  177. package/packages/functions/node_modules/.bin/vite +0 -22
  178. package/packages/functions/node_modules/.bin/vitest +0 -22
  179. package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  180. package/packages/functions/src/FunctionsService.ts +0 -188
  181. package/packages/functions/src/errors.ts +0 -70
  182. package/packages/functions/src/index.ts +0 -6
  183. package/packages/functions/tests/invoke.test.ts +0 -97
  184. package/packages/functions/tests/response-type-errors.test.ts +0 -135
  185. package/packages/functions/tsconfig.json +0 -5
  186. package/packages/functions/tsup.config.ts +0 -11
  187. package/packages/gen/node_modules/.bin/esbuild +0 -19
  188. package/packages/gen/node_modules/.bin/tsc +0 -22
  189. package/packages/gen/node_modules/.bin/tsserver +0 -22
  190. package/packages/gen/node_modules/.bin/tsup +0 -22
  191. package/packages/gen/node_modules/.bin/tsup-node +0 -22
  192. package/packages/gen/node_modules/.bin/vite +0 -22
  193. package/packages/gen/node_modules/.bin/vitest +0 -22
  194. package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  195. package/packages/gen/src/cli.ts +0 -67
  196. package/packages/gen/src/generate.ts +0 -0
  197. package/packages/gen/src/index.ts +0 -2
  198. package/packages/gen/tests/compile.test.ts +0 -213
  199. package/packages/gen/tests/e2e.test.ts +0 -130
  200. package/packages/gen/tests/generate.test.ts +0 -396
  201. package/packages/gen/tsconfig.json +0 -5
  202. package/packages/gen/tsup.config.ts +0 -11
  203. package/packages/notifications/node_modules/.bin/esbuild +0 -19
  204. package/packages/notifications/node_modules/.bin/tsc +0 -22
  205. package/packages/notifications/node_modules/.bin/tsserver +0 -22
  206. package/packages/notifications/node_modules/.bin/tsup +0 -22
  207. package/packages/notifications/node_modules/.bin/tsup-node +0 -22
  208. package/packages/notifications/node_modules/.bin/vite +0 -22
  209. package/packages/notifications/node_modules/.bin/vitest +0 -22
  210. package/packages/notifications/src/NotificationsService.ts +0 -230
  211. package/packages/notifications/src/index.ts +0 -19
  212. package/packages/notifications/tsconfig.json +0 -5
  213. package/packages/notifications/tsup.config.ts +0 -11
  214. package/packages/realtime/node_modules/.bin/esbuild +0 -19
  215. package/packages/realtime/node_modules/.bin/tsc +0 -22
  216. package/packages/realtime/node_modules/.bin/tsserver +0 -22
  217. package/packages/realtime/node_modules/.bin/tsup +0 -22
  218. package/packages/realtime/node_modules/.bin/tsup-node +0 -22
  219. package/packages/realtime/node_modules/.bin/vite +0 -22
  220. package/packages/realtime/node_modules/.bin/vitest +0 -22
  221. package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  222. package/packages/realtime/src/AuraChannel.ts +0 -485
  223. package/packages/realtime/src/HeartbeatManager.ts +0 -73
  224. package/packages/realtime/src/RealtimeService.ts +0 -505
  225. package/packages/realtime/src/columnFilter.ts +0 -301
  226. package/packages/realtime/src/index.ts +0 -4
  227. package/packages/realtime/tests/ack-routing.test.ts +0 -81
  228. package/packages/realtime/tests/column-filter.test.ts +0 -144
  229. package/packages/realtime/tests/dispatch.test.ts +0 -191
  230. package/packages/realtime/tests/heartbeat.test.ts +0 -67
  231. package/packages/realtime/tests/http-broadcast.test.ts +0 -68
  232. package/packages/realtime/tests/realtime-token.test.ts +0 -43
  233. package/packages/realtime/tests/reconnect-race.test.ts +0 -149
  234. package/packages/realtime/tests/ref-id.test.ts +0 -34
  235. package/packages/realtime/tests/set-auth.test.ts +0 -70
  236. package/packages/realtime/tsconfig.json +0 -5
  237. package/packages/realtime/tsup.config.ts +0 -11
  238. package/packages/storage/node_modules/.bin/esbuild +0 -19
  239. package/packages/storage/node_modules/.bin/tsc +0 -22
  240. package/packages/storage/node_modules/.bin/tsserver +0 -22
  241. package/packages/storage/node_modules/.bin/tsup +0 -22
  242. package/packages/storage/node_modules/.bin/tsup-node +0 -22
  243. package/packages/storage/node_modules/.bin/vite +0 -22
  244. package/packages/storage/node_modules/.bin/vitest +0 -22
  245. package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  246. package/packages/storage/src/DownloadBuilder.ts +0 -60
  247. package/packages/storage/src/StorageService.ts +0 -323
  248. package/packages/storage/src/index.ts +0 -2
  249. package/packages/storage/src/path.ts +0 -35
  250. package/packages/storage/src/transform.ts +0 -15
  251. package/packages/storage/tests/download.test.ts +0 -64
  252. package/packages/storage/tests/parity.test.ts +0 -154
  253. package/packages/storage/tests/path.test.ts +0 -87
  254. package/packages/storage/tests/transform.test.ts +0 -79
  255. package/packages/storage/tsconfig.json +0 -5
  256. package/packages/storage/tsup.config.ts +0 -11
  257. package/pnpm-workspace.yaml +0 -24
  258. package/tsconfig.base.json +0 -15
@@ -1,505 +0,0 @@
1
- import type { AuraClientLike, AuraResponse } from '@aurabase/core'
2
- import type { ServerMessage, ChannelConfig, BroadcastAckMessage } from '@aurabase/core'
3
- import { fetchWithAuth } from '@aurabase/core'
4
- import { AuraChannel } from './AuraChannel'
5
- import { HeartbeatManager } from './HeartbeatManager'
6
-
7
- /** Connection state machine */
8
- export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
9
-
10
- /**
11
- * Realtime service — manages a single WebSocket connection and multiplexes
12
- * channels on top of it. Supabase-compatible API.
13
- *
14
- * @example
15
- * ```ts
16
- * const aura = createClient('http://localhost:8080', 'my-project')
17
- *
18
- * const channel = aura.realtime.channel('room-1')
19
- * .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {
20
- * console.log('New message:', payload)
21
- * })
22
- * .on('broadcast', { event: 'cursor-pos' }, (payload) => {
23
- * console.log('Cursor:', payload)
24
- * })
25
- * .subscribe((status) => {
26
- * if (status === 'SUBSCRIBED') {
27
- * channel.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100, y: 200 } })
28
- * }
29
- * })
30
- * ```
31
- */
32
- export class RealtimeService {
33
- private readonly client: AuraClientLike
34
- private _ws: WebSocket | null = null
35
- private _channels = new Map<string, AuraChannel>()
36
- private _state: ConnectionState = 'disconnected'
37
- private _stateListeners = new Set<(state: ConnectionState) => void>()
38
-
39
- // ─── Reconnection state ──────────────────────────────────────────────
40
- private _autoReconnect = true
41
- private _reconnectAttempts = 0
42
- private _maxReconnectAttempts = Infinity
43
- private _reconnectTimer: ReturnType<typeof setTimeout> | null = null
44
- private _intentionalClose = false
45
- private _heartbeat = new HeartbeatManager()
46
- private _tokenRefreshTimer: ReturnType<typeof setTimeout> | null = null
47
- /** Dernier access_token propagé au serveur — évite les pushs redondants (propagation différentielle). */
48
- private _lastSentToken: string | null = null
49
-
50
- /** Backoff delays in ms: 1s, 2s, 4s, 8s, 16s, 30s (cap) */
51
- private static readonly BACKOFF_MS = [1000, 2000, 4000, 8000, 16000, 30000]
52
-
53
- constructor(client: AuraClientLike) {
54
- this.client = client
55
- }
56
-
57
- /** Current connection state. */
58
- get connectionState(): ConnectionState {
59
- return this._state
60
- }
61
-
62
- /** Listen for connection state changes. */
63
- onStateChange(callback: (state: ConnectionState) => void): () => void {
64
- this._stateListeners.add(callback)
65
- return () => this._stateListeners.delete(callback)
66
- }
67
-
68
- private _setState(state: ConnectionState): void {
69
- this._state = state
70
- for (const cb of this._stateListeners) {
71
- try { cb(state) } catch { /* ignore */ }
72
- }
73
- }
74
-
75
- // ─── WebSocket URL ──────────────────────────────────────────────────────
76
-
77
- private get _wsUrl(): string {
78
- const base = this.client.baseUrl.replace(/^http/, 'ws')
79
- return `${base}/v1/realtime/ws`
80
- }
81
-
82
- // ─── Connection management ──────────────────────────────────────────────
83
-
84
- /** Ensure the WebSocket connection is open. */
85
- connect(): void {
86
- if (this._ws && (this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CONNECTING)) {
87
- return
88
- }
89
-
90
- // Toute nouvelle tentative de connexion repart d'un état « non-intentionnel » :
91
- // évite qu'un flag `_intentionalClose` laissé à true (car le onclose de
92
- // l'ancien socket est désormais court-circuité par la garde anti-race) ne
93
- // fasse traiter une future coupure inattendue comme volontaire.
94
- this._intentionalClose = false
95
-
96
- this._setState('connecting')
97
-
98
- const token = this.client.authStore.token
99
- const params = new URLSearchParams()
100
- if (token) params.set('token', token)
101
- // Clé API en query param (les navigateurs ne permettent pas de headers custom sur WebSocket)
102
- if (this.client.apiKey) params.set('apikey', this.client.apiKey)
103
-
104
- const url = `${this._wsUrl}?${params.toString()}`
105
- // On capture `ws` dans les closures des handlers : un ancien socket dont
106
- // l'événement `onclose`/`onopen` se déclenche de façon asynchrone APRÈS
107
- // qu'un nouveau socket ait été créé ne doit pas écraser la connexion
108
- // courante (race « stale onclose »). Chaque handler se garde donc par
109
- // `if (ws !== this._ws) return`.
110
- const ws = new WebSocket(url)
111
- this._ws = ws
112
-
113
- ws.onopen = () => {
114
- // Socket périmé (superseded par un connect()/reconnexion) ⇒ on le ferme
115
- // proprement et on n'active rien dessus.
116
- if (ws !== this._ws) {
117
- try { ws.close() } catch { /* ignore */ }
118
- return
119
- }
120
- this._reconnectAttempts = 0
121
- this._setState('connected')
122
-
123
- // Démarrer le heartbeat proactif (anti-throttle navigateur)
124
- this._heartbeat.start(() => this._send(JSON.stringify({ type: 'ping' })), 25000)
125
-
126
- // Planifier le refresh proactif du token (à 80% du TTL)
127
- this._scheduleTokenRefresh()
128
-
129
- // Bind send function and re-subscribe all pending channels
130
- for (const channel of this._channels.values()) {
131
- channel._bindSend((data) => this._send(data))
132
- if (channel.isSubscribed) {
133
- channel._doSubscribe()
134
- }
135
- }
136
- }
137
-
138
- ws.onmessage = (evt) => {
139
- // Ignore les messages d'un socket périmé.
140
- if (ws !== this._ws) return
141
- try {
142
- const raw = typeof evt.data === 'string' ? evt.data : ''
143
- const msg: ServerMessage = JSON.parse(raw)
144
- this._dispatch(msg)
145
- } catch (e) {
146
- if (typeof console !== 'undefined') {
147
- console.warn('[aura-realtime] Failed to parse WS message:', evt.data, e)
148
- }
149
- }
150
- }
151
-
152
- ws.onclose = () => {
153
- // Garde anti-race : si ce socket n'est plus le socket courant (une
154
- // nouvelle connexion l'a remplacé, ou disconnect() a déjà nettoyé),
155
- // son `onclose` tardif ne doit PAS toucher l'état partagé.
156
- if (ws !== this._ws) return
157
- this._ws = null
158
- // Nouvelle connexion (après reconnexion auto) ⇒ le token devra être renvoyé.
159
- this._lastSentToken = null
160
- this._heartbeat.stop()
161
- if (this._intentionalClose) {
162
- this._setState('disconnected')
163
- this._intentionalClose = false
164
- } else if (this._autoReconnect) {
165
- this._scheduleReconnect()
166
- } else {
167
- this._setState('disconnected')
168
- }
169
- }
170
-
171
- ws.onerror = (evt) => {
172
- if (ws !== this._ws) return
173
- if (typeof console !== 'undefined') {
174
- console.warn('[aura-realtime] WebSocket error', evt)
175
- }
176
- }
177
- }
178
-
179
- /** Send raw data through the WebSocket. */
180
- private _send(data: string): void {
181
- if (this._ws && this._ws.readyState === WebSocket.OPEN) {
182
- this._ws.send(data)
183
- }
184
- }
185
-
186
- /** Refresh the JWT token on an existing connection (no disconnect). */
187
- refreshToken(token: string): void {
188
- if (token === this._lastSentToken) {
189
- this._scheduleTokenRefresh()
190
- return
191
- }
192
- this._lastSentToken = token
193
- this._send(JSON.stringify({ type: 'access_token', token }))
194
- this._scheduleTokenRefresh()
195
- }
196
-
197
- /**
198
- * Définit/rafraîchit explicitement le token utilisé pour l'autorisation des
199
- * canaux privés et broadcast (parité `@supabase/realtime-js` `setAuth`).
200
- *
201
- * - Avec `token` : utilise ce token. Sinon, reprend `client.authStore.token`.
202
- * - Propage via le pipeline existant `refreshToken` (push
203
- * `{ type:'access_token', token }` sur une connexion ouverte, dédupliqué via
204
- * `_lastSentToken`). N'ouvre ni ne ferme aucune connexion WS.
205
- * - Hors-ligne : `connect()` lit `client.authStore.token` au moment de l'open,
206
- * donc `setAuth()` doit être appelé avec un token présent dans le store (ou
207
- * après authentification) pour prendre effet sur une future connexion.
208
- *
209
- * À appeler AVANT de s'abonner à un canal privé (`channel(name,{private:true})`).
210
- *
211
- * @example
212
- * ```ts
213
- * aura.realtime.setAuth(session.access_token)
214
- * const ch = aura.realtime.channel('secret', { private: true }).subscribe()
215
- * ```
216
- */
217
- setAuth(token?: string): void {
218
- const resolved = token ?? this.client.authStore.token
219
- if (resolved == null) return
220
- this.refreshToken(resolved)
221
- }
222
-
223
- /**
224
- * Envoie un broadcast par HTTP (REST), sans connexion WebSocket ouverte
225
- * (parité `@supabase/realtime-js`). Utile côté serveur/edge.
226
- *
227
- * POST `/v1/realtime/{projectId}/broadcast` avec le contrat du handler backend
228
- * (`broadcast.rs`) : `{ channel, payload }`, `channel` préfixé par le projet,
229
- * `event` replié dans `payload` (aligné sur le broadcast WS).
230
- *
231
- * @param channel Nom du canal (préfixé projet automatiquement si absent).
232
- * @param event Nom de l'événement broadcast.
233
- * @param payload Données de l'événement.
234
- * @returns `AuraResponse<null>` — `error` non nul en cas de 4xx/rate-limit.
235
- *
236
- * @example
237
- * ```ts
238
- * await aura.realtime.httpBroadcast('room-1', 'cursor', { x: 10, y: 20 })
239
- * ```
240
- */
241
- httpBroadcast(
242
- channel: string,
243
- event: string,
244
- payload: Record<string, unknown> = {},
245
- ): Promise<AuraResponse<null>> {
246
- const prefix = `${this.client.projectId}:`
247
- const fullChannel = channel.startsWith(prefix) ? channel : `${prefix}${channel}`
248
- const body = {
249
- channel: fullChannel,
250
- payload: { event, ...payload },
251
- }
252
- return fetchWithAuth<null>(
253
- `${this.client.baseUrl}/v1/realtime/${this.client.projectId}/broadcast`,
254
- { method: 'POST', body },
255
- this.client,
256
- )
257
- }
258
-
259
- /**
260
- * Schedule proactive token refresh at 80% of the JWT TTL.
261
- * Prevents token expiration during an active WebSocket session.
262
- */
263
- private _scheduleTokenRefresh(): void {
264
- if (this._tokenRefreshTimer) {
265
- clearTimeout(this._tokenRefreshTimer)
266
- this._tokenRefreshTimer = null
267
- }
268
- const token = this.client.authStore.token
269
- if (!token) return
270
- try {
271
- const payload = JSON.parse(atob(token.split('.')[1]))
272
- const exp = payload.exp * 1000
273
- const ttl = exp - Date.now()
274
- if (ttl <= 0) return
275
- // Refresh at 80% of the remaining TTL
276
- const refreshIn = ttl * 0.8
277
- this._tokenRefreshTimer = setTimeout(async () => {
278
- this._tokenRefreshTimer = null
279
- try {
280
- const res = await this.client.auth.refreshSession()
281
- if (res.data) {
282
- this.refreshToken(res.data.access_token)
283
- }
284
- } catch {
285
- // Refresh failed — the server will eventually disconnect on token expiry
286
- }
287
- }, refreshIn)
288
- } catch {
289
- // JWT decode failed — skip proactive refresh
290
- }
291
- }
292
-
293
- /** Schedule a reconnection attempt with exponential backoff + jitter. */
294
- private _scheduleReconnect(): void {
295
- // Clear any existing timer to avoid double-reconnect
296
- if (this._reconnectTimer) {
297
- clearTimeout(this._reconnectTimer)
298
- this._reconnectTimer = null
299
- }
300
-
301
- if (this._reconnectAttempts >= this._maxReconnectAttempts) {
302
- this._setState('disconnected')
303
- return
304
- }
305
-
306
- this._reconnectAttempts++
307
- this._setState('reconnecting')
308
-
309
- const idx = Math.min(this._reconnectAttempts - 1, RealtimeService.BACKOFF_MS.length - 1)
310
- const baseDelay = RealtimeService.BACKOFF_MS[idx]
311
- const jitter = Math.random() * baseDelay * 0.25
312
- const delay = baseDelay + jitter
313
-
314
- this._reconnectTimer = setTimeout(() => {
315
- this._reconnectTimer = null
316
- this.connect()
317
- }, delay)
318
- }
319
-
320
- /** Enable/disable auto-reconnect (enabled by default). */
321
- setAutoReconnect(enabled: boolean, maxAttempts?: number): void {
322
- this._autoReconnect = enabled
323
- if (maxAttempts !== undefined) this._maxReconnectAttempts = maxAttempts
324
- }
325
-
326
- /** Disconnect the WebSocket and clean up all channels. */
327
- disconnect(): void {
328
- this._intentionalClose = true
329
- this._heartbeat.stop()
330
- if (this._tokenRefreshTimer) {
331
- clearTimeout(this._tokenRefreshTimer)
332
- this._tokenRefreshTimer = null
333
- }
334
- if (this._reconnectTimer) {
335
- clearTimeout(this._reconnectTimer)
336
- this._reconnectTimer = null
337
- }
338
- this._reconnectAttempts = 0
339
- for (const channel of this._channels.values()) {
340
- channel.unsubscribe()
341
- }
342
- this._channels.clear()
343
- if (this._ws) {
344
- this._ws.close()
345
- this._ws = null
346
- }
347
- this._setState('disconnected')
348
- this._lastSentToken = null
349
- }
350
-
351
- // ─── Channel factory ────────────────────────────────────────────────────
352
-
353
- /**
354
- * Create or retrieve a channel by name.
355
- * Returns an `AuraChannel` with chainable `.on()` / `.subscribe()` API.
356
- *
357
- * @example
358
- * ```ts
359
- * const channel = aura.realtime.channel('room-1')
360
- * ```
361
- */
362
- channel(name: string, options?: ChannelConfig): AuraChannel {
363
- const existing = this._channels.get(name)
364
- if (existing) return existing
365
-
366
- const ch = new AuraChannel(name, this.client.projectId, options)
367
- this._channels.set(name, ch)
368
-
369
- // Établit la connexion WS si nécessaire (idempotent — connect() vérifie
370
- // déjà readyState avant d'ouvrir un nouveau socket).
371
- this.connect()
372
-
373
- // If already connected, bind immediately
374
- if (this._ws && this._ws.readyState === WebSocket.OPEN) {
375
- ch._bindSend((data) => this._send(data))
376
- }
377
-
378
- return ch
379
- }
380
-
381
- /** Remove a channel. */
382
- removeChannel(name: string): void {
383
- const ch = this._channels.get(name)
384
- if (ch) {
385
- ch.unsubscribe()
386
- this._channels.delete(name)
387
- }
388
- }
389
-
390
- /** List active channels. */
391
- getChannels(): AuraChannel[] {
392
- return Array.from(this._channels.values())
393
- }
394
-
395
- /** Remove all channels and subscriptions. */
396
- removeAllChannels(): void {
397
- for (const ch of this._channels.values()) {
398
- ch.unsubscribe()
399
- }
400
- this._channels.clear()
401
- }
402
-
403
- // ─── Message dispatch ───────────────────────────────────────────────────
404
-
405
- /** Route incoming server messages to the correct channel. */
406
- private _dispatch(msg: ServerMessage): void {
407
- // Extract channel name — present in subscribed, presence_state, OR topic in event messages
408
- // ServerMessage::Event uses 'topic' (serde flatten de RealtimeEvent), pas 'channel'
409
- const channelFull = 'channel' in msg
410
- ? (msg as { channel?: string }).channel
411
- : 'topic' in msg
412
- ? (msg as { topic?: string }).topic
413
- : undefined
414
-
415
- if (channelFull) {
416
- // Strip project prefix to find channel
417
- const prefix = `${this.client.projectId}:`
418
- const name = channelFull.startsWith(prefix) ? channelFull.slice(prefix.length) : channelFull
419
- const ch = this._channels.get(name)
420
- if (ch) {
421
- ch._onMessage(msg)
422
- return
423
- }
424
-
425
- // Try matching by full name (for CDC channels like proj:db:schema.table)
426
- // The channel might have been created with a short name but CDC sends full topic
427
- for (const [, channel] of this._channels) {
428
- if (channel.fullName === channelFull || channelFull.startsWith(channel.fullName + ':')) {
429
- channel._onMessage(msg)
430
- return
431
- }
432
- }
433
- }
434
-
435
- // Messages without channel (pong, token_refreshed, global errors)
436
- if (msg.type === 'token_refreshed') {
437
- // Global token refresh ACK — no action needed
438
- return
439
- }
440
-
441
- if (msg.type === 'pong') {
442
- // Keepalive — no action needed
443
- return
444
- }
445
-
446
- if (msg.type === 'broadcast_ack') {
447
- // Router l'ACK vers le SEUL channel émetteur. Le `ref_id` est préfixé par
448
- // le `fullName` du channel (`{fullName}:r{n}`, cf. AuraChannel._nextRef) :
449
- // on récupère le suffixe `:r{n}` via lastIndexOf (robuste à un nom de
450
- // channel contenant lui-même « :r ») et on résout par `fullName`.
451
- const ack = msg as BroadcastAckMessage
452
- const refId = ack.ref_id ?? ack.message_id
453
- if (refId) {
454
- const marker = refId.lastIndexOf(':r')
455
- if (marker > 0) {
456
- const fullName = refId.slice(0, marker)
457
- for (const ch of this._channels.values()) {
458
- if (ch.fullName === fullName) {
459
- ch._onMessage(msg)
460
- return
461
- }
462
- }
463
- }
464
- }
465
- // Fallback prudent : `ref_id` non résoluble (préfixe inattendu ou
466
- // `message_id` brut sans « :r ») → dispatch à tous pour ne pas perdre
467
- // l'ACK. Chaque channel n'agit que sur un `ref_id`/`message_id` présent
468
- // dans ses `_pendingAcks`, donc aucun faux positif.
469
- for (const ch of this._channels.values()) {
470
- ch._onMessage(msg)
471
- }
472
- return
473
- }
474
-
475
- // Broadcast to all channels for unroutable messages
476
- if (msg.type === 'error') {
477
- for (const ch of this._channels.values()) {
478
- ch._onMessage(msg)
479
- }
480
- }
481
- }
482
-
483
- // ─── Backward compatibility ─────────────────────────────────────────────
484
-
485
- /**
486
- * Simple callback-based subscription (legacy API).
487
- * Prefer using `.channel().on().subscribe()` instead.
488
- *
489
- * @deprecated Use `channel().on().subscribe()` instead.
490
- */
491
- on<T = Record<string, unknown>>(
492
- channelName: string,
493
- callback: (event: any) => void,
494
- ): { unsubscribe: () => void } {
495
- this.connect()
496
-
497
- const ch = this.channel(channelName)
498
- ch.on('postgres_changes', { event: '*' }, callback)
499
- ch.subscribe()
500
-
501
- return {
502
- unsubscribe: () => this.removeChannel(channelName),
503
- }
504
- }
505
- }