@aurabase/aurabase-js 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/.changeset/config.json +11 -0
  2. package/CHANGELOG.md +30 -0
  3. package/LICENSE +21 -0
  4. package/README.md +833 -0
  5. package/package.json +90 -0
  6. package/packages/ai/LICENSE +21 -0
  7. package/packages/ai/README.md +106 -0
  8. package/packages/ai/dist/index.cjs +229 -0
  9. package/packages/ai/dist/index.d.cts +139 -0
  10. package/packages/ai/dist/index.d.ts +139 -0
  11. package/packages/ai/dist/index.js +226 -0
  12. package/packages/ai/node_modules/.bin/esbuild +19 -0
  13. package/packages/ai/node_modules/.bin/tsc +22 -0
  14. package/packages/ai/node_modules/.bin/tsserver +22 -0
  15. package/packages/ai/node_modules/.bin/tsup +22 -0
  16. package/packages/ai/node_modules/.bin/tsup-node +22 -0
  17. package/packages/ai/node_modules/.bin/vite +22 -0
  18. package/packages/ai/node_modules/.bin/vitest +22 -0
  19. package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  20. package/packages/ai/package.json +61 -0
  21. package/packages/ai/src/AiService.ts +226 -0
  22. package/packages/ai/src/index.ts +18 -0
  23. package/packages/ai/src/sse.ts +141 -0
  24. package/packages/ai/src/types.ts +30 -0
  25. package/packages/ai/tests/chat-stream.test.ts +151 -0
  26. package/packages/ai/tests/rag-namespaces.test.ts +54 -0
  27. package/packages/ai/tests/sse.test.ts +69 -0
  28. package/packages/ai/tsconfig.json +5 -0
  29. package/packages/ai/tsup.config.ts +11 -0
  30. package/packages/aurabase/LICENSE +21 -0
  31. package/packages/aurabase/README.md +64 -0
  32. package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts +77 -0
  33. package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts +77 -0
  34. package/packages/aurabase/dist/index.cjs +237 -0
  35. package/packages/aurabase/dist/index.d.cts +46 -0
  36. package/packages/aurabase/dist/index.d.ts +46 -0
  37. package/packages/aurabase/dist/index.js +177 -0
  38. package/packages/aurabase/dist/ssr.cjs +224 -0
  39. package/packages/aurabase/dist/ssr.d.cts +65 -0
  40. package/packages/aurabase/dist/ssr.d.ts +65 -0
  41. package/packages/aurabase/dist/ssr.js +214 -0
  42. package/packages/aurabase/node_modules/.bin/esbuild +19 -0
  43. package/packages/aurabase/node_modules/.bin/tsc +22 -0
  44. package/packages/aurabase/node_modules/.bin/tsserver +22 -0
  45. package/packages/aurabase/node_modules/.bin/tsup +22 -0
  46. package/packages/aurabase/node_modules/.bin/tsup-node +22 -0
  47. package/packages/aurabase/node_modules/.bin/vite +22 -0
  48. package/packages/aurabase/node_modules/.bin/vitest +22 -0
  49. package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  50. package/packages/aurabase/package.json +80 -0
  51. package/packages/aurabase/src/AuraClient.ts +237 -0
  52. package/packages/aurabase/src/index.ts +152 -0
  53. package/packages/aurabase/src/ssr.ts +156 -0
  54. package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +82 -0
  55. package/packages/aurabase/tests/ai.test.ts +111 -0
  56. package/packages/aurabase/tests/auth-admin.test.ts +515 -0
  57. package/packages/aurabase/tests/auth-e2e.test.ts +135 -0
  58. package/packages/aurabase/tests/auth-hardening.test.ts +206 -0
  59. package/packages/aurabase/tests/auth.test.ts +947 -0
  60. package/packages/aurabase/tests/database.test.ts +230 -0
  61. package/packages/aurabase/tests/db-types-e2e.test.ts +217 -0
  62. package/packages/aurabase/tests/dispose.test.ts +23 -0
  63. package/packages/aurabase/tests/foreign-token.e2e.test.ts +129 -0
  64. package/packages/aurabase/tests/functions.test.ts +107 -0
  65. package/packages/aurabase/tests/integration.test.ts +413 -0
  66. package/packages/aurabase/tests/live-production-validation.js +241 -0
  67. package/packages/aurabase/tests/live-sdk-production-validation.js +293 -0
  68. package/packages/aurabase/tests/mocks.ts +139 -0
  69. package/packages/aurabase/tests/notifications.test.ts +314 -0
  70. package/packages/aurabase/tests/observability-backup.test.ts +186 -0
  71. package/packages/aurabase/tests/observability-hook.test.ts +130 -0
  72. package/packages/aurabase/tests/production-robustness.test.ts +268 -0
  73. package/packages/aurabase/tests/public-api-surface.test.ts +53 -0
  74. package/packages/aurabase/tests/query-builder.test.ts +329 -0
  75. package/packages/aurabase/tests/readme-smoke-e2e.test.ts +231 -0
  76. package/packages/aurabase/tests/realtime-filter-e2e.test.ts +344 -0
  77. package/packages/aurabase/tests/refresh-locks.test.ts +210 -0
  78. package/packages/aurabase/tests/retry-option.test.ts +17 -0
  79. package/packages/aurabase/tests/rpc-e2e.test.ts +254 -0
  80. package/packages/aurabase/tests/rs256-local-e2e.test.ts +114 -0
  81. package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +235 -0
  82. package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +124 -0
  83. package/packages/aurabase/tests/service-role-guard.test.ts +99 -0
  84. package/packages/aurabase/tests/ssr.test.ts +91 -0
  85. package/packages/aurabase/tests/storage-backend-e2e.test.ts +481 -0
  86. package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +104 -0
  87. package/packages/aurabase/tests/storage-transform-e2e.test.ts +684 -0
  88. package/packages/aurabase/tests/storage.test.ts +55 -0
  89. package/packages/aurabase/tests/types/client-generic.test-d.ts +79 -0
  90. package/packages/aurabase/tests/types/docs-snippets.test-d.ts +571 -0
  91. package/packages/aurabase/tests/types/error-code.test-d.ts +53 -0
  92. package/packages/aurabase/tsconfig.json +5 -0
  93. package/packages/aurabase/tsconfig.test-d.json +5 -0
  94. package/packages/aurabase/tsup.config.ts +11 -0
  95. package/packages/aurabase/vitest.config.ts +16 -0
  96. package/packages/aurabase/vitest.e2e.config.ts +9 -0
  97. package/packages/auth/LICENSE +21 -0
  98. package/packages/auth/README.md +215 -0
  99. package/packages/auth/dist/index.cjs +1175 -0
  100. package/packages/auth/dist/index.d.cts +397 -0
  101. package/packages/auth/dist/index.d.ts +397 -0
  102. package/packages/auth/dist/index.js +1170 -0
  103. package/packages/auth/node_modules/.bin/esbuild +19 -0
  104. package/packages/auth/node_modules/.bin/tsc +22 -0
  105. package/packages/auth/node_modules/.bin/tsserver +22 -0
  106. package/packages/auth/node_modules/.bin/tsup +22 -0
  107. package/packages/auth/node_modules/.bin/tsup-node +22 -0
  108. package/packages/auth/node_modules/.bin/vite +22 -0
  109. package/packages/auth/node_modules/.bin/vitest +22 -0
  110. package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  111. package/packages/auth/package.json +61 -0
  112. package/packages/auth/src/AuthAdminService.ts +149 -0
  113. package/packages/auth/src/AuthService.ts +1166 -0
  114. package/packages/auth/src/SessionManager.ts +92 -0
  115. package/packages/auth/src/index.ts +17 -0
  116. package/packages/auth/src/jwks.ts +337 -0
  117. package/packages/auth/src/pkce.ts +27 -0
  118. package/packages/auth/src/types.ts +89 -0
  119. package/packages/auth/tests/auth-lifecycle.test.ts +149 -0
  120. package/packages/auth/tests/detect-session.test.ts +361 -0
  121. package/packages/auth/tests/getclaims.test.ts +780 -0
  122. package/packages/auth/tests/is-mfa-challenge.test.ts +27 -0
  123. package/packages/auth/tests/password-recovery.test.ts +124 -0
  124. package/packages/auth/tests/pkce.test.ts +192 -0
  125. package/packages/auth/tests/session-manager.test.ts +103 -0
  126. package/packages/auth/tests/set-session.test.ts +139 -0
  127. package/packages/auth/tests/verify-otp.test.ts +153 -0
  128. package/packages/auth/tsconfig.json +5 -0
  129. package/packages/auth/tsup.config.ts +11 -0
  130. package/packages/core/LICENSE +21 -0
  131. package/packages/core/README.md +132 -0
  132. package/packages/core/dist/index.cjs +695 -0
  133. package/packages/core/dist/index.d.cts +1137 -0
  134. package/packages/core/dist/index.d.ts +1137 -0
  135. package/packages/core/dist/index.js +673 -0
  136. package/packages/core/node_modules/.bin/esbuild +22 -0
  137. package/packages/core/node_modules/.bin/tsc +22 -0
  138. package/packages/core/node_modules/.bin/tsserver +22 -0
  139. package/packages/core/node_modules/.bin/tsup +22 -0
  140. package/packages/core/node_modules/.bin/tsup-node +22 -0
  141. package/packages/core/node_modules/.bin/vite +22 -0
  142. package/packages/core/node_modules/.bin/vitest +22 -0
  143. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  144. package/packages/core/package.json +57 -0
  145. package/packages/core/src/AuraError.ts +89 -0
  146. package/packages/core/src/BaseService.ts +44 -0
  147. package/packages/core/src/client.ts +34 -0
  148. package/packages/core/src/cookie-utils.ts +42 -0
  149. package/packages/core/src/fetch.ts +435 -0
  150. package/packages/core/src/index.ts +22 -0
  151. package/packages/core/src/retry.ts +63 -0
  152. package/packages/core/src/serialize.ts +6 -0
  153. package/packages/core/src/stores/BaseAuthStore.ts +164 -0
  154. package/packages/core/src/stores/LocalAuthStore.ts +199 -0
  155. package/packages/core/src/types.ts +1042 -0
  156. package/packages/core/tests/auth-store.test.ts +300 -0
  157. package/packages/core/tests/error-code.test.ts +61 -0
  158. package/packages/core/tests/error.test.ts +24 -0
  159. package/packages/core/tests/fetch.test.ts +286 -0
  160. package/packages/core/tests/mocks.ts +139 -0
  161. package/packages/core/tests/observability.test.ts +86 -0
  162. package/packages/core/tests/refresh-hardening.test.ts +164 -0
  163. package/packages/core/tests/refresh-observability.test.ts +111 -0
  164. package/packages/core/tests/retry.test.ts +106 -0
  165. package/packages/core/tests/serialize.test.ts +12 -0
  166. package/packages/core/tests/timeout.test.ts +79 -0
  167. package/packages/core/tsconfig.json +5 -0
  168. package/packages/core/tsup.config.ts +11 -0
  169. package/packages/db/LICENSE +21 -0
  170. package/packages/db/README.md +152 -0
  171. package/packages/db/VENDORED.md +93 -0
  172. package/packages/db/dist/index.cjs +544 -0
  173. package/packages/db/dist/index.d.cts +1111 -0
  174. package/packages/db/dist/index.d.ts +1111 -0
  175. package/packages/db/dist/index.js +541 -0
  176. package/packages/db/node_modules/.bin/esbuild +19 -0
  177. package/packages/db/node_modules/.bin/tsc +22 -0
  178. package/packages/db/node_modules/.bin/tsserver +22 -0
  179. package/packages/db/node_modules/.bin/tsup +22 -0
  180. package/packages/db/node_modules/.bin/tsup-node +22 -0
  181. package/packages/db/node_modules/.bin/vite +22 -0
  182. package/packages/db/node_modules/.bin/vitest +22 -0
  183. package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  184. package/packages/db/package.json +62 -0
  185. package/packages/db/src/DatabaseService.ts +170 -0
  186. package/packages/db/src/QueryBuilder.ts +830 -0
  187. package/packages/db/src/index.ts +2 -0
  188. package/packages/db/src/pgrst-types/common/common.ts +86 -0
  189. package/packages/db/src/pgrst-types/common/rpc.ts +151 -0
  190. package/packages/db/src/pgrst-types/feature-flags.ts +20 -0
  191. package/packages/db/src/pgrst-types/types.ts +134 -0
  192. package/packages/db/src/select-query-parser/parser.ts +479 -0
  193. package/packages/db/src/select-query-parser/result.ts +553 -0
  194. package/packages/db/src/select-query-parser/types.ts +132 -0
  195. package/packages/db/src/select-query-parser/utils.ts +707 -0
  196. package/packages/db/tests/filters.test.ts +81 -0
  197. package/packages/db/tests/mutations-filtered.e2e.test.ts +132 -0
  198. package/packages/db/tests/mutations-filtered.test.ts +188 -0
  199. package/packages/db/tests/order.test.ts +45 -0
  200. package/packages/db/tests/rpc.test.ts +41 -0
  201. package/packages/db/tests/scale/build-large-schema.ts +94 -0
  202. package/packages/db/tests/scale/scale-compile.test.ts +167 -0
  203. package/packages/db/tests/schema.test.ts +77 -0
  204. package/packages/db/tests/single-accept.e2e.test.ts +123 -0
  205. package/packages/db/tests/single.test.ts +211 -0
  206. package/packages/db/tests/types/filters.test-d.ts +52 -0
  207. package/packages/db/tests/types/fixtures.ts +40 -0
  208. package/packages/db/tests/types/from-views.test-d.ts +18 -0
  209. package/packages/db/tests/types/mutations.test-d.ts +23 -0
  210. package/packages/db/tests/types/rpc.test-d.ts +60 -0
  211. package/packages/db/tests/types/schema-shape.test-d.ts +8 -0
  212. package/packages/db/tests/types/select-inference.test-d.ts +38 -0
  213. package/packages/db/tests/vendored/fixtures.ts +83 -0
  214. package/packages/db/tests/vendored/get-result.test-d.ts +75 -0
  215. package/packages/db/tsconfig.json +5 -0
  216. package/packages/db/tsconfig.test-d.json +5 -0
  217. package/packages/db/tsup.config.ts +11 -0
  218. package/packages/db/vitest.config.ts +8 -0
  219. package/packages/functions/LICENSE +21 -0
  220. package/packages/functions/README.md +96 -0
  221. package/packages/functions/dist/index.cjs +173 -0
  222. package/packages/functions/dist/index.d.cts +81 -0
  223. package/packages/functions/dist/index.d.ts +81 -0
  224. package/packages/functions/dist/index.js +168 -0
  225. package/packages/functions/node_modules/.bin/esbuild +19 -0
  226. package/packages/functions/node_modules/.bin/tsc +22 -0
  227. package/packages/functions/node_modules/.bin/tsserver +22 -0
  228. package/packages/functions/node_modules/.bin/tsup +22 -0
  229. package/packages/functions/node_modules/.bin/tsup-node +22 -0
  230. package/packages/functions/node_modules/.bin/vite +22 -0
  231. package/packages/functions/node_modules/.bin/vitest +22 -0
  232. package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  233. package/packages/functions/package.json +60 -0
  234. package/packages/functions/src/FunctionsService.ts +188 -0
  235. package/packages/functions/src/errors.ts +70 -0
  236. package/packages/functions/src/index.ts +6 -0
  237. package/packages/functions/tests/invoke.test.ts +97 -0
  238. package/packages/functions/tests/response-type-errors.test.ts +135 -0
  239. package/packages/functions/tsconfig.json +5 -0
  240. package/packages/functions/tsup.config.ts +11 -0
  241. package/packages/gen/LICENSE +21 -0
  242. package/packages/gen/README.md +84 -0
  243. package/packages/gen/dist/cli.cjs +334 -0
  244. package/packages/gen/dist/cli.d.cts +1 -0
  245. package/packages/gen/dist/cli.d.ts +1 -0
  246. package/packages/gen/dist/cli.js +332 -0
  247. package/packages/gen/dist/index.cjs +257 -0
  248. package/packages/gen/dist/index.d.cts +12 -0
  249. package/packages/gen/dist/index.d.ts +12 -0
  250. package/packages/gen/dist/index.js +255 -0
  251. package/packages/gen/node_modules/.bin/esbuild +19 -0
  252. package/packages/gen/node_modules/.bin/tsc +22 -0
  253. package/packages/gen/node_modules/.bin/tsserver +22 -0
  254. package/packages/gen/node_modules/.bin/tsup +22 -0
  255. package/packages/gen/node_modules/.bin/tsup-node +22 -0
  256. package/packages/gen/node_modules/.bin/vite +22 -0
  257. package/packages/gen/node_modules/.bin/vitest +22 -0
  258. package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  259. package/packages/gen/package.json +66 -0
  260. package/packages/gen/src/cli.ts +67 -0
  261. package/packages/gen/src/generate.ts +0 -0
  262. package/packages/gen/src/index.ts +2 -0
  263. package/packages/gen/tests/compile.test.ts +213 -0
  264. package/packages/gen/tests/e2e.test.ts +130 -0
  265. package/packages/gen/tests/generate.test.ts +396 -0
  266. package/packages/gen/tsconfig.json +5 -0
  267. package/packages/gen/tsup.config.ts +11 -0
  268. package/packages/notifications/LICENSE +21 -0
  269. package/packages/notifications/README.md +134 -0
  270. package/packages/notifications/dist/index.cjs +128 -0
  271. package/packages/notifications/dist/index.d.cts +81 -0
  272. package/packages/notifications/dist/index.d.ts +81 -0
  273. package/packages/notifications/dist/index.js +125 -0
  274. package/packages/notifications/node_modules/.bin/esbuild +19 -0
  275. package/packages/notifications/node_modules/.bin/tsc +22 -0
  276. package/packages/notifications/node_modules/.bin/tsserver +22 -0
  277. package/packages/notifications/node_modules/.bin/tsup +22 -0
  278. package/packages/notifications/node_modules/.bin/tsup-node +22 -0
  279. package/packages/notifications/node_modules/.bin/vite +22 -0
  280. package/packages/notifications/node_modules/.bin/vitest +22 -0
  281. package/packages/notifications/package.json +60 -0
  282. package/packages/notifications/src/NotificationsService.ts +230 -0
  283. package/packages/notifications/src/index.ts +19 -0
  284. package/packages/notifications/tsconfig.json +5 -0
  285. package/packages/notifications/tsup.config.ts +11 -0
  286. package/packages/realtime/LICENSE +21 -0
  287. package/packages/realtime/README.md +183 -0
  288. package/packages/realtime/dist/index.cjs +977 -0
  289. package/packages/realtime/dist/index.d.cts +259 -0
  290. package/packages/realtime/dist/index.d.ts +259 -0
  291. package/packages/realtime/dist/index.js +973 -0
  292. package/packages/realtime/node_modules/.bin/esbuild +19 -0
  293. package/packages/realtime/node_modules/.bin/tsc +22 -0
  294. package/packages/realtime/node_modules/.bin/tsserver +22 -0
  295. package/packages/realtime/node_modules/.bin/tsup +22 -0
  296. package/packages/realtime/node_modules/.bin/tsup-node +22 -0
  297. package/packages/realtime/node_modules/.bin/vite +22 -0
  298. package/packages/realtime/node_modules/.bin/vitest +22 -0
  299. package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  300. package/packages/realtime/package.json +60 -0
  301. package/packages/realtime/src/AuraChannel.ts +485 -0
  302. package/packages/realtime/src/HeartbeatManager.ts +73 -0
  303. package/packages/realtime/src/RealtimeService.ts +505 -0
  304. package/packages/realtime/src/columnFilter.ts +301 -0
  305. package/packages/realtime/src/index.ts +4 -0
  306. package/packages/realtime/tests/ack-routing.test.ts +81 -0
  307. package/packages/realtime/tests/column-filter.test.ts +144 -0
  308. package/packages/realtime/tests/dispatch.test.ts +191 -0
  309. package/packages/realtime/tests/heartbeat.test.ts +67 -0
  310. package/packages/realtime/tests/http-broadcast.test.ts +68 -0
  311. package/packages/realtime/tests/realtime-token.test.ts +43 -0
  312. package/packages/realtime/tests/reconnect-race.test.ts +149 -0
  313. package/packages/realtime/tests/ref-id.test.ts +34 -0
  314. package/packages/realtime/tests/set-auth.test.ts +70 -0
  315. package/packages/realtime/tsconfig.json +5 -0
  316. package/packages/realtime/tsup.config.ts +11 -0
  317. package/packages/storage/LICENSE +21 -0
  318. package/packages/storage/README.md +141 -0
  319. package/packages/storage/dist/index.cjs +333 -0
  320. package/packages/storage/dist/index.d.cts +185 -0
  321. package/packages/storage/dist/index.d.ts +185 -0
  322. package/packages/storage/dist/index.js +329 -0
  323. package/packages/storage/node_modules/.bin/esbuild +19 -0
  324. package/packages/storage/node_modules/.bin/tsc +22 -0
  325. package/packages/storage/node_modules/.bin/tsserver +22 -0
  326. package/packages/storage/node_modules/.bin/tsup +22 -0
  327. package/packages/storage/node_modules/.bin/tsup-node +22 -0
  328. package/packages/storage/node_modules/.bin/vite +22 -0
  329. package/packages/storage/node_modules/.bin/vitest +22 -0
  330. package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  331. package/packages/storage/package.json +60 -0
  332. package/packages/storage/src/DownloadBuilder.ts +60 -0
  333. package/packages/storage/src/StorageService.ts +323 -0
  334. package/packages/storage/src/index.ts +2 -0
  335. package/packages/storage/src/path.ts +35 -0
  336. package/packages/storage/src/transform.ts +15 -0
  337. package/packages/storage/tests/download.test.ts +64 -0
  338. package/packages/storage/tests/parity.test.ts +154 -0
  339. package/packages/storage/tests/path.test.ts +87 -0
  340. package/packages/storage/tests/transform.test.ts +79 -0
  341. package/packages/storage/tsconfig.json +5 -0
  342. package/packages/storage/tsup.config.ts +11 -0
  343. package/pnpm-workspace.yaml +24 -0
  344. package/tsconfig.base.json +15 -0
@@ -0,0 +1,485 @@
1
+ import type {
2
+ RealtimeEvent,
3
+ RealtimeListenType,
4
+ PostgresChangesFilter,
5
+ BroadcastFilter,
6
+ PresenceFilter,
7
+ SubscribeCallback,
8
+ BroadcastPayload,
9
+ PresenceState,
10
+ PresenceInfo,
11
+ ServerMessage,
12
+ ServerSubscribedMsg,
13
+ ServerEventMsg,
14
+ ServerPresenceStateMsg,
15
+ ServerErrorMsg,
16
+ ChannelConfig,
17
+ } from '@aurabase/core'
18
+ import { matchesColumnFilter } from './columnFilter'
19
+
20
+ // ─── Internal types ─────────────────────────────────────────────────────────
21
+
22
+ type ListenerCallback = (payload: RealtimeEvent | Record<string, unknown>) => void
23
+
24
+ type ListenerEntry = {
25
+ type: RealtimeListenType
26
+ filter: PostgresChangesFilter | BroadcastFilter | PresenceFilter
27
+ callback: ListenerCallback
28
+ }
29
+
30
+ type SendFn = (data: string) => void
31
+
32
+ // ─── AuraChannel ────────────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * A realtime channel with Supabase-compatible chainable API.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const channel = aura.channel('room-1')
40
+ * .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {
41
+ * console.log('New message:', payload)
42
+ * })
43
+ * .on('broadcast', { event: 'cursor-pos' }, (payload) => {
44
+ * console.log('Cursor:', payload)
45
+ * })
46
+ * .on('presence', { event: 'sync' }, () => {
47
+ * console.log('Presence synced:', channel.presenceState())
48
+ * })
49
+ * .subscribe((status) => {
50
+ * if (status === 'SUBSCRIBED') console.log('Connected!')
51
+ * })
52
+ * ```
53
+ */
54
+ export class AuraChannel {
55
+ readonly name: string
56
+ private readonly _projectId: string
57
+ private readonly _listeners: ListenerEntry[] = []
58
+ private _subscribed = false
59
+ private _sendFn: SendFn | null = null
60
+ private _subscribeCallback: SubscribeCallback | null = null
61
+ private _presenceState: PresenceState = {}
62
+ private _isPrivate = false
63
+ private _broadcastAck = false
64
+ private _broadcastSelf = false
65
+ private _presenceKey = ''
66
+ /** ID de subscription retourné par le serveur (utilisé pour le filtre RLS allowed_sub_ids) */
67
+ private _subscriptionId: string | null = null
68
+ /** Compteur pour générer des ref_id uniques par connexion */
69
+ private _refCounter = 0
70
+ /** Dernier event ID recu — utilise pour since_id au reconnect */
71
+ private _lastEventId: string | null = null
72
+ /** Pending ACK callbacks : ref_id → resolve */
73
+ private _pendingAcks = new Map<string, () => void>()
74
+
75
+ constructor(name: string, projectId: string, options?: ChannelConfig) {
76
+ this.name = name
77
+ this._projectId = projectId
78
+ this._isPrivate = options?.private ?? false
79
+ this._broadcastAck = options?.broadcast?.ack ?? false
80
+ this._broadcastSelf = options?.broadcast?.self ?? false
81
+ this._presenceKey = options?.config?.presence?.key ?? ''
82
+ }
83
+
84
+ /** Génère un ref_id unique pour la corrélation request/response. */
85
+ private _nextRef(): string {
86
+ return `${this.fullName}:r${++this._refCounter}`
87
+ }
88
+
89
+ /** Full channel name with project prefix (for server protocol) */
90
+ get fullName(): string {
91
+ return `${this._projectId}:${this.name}`
92
+ }
93
+
94
+ // ─── Chainable .on() ────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Register a listener for a specific event type.
98
+ * Returns `this` for chaining.
99
+ */
100
+ on(
101
+ type: 'postgres_changes',
102
+ filter: PostgresChangesFilter,
103
+ callback: (payload: RealtimeEvent) => void,
104
+ ): this
105
+ on(
106
+ type: 'broadcast',
107
+ filter: BroadcastFilter,
108
+ callback: (payload: RealtimeEvent) => void,
109
+ ): this
110
+ on(
111
+ type: 'presence',
112
+ filter: PresenceFilter,
113
+ callback: (payload: any) => void,
114
+ ): this
115
+ on(
116
+ type: RealtimeListenType,
117
+ filter: PostgresChangesFilter | BroadcastFilter | PresenceFilter,
118
+ callback: (payload: any) => void,
119
+ ): this {
120
+ this._listeners.push({ type, filter, callback })
121
+ return this
122
+ }
123
+
124
+ // ─── Subscribe ──────────────────────────────────────────────────────────
125
+
126
+ /**
127
+ * Subscribe to the channel. Sends the Subscribe message to the server
128
+ * with the full config (postgres_changes, broadcast, presence).
129
+ */
130
+ subscribe(callback?: SubscribeCallback): this {
131
+ this._subscribeCallback = callback ?? null
132
+ this._subscribed = true
133
+
134
+ // Si le WS est déjà connecté (canal créé après une connexion existante),
135
+ // s'abonner immédiatement. Sinon, RealtimeService appellera _bindSend()
136
+ // puis _doSubscribe() depuis son handler ws.onopen une fois connecté.
137
+ if (this._sendFn) {
138
+ this._doSubscribe()
139
+ }
140
+
141
+ return this
142
+ }
143
+
144
+ /** Called by RealtimeService once the WS connection is ready. */
145
+ _bindSend(sendFn: SendFn): void {
146
+ this._sendFn = sendFn
147
+ }
148
+
149
+ /** Send the subscribe message to the server. */
150
+ _doSubscribe(): void {
151
+ if (!this._sendFn) return
152
+
153
+ // Build postgres_changes config from listeners
154
+ const pgFilters = this._listeners
155
+ .filter((l) => l.type === 'postgres_changes')
156
+ .map((l) => l.filter as PostgresChangesFilter)
157
+
158
+ const msg = {
159
+ type: 'subscribe',
160
+ channel: this.fullName,
161
+ ref_id: this._nextRef(),
162
+ since_id: this._lastEventId ?? undefined,
163
+ config: {
164
+ postgres_changes: pgFilters,
165
+ broadcast: {
166
+ self_broadcast: this._broadcastSelf, // ← depuis les options du channel
167
+ ack: this._broadcastAck, // ← depuis les options du channel
168
+ },
169
+ presence: { key: this._presenceKey }, // ← depuis les options du channel
170
+ private: this._isPrivate,
171
+ },
172
+ }
173
+
174
+ this._sendFn(JSON.stringify(msg))
175
+ }
176
+
177
+ /** Unsubscribe from the channel. */
178
+ unsubscribe(): void {
179
+ if (this._sendFn) {
180
+ this._sendFn(JSON.stringify({ type: 'unsubscribe', channel: this.fullName }))
181
+ }
182
+ this._subscribed = false
183
+ this._listeners.length = 0
184
+ this._presenceState = {}
185
+ }
186
+
187
+ // ─── Broadcast ──────────────────────────────────────────────────────────
188
+
189
+ /**
190
+ * Send a broadcast message to the channel.
191
+ * If broadcast.ack=true, returns a Promise that resolves when the server ACKs.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * // Without ACK (fire & forget)
196
+ * channel.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100, y: 200 } })
197
+ *
198
+ * // With ACK (wait for server confirmation)
199
+ * await channel.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100 } })
200
+ * ```
201
+ */
202
+ send(message: BroadcastPayload): Promise<'ok' | 'timeout' | 'error'> {
203
+ return new Promise((resolve) => {
204
+ if (!this._sendFn) {
205
+ resolve('error')
206
+ return
207
+ }
208
+ const ref_id = message.ref_id ?? this._nextRef()
209
+
210
+ // Si ack demandé, enregistrer un callback en attente
211
+ if (this._broadcastAck) {
212
+ const timer = setTimeout(() => {
213
+ this._pendingAcks.delete(ref_id)
214
+ resolve('timeout')
215
+ }, 5000)
216
+
217
+ this._pendingAcks.set(ref_id, () => {
218
+ clearTimeout(timer)
219
+ resolve('ok')
220
+ })
221
+ } else {
222
+ resolve('ok')
223
+ }
224
+
225
+ this._sendFn(
226
+ JSON.stringify({
227
+ type: 'broadcast',
228
+ channel: this.fullName,
229
+ ref_id,
230
+ payload: { event: message.event, ...message.payload },
231
+ }),
232
+ )
233
+ })
234
+ }
235
+
236
+ // ─── Presence ───────────────────────────────────────────────────────────
237
+
238
+ /**
239
+ * Track presence state for this client.
240
+ * Returns a Promise that resolves with 'ok' once tracked (compatible Supabase).
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * await channel.track({ user: 'user-1', online_at: new Date().toISOString() })
245
+ * ```
246
+ */
247
+ async track(state: Record<string, unknown>): Promise<'ok' | 'error'> {
248
+ if (!this._sendFn) return 'error'
249
+ this._sendFn(
250
+ JSON.stringify({
251
+ type: 'presence_update',
252
+ channel: this.fullName,
253
+ meta: state,
254
+ }),
255
+ )
256
+ return 'ok'
257
+ }
258
+
259
+ /** Stop tracking presence. */
260
+ async untrack(): Promise<'ok' | 'error'> {
261
+ return this.track({})
262
+ }
263
+
264
+ /** Get the current presence state for this channel. */
265
+ presenceState(): PresenceState {
266
+ return { ...this._presenceState }
267
+ }
268
+
269
+ // ─── Internal: dispatch server messages ──────────────────────────────────
270
+
271
+ /** Called by RealtimeService when a server message arrives for this channel. */
272
+ _onMessage(msg: ServerMessage): void {
273
+ switch (msg.type) {
274
+ case 'subscribed': {
275
+ const subMsg = msg as ServerSubscribedMsg
276
+ if (subMsg.subscription_id) this._subscriptionId = subMsg.subscription_id
277
+ this._subscribeCallback?.('SUBSCRIBED')
278
+ break
279
+ }
280
+
281
+ case 'event': {
282
+ // Serde flatten : les champs de RealtimeEvent sont au top-level du JSON
283
+ // ServerEventMsg a les champs id, event, topic, payload, timestamp directement
284
+ const evtMsg = msg as ServerEventMsg
285
+ if (evtMsg.id) {
286
+ const event: RealtimeEvent = {
287
+ id: evtMsg.id,
288
+ event: evtMsg.event as RealtimeEvent['event'],
289
+ topic: evtMsg.topic,
290
+ payload: evtMsg.payload,
291
+ timestamp: evtMsg.timestamp,
292
+ allowed_sub_ids: evtMsg.allowed_sub_ids,
293
+ }
294
+ this._dispatchEvent(event)
295
+ }
296
+ break
297
+ }
298
+
299
+ case 'presence_state': {
300
+ const presMsg = msg as ServerPresenceStateMsg
301
+ if (presMsg.presences) {
302
+ this._presenceState = {}
303
+ for (const p of presMsg.presences) {
304
+ const key = p.user_id || p.client_id
305
+ if (!this._presenceState[key]) this._presenceState[key] = []
306
+ this._presenceState[key].push(p)
307
+ }
308
+ for (const l of this._listeners) {
309
+ if (l.type === 'presence' && (l.filter as PresenceFilter).event === 'sync') {
310
+ l.callback(this._presenceState)
311
+ }
312
+ }
313
+ }
314
+ break
315
+ }
316
+
317
+ case 'error': {
318
+ const errMsg = msg as ServerErrorMsg
319
+ this._subscribeCallback?.('CHANNEL_ERROR', new Error(errMsg.message ?? 'Unknown error'))
320
+ break
321
+ }
322
+
323
+ case 'token_refreshed':
324
+ break
325
+
326
+ case 'broadcast_ack': {
327
+ // Résoudre le pending ACK si ref_id présent
328
+ const ackMsg = msg as { type: 'broadcast_ack'; ref_id?: string; message_id?: string }
329
+ const refId = ackMsg.ref_id ?? ackMsg.message_id
330
+ if (refId) {
331
+ const resolve = this._pendingAcks.get(refId)
332
+ if (resolve) {
333
+ this._pendingAcks.delete(refId)
334
+ resolve()
335
+ }
336
+ }
337
+ break
338
+ }
339
+ }
340
+ }
341
+
342
+ /** Dispatch a RealtimeEvent to matching listeners. */
343
+ private _dispatchEvent(event: RealtimeEvent): void {
344
+ if (!event) return
345
+
346
+ // Tracker le dernier event ID pour since_id au reconnect
347
+ if (event.id) this._lastEventId = event.id
348
+
349
+ // Filtre RLS : si l'event a une liste allowed_sub_ids et que notre subscription_id
350
+ // n'y figure pas, ne pas dispatcher (defense-in-depth côté client)
351
+ const allowed = event.allowed_sub_ids
352
+ if (allowed && this._subscriptionId && !allowed.includes(this._subscriptionId)) {
353
+ return
354
+ }
355
+
356
+ const eventType = event.event // 'insert', 'update', 'delete', 'broadcast', 'presence'
357
+
358
+ // Mettre à jour l'état de présence local avant de notifier les écouteurs
359
+ if (eventType === 'presence') {
360
+ const payload = event.payload as Record<string, unknown>
361
+ const action = payload.action as string
362
+ const key = (payload.user_id || payload.client_id) as string
363
+
364
+ if (action === 'join') {
365
+ if (!this._presenceState[key]) this._presenceState[key] = []
366
+ if (!this._presenceState[key].some(p => p.client_id === payload.client_id)) {
367
+ const presenceInfo: PresenceInfo = {
368
+ client_id: payload.client_id as string,
369
+ user_id: (payload.user_id as string) ?? undefined,
370
+ metadata: (payload.metadata ?? payload.meta ?? {}) as Record<string, unknown>,
371
+ joined_at: new Date().toISOString(),
372
+ last_seen: new Date().toISOString(),
373
+ }
374
+ this._presenceState[key].push(presenceInfo)
375
+ }
376
+ } else if (action === 'leave') {
377
+ const arr = this._presenceState[key]
378
+ if (arr) {
379
+ const idx = arr.findIndex((p) => p.client_id === payload.client_id)
380
+ if (idx >= 0) arr.splice(idx, 1)
381
+ if (arr.length === 0) delete this._presenceState[key]
382
+ }
383
+ } else if (action === 'update') {
384
+ const arr = this._presenceState[key]
385
+ if (arr) {
386
+ const idx = arr.findIndex((p) => p.client_id === payload.client_id)
387
+ if (idx >= 0) {
388
+ arr[idx].metadata = (payload.metadata ?? payload.meta ?? {}) as Record<string, unknown>
389
+ arr[idx].last_seen = new Date().toISOString()
390
+ } else {
391
+ const presenceInfo: PresenceInfo = {
392
+ client_id: payload.client_id as string,
393
+ user_id: (payload.user_id as string) ?? undefined,
394
+ metadata: (payload.metadata ?? payload.meta ?? {}) as Record<string, unknown>,
395
+ joined_at: new Date().toISOString(),
396
+ last_seen: new Date().toISOString(),
397
+ }
398
+ this._presenceState[key].push(presenceInfo)
399
+ }
400
+ } else {
401
+ this._presenceState[key] = [{
402
+ client_id: payload.client_id as string,
403
+ user_id: (payload.user_id as string) ?? undefined,
404
+ metadata: (payload.metadata ?? payload.meta ?? {}) as Record<string, unknown>,
405
+ joined_at: new Date().toISOString(),
406
+ last_seen: new Date().toISOString(),
407
+ }]
408
+ }
409
+ }
410
+ }
411
+
412
+ for (const listener of this._listeners) {
413
+ if (listener.type === 'postgres_changes') {
414
+ const filter = listener.filter as PostgresChangesFilter
415
+ if (
416
+ eventType === 'insert' ||
417
+ eventType === 'update' ||
418
+ eventType === 'delete'
419
+ ) {
420
+ const filterEvent = filter.event.toLowerCase()
421
+ if (filterEvent === '*' || filterEvent === eventType) {
422
+ // Check schema/table if specified
423
+ const payload = event.payload as Record<string, unknown>
424
+ if (filter.schema && payload.schema !== filter.schema) continue
425
+ if (filter.table && payload.table !== filter.table) continue
426
+
427
+ // Second rideau client (defense-in-depth) : le serveur a déjà filtré
428
+ // par colonne de façon autoritaire. DELETE -> old_record (record CDC
429
+ // vide) ; INSERT/UPDATE -> record. Si la cible est absente/non-objet,
430
+ // ne pas filtrer (éviter un faux négatif sur un payload sans record).
431
+ if (filter.filter) {
432
+ const target = eventType === 'delete' ? payload.old_record : payload.record
433
+ if (target && typeof target === 'object') {
434
+ if (!matchesColumnFilter(filter.filter, target as Record<string, unknown>)) {
435
+ continue
436
+ }
437
+ }
438
+ }
439
+
440
+ listener.callback(event)
441
+ }
442
+ }
443
+ } else if (listener.type === 'broadcast') {
444
+ if (eventType === 'broadcast') {
445
+ const filter = listener.filter as BroadcastFilter
446
+ const payload = event.payload as Record<string, unknown>
447
+ // Match broadcast event name if specified
448
+ if (!filter.event || payload.event === filter.event) {
449
+ listener.callback(event)
450
+ }
451
+ }
452
+ } else if (listener.type === 'presence') {
453
+ if (eventType === 'presence') {
454
+ const filter = listener.filter as PresenceFilter
455
+ const payload = event.payload as Record<string, unknown>
456
+ const action = payload.action as string
457
+
458
+ if (filter.event === 'join' && action === 'join') {
459
+ listener.callback({
460
+ key: payload.user_id || payload.client_id,
461
+ newPresences: [payload],
462
+ })
463
+ } else if (filter.event === 'leave' && action === 'leave') {
464
+ listener.callback({
465
+ key: payload.user_id || payload.client_id,
466
+ leftPresences: [payload],
467
+ })
468
+ } else if (filter.event === 'sync') {
469
+ listener.callback(this.presenceState())
470
+ }
471
+ }
472
+ }
473
+ }
474
+ }
475
+
476
+ /** Whether this channel has been subscribed. */
477
+ get isSubscribed(): boolean {
478
+ return this._subscribed
479
+ }
480
+
481
+ /** Get listeners (for re-subscribe after reconnect). */
482
+ _getListeners(): ListenerEntry[] {
483
+ return this._listeners
484
+ }
485
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * HeartbeatManager — pings périodiques pour maintenir la connexion WS.
3
+ * Utilise un Worker dédié (anti-throttle des onglets en arrière-plan) ; fallback setInterval.
4
+ */
5
+ export class HeartbeatManager {
6
+ private _worker: Worker | null = null
7
+ private _workerUrl: string | null = null
8
+ private _interval: ReturnType<typeof setInterval> | null = null
9
+ private _sendFn: (() => void) | null = null
10
+
11
+ start(sendPing: () => void, intervalMs = 25000): void {
12
+ this.stop()
13
+ this._sendFn = sendPing
14
+
15
+ if (typeof Worker !== 'undefined' && typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
16
+ try {
17
+ const code = `let i=null;onmessage=(e)=>{if(e.data.type==='start'){if(i)clearInterval(i);i=setInterval(()=>postMessage('ping'),e.data.interval||25000);}else if(e.data.type==='stop'){if(i){clearInterval(i);i=null;}}};`
18
+ const blob = new Blob([code], { type: 'application/javascript' })
19
+ this._workerUrl = URL.createObjectURL(blob)
20
+ this._worker = new Worker(this._workerUrl)
21
+ this._worker.onmessage = () => {
22
+ this._sendFn?.()
23
+ }
24
+ this._worker.postMessage({ type: 'start', interval: intervalMs })
25
+ return
26
+ } catch {
27
+ // Worker indispo/CSP → fallback
28
+ this._worker = null
29
+ if (this._workerUrl) {
30
+ try {
31
+ URL.revokeObjectURL(this._workerUrl)
32
+ } catch {
33
+ /* ignore */
34
+ }
35
+ this._workerUrl = null
36
+ }
37
+ }
38
+ }
39
+
40
+ this._interval = setInterval(() => {
41
+ this._sendFn?.()
42
+ }, intervalMs)
43
+ }
44
+
45
+ stop(): void {
46
+ if (this._worker) {
47
+ try {
48
+ this._worker.postMessage({ type: 'stop' })
49
+ this._worker.terminate()
50
+ } catch {
51
+ /* ignore */
52
+ }
53
+ this._worker = null
54
+ }
55
+ if (this._workerUrl) {
56
+ try {
57
+ URL.revokeObjectURL(this._workerUrl)
58
+ } catch {
59
+ /* ignore */
60
+ }
61
+ this._workerUrl = null
62
+ }
63
+ if (this._interval) {
64
+ clearInterval(this._interval)
65
+ this._interval = null
66
+ }
67
+ this._sendFn = null
68
+ }
69
+
70
+ get isActive(): boolean {
71
+ return this._worker !== null || this._interval !== null
72
+ }
73
+ }