@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,300 @@
1
+ // ---------------------------------------------------------------------------
2
+ // BaseAuthStore + LocalAuthStore tests
3
+ // ---------------------------------------------------------------------------
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
6
+ import { BaseAuthStore, isTokenExpired } from '../src/stores/BaseAuthStore'
7
+ import { LocalAuthStore } from '../src/stores/LocalAuthStore'
8
+ import { fakeUser, fakeJwt, validJwt, expiredJwt } from './mocks'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // BaseAuthStore
12
+ // ---------------------------------------------------------------------------
13
+
14
+ describe('BaseAuthStore', () => {
15
+ let store: BaseAuthStore
16
+
17
+ beforeEach(() => {
18
+ store = new BaseAuthStore()
19
+ })
20
+
21
+ it('starts with null token/user', () => {
22
+ expect(store.token).toBeNull()
23
+ expect(store.refreshToken).toBeNull()
24
+ expect(store.user).toBeNull()
25
+ })
26
+
27
+ it('save() stores token, refreshToken and user', () => {
28
+ const user = fakeUser()
29
+ store.save('tok_1', 'rt_1', user)
30
+
31
+ expect(store.token).toBe('tok_1')
32
+ expect(store.refreshToken).toBe('rt_1')
33
+ expect(store.user).toEqual(user)
34
+ })
35
+
36
+ it('clear() resets to null', () => {
37
+ store.save('tok_1', 'rt_1', fakeUser())
38
+ store.clear()
39
+
40
+ expect(store.token).toBeNull()
41
+ expect(store.refreshToken).toBeNull()
42
+ expect(store.user).toBeNull()
43
+ })
44
+
45
+ it('onChange fires callback on save and clear', () => {
46
+ const events: Array<{ token: string | null; user: unknown }> = []
47
+ store.onChange((token, user) => {
48
+ events.push({ token, user })
49
+ })
50
+
51
+ const user = fakeUser()
52
+ store.save('tok_1', 'rt_1', user)
53
+ store.clear()
54
+
55
+ expect(events).toHaveLength(2)
56
+ expect(events[0].token).toBe('tok_1')
57
+ expect(events[1].token).toBeNull()
58
+ })
59
+
60
+ it('unsubscribe stops receiving events', () => {
61
+ const events: string[] = []
62
+ const unsub = store.onChange((token) => {
63
+ events.push(token ?? 'null')
64
+ })
65
+
66
+ store.save('tok_1', 'rt_1', fakeUser())
67
+ expect(events).toHaveLength(1)
68
+
69
+ unsub()
70
+ store.save('tok_2', 'rt_2', fakeUser())
71
+ // Should still be 1 since we unsubscribed
72
+ expect(events).toHaveLength(1)
73
+ })
74
+
75
+ it('isValid returns true for a non-expired JWT', () => {
76
+ store.save(validJwt(), 'rt', fakeUser())
77
+ expect(store.isValid).toBe(true)
78
+ })
79
+
80
+ it('isValid returns false for an expired JWT', () => {
81
+ store.save(expiredJwt(), 'rt', fakeUser())
82
+ expect(store.isValid).toBe(false)
83
+ })
84
+
85
+ it('isValid returns false when no token is set', () => {
86
+ expect(store.isValid).toBe(false)
87
+ })
88
+
89
+ it('subscriber errors do not break the store', () => {
90
+ store.onChange(() => {
91
+ throw new Error('subscriber boom')
92
+ })
93
+ const events: string[] = []
94
+ store.onChange((token) => events.push(token ?? 'null'))
95
+
96
+ // Should not throw
97
+ store.save('tok_1', 'rt_1', fakeUser())
98
+ expect(events).toHaveLength(1)
99
+ expect(events[0]).toBe('tok_1')
100
+ })
101
+ })
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // isTokenExpired
105
+ // ---------------------------------------------------------------------------
106
+
107
+ describe('isTokenExpired', () => {
108
+ it('returns false for a future-exp token', () => {
109
+ const future = fakeJwt(Math.floor(Date.now() / 1000) + 3600)
110
+ expect(isTokenExpired(future)).toBe(false)
111
+ })
112
+
113
+ it('returns true for a past-exp token', () => {
114
+ const past = fakeJwt(Math.floor(Date.now() / 1000) - 60)
115
+ expect(isTokenExpired(past)).toBe(true)
116
+ })
117
+
118
+ it('respects margin parameter', () => {
119
+ // Token expires in 10 seconds, but margin is 30 -- should be "expired"
120
+ const almostExpired = fakeJwt(Math.floor(Date.now() / 1000) + 10)
121
+ expect(isTokenExpired(almostExpired, 30)).toBe(true)
122
+ })
123
+
124
+ it('returns true for malformed tokens', () => {
125
+ expect(isTokenExpired('not-a-jwt')).toBe(true)
126
+ expect(isTokenExpired('')).toBe(true)
127
+ expect(isTokenExpired('a.b')).toBe(true)
128
+ })
129
+ })
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // LocalAuthStore (mocked localStorage)
133
+ // ---------------------------------------------------------------------------
134
+
135
+ describe('LocalAuthStore', () => {
136
+ let mockStorage: Record<string, string>
137
+
138
+ beforeEach(() => {
139
+ mockStorage = {}
140
+ // Mock localStorage
141
+ const storage = {
142
+ getItem: vi.fn((key: string) => mockStorage[key] ?? null),
143
+ setItem: vi.fn((key: string, value: string) => {
144
+ mockStorage[key] = value
145
+ }),
146
+ removeItem: vi.fn((key: string) => {
147
+ delete mockStorage[key]
148
+ }),
149
+ clear: vi.fn(() => {
150
+ mockStorage = {}
151
+ }),
152
+ get length() {
153
+ return Object.keys(mockStorage).length
154
+ },
155
+ key: vi.fn((index: number) => Object.keys(mockStorage)[index] ?? null),
156
+ }
157
+
158
+ Object.defineProperty(globalThis, 'localStorage', {
159
+ value: storage,
160
+ writable: true,
161
+ configurable: true,
162
+ })
163
+ })
164
+
165
+ afterEach(() => {
166
+ // Clean up
167
+ delete (globalThis as any).localStorage
168
+ })
169
+
170
+ it('persists to localStorage on save', () => {
171
+ const store = new LocalAuthStore()
172
+ store.save('tok_1', 'rt_1', fakeUser())
173
+
174
+ const raw = mockStorage['aurabase_auth']
175
+ expect(raw).toBeDefined()
176
+ const parsed = JSON.parse(raw)
177
+ expect(parsed.token).toBe('tok_1')
178
+ expect(parsed.refreshToken).toBe('rt_1')
179
+ expect(parsed.user.id).toBe('user-1')
180
+ })
181
+
182
+ it('clears localStorage on clear()', () => {
183
+ const store = new LocalAuthStore()
184
+ store.save('tok_1', 'rt_1', fakeUser())
185
+ expect(mockStorage['aurabase_auth']).toBeDefined()
186
+
187
+ store.clear()
188
+ expect(mockStorage['aurabase_auth']).toBeUndefined()
189
+ })
190
+
191
+ it('loads from localStorage on construction', () => {
192
+ // Pre-populate storage
193
+ mockStorage['aurabase_auth'] = JSON.stringify({
194
+ token: 'tok_pre',
195
+ refreshToken: 'rt_pre',
196
+ user: fakeUser({ email: 'stored@example.com' }),
197
+ })
198
+
199
+ const store = new LocalAuthStore()
200
+
201
+ expect(store.token).toBe('tok_pre')
202
+ expect(store.refreshToken).toBe('rt_pre')
203
+ expect(store.user?.email).toBe('stored@example.com')
204
+ })
205
+
206
+ it('uses custom key', () => {
207
+ const store = new LocalAuthStore('custom_key')
208
+ store.save('tok_1', 'rt_1', fakeUser())
209
+
210
+ expect(mockStorage['custom_key']).toBeDefined()
211
+ expect(mockStorage['aurabase_auth']).toBeUndefined()
212
+ })
213
+
214
+ it('handles corrupted localStorage gracefully', () => {
215
+ mockStorage['aurabase_auth'] = 'not valid json {'
216
+
217
+ // Should not throw
218
+ const store = new LocalAuthStore()
219
+ expect(store.token).toBeNull()
220
+ })
221
+
222
+ it('exportToCookie produces valid Set-Cookie string', () => {
223
+ const store = new LocalAuthStore()
224
+ store.save('tok_1', 'rt_1', fakeUser())
225
+
226
+ const cookie = store.exportToCookie({
227
+ path: '/',
228
+ secure: true,
229
+ sameSite: 'Lax',
230
+ maxAge: 86400,
231
+ })
232
+
233
+ expect(cookie).toContain('aurabase_auth=')
234
+ expect(cookie).toContain('Path=/')
235
+ expect(cookie).toContain('Secure')
236
+ expect(cookie).toContain('SameSite=Lax')
237
+ expect(cookie).toContain('Max-Age=86400')
238
+ })
239
+
240
+ it('LOT 3 (I8) : setItem qui throw (quota) émet storage-blocked sans casser save()', () => {
241
+ const store = new LocalAuthStore()
242
+ const events: Array<{ kind: string; op?: string }> = []
243
+ store.setObservabilityHook((e) => events.push(e as { kind: string; op?: string }), false)
244
+
245
+ const original = (globalThis as any).localStorage.setItem
246
+ ;(globalThis as any).localStorage.setItem = vi.fn(() => {
247
+ throw new Error('QuotaExceededError')
248
+ })
249
+
250
+ // save() ne throw jamais malgré le localStorage bloqué.
251
+ expect(() => store.save('tok_1', 'rt_1', fakeUser())).not.toThrow()
252
+ expect(store.token).toBe('tok_1') // état en mémoire toujours à jour
253
+
254
+ // save() persiste via `_persist()` (op 'persist'), pas via le `setItem` public
255
+ // override (réservé au stockage clé/valeur générique — PKCE `code_verifier`).
256
+ expect(events).toContainEqual({ kind: 'storage-blocked', op: 'persist' })
257
+
258
+ ;(globalThis as any).localStorage.setItem = original
259
+ })
260
+
261
+ it('LOT 3 (I8) : setItem (générique, ex. PKCE) qui throw émet storage-blocked op:"setItem"', () => {
262
+ const store = new LocalAuthStore()
263
+ const events: Array<{ kind: string; op?: string }> = []
264
+ store.setObservabilityHook((e) => events.push(e as { kind: string; op?: string }), false)
265
+
266
+ const original = (globalThis as any).localStorage.setItem
267
+ ;(globalThis as any).localStorage.setItem = vi.fn(() => {
268
+ throw new Error('QuotaExceededError')
269
+ })
270
+
271
+ expect(() => store.setItem('some-key', 'some-value')).not.toThrow()
272
+ expect(events).toContainEqual({ kind: 'storage-blocked', op: 'setItem' })
273
+
274
+ ;(globalThis as any).localStorage.setItem = original
275
+ })
276
+
277
+ it('LOT 3 (I8) : sans hook câblé, aucun throw / no-op silencieux', () => {
278
+ const store = new LocalAuthStore()
279
+ ;(globalThis as any).localStorage.setItem = vi.fn(() => {
280
+ throw new Error('blocked')
281
+ })
282
+ expect(() => store.save('tok_1', 'rt_1', fakeUser())).not.toThrow()
283
+ })
284
+
285
+ it('loadFromCookie restores auth state', () => {
286
+ const store = new LocalAuthStore()
287
+ const data = {
288
+ token: 'tok_cookie',
289
+ refreshToken: 'rt_cookie',
290
+ user: fakeUser({ email: 'cookie@example.com' }),
291
+ }
292
+ const cookieString = `other=val; aurabase_auth=${encodeURIComponent(JSON.stringify(data))}; foo=bar`
293
+
294
+ store.loadFromCookie(cookieString)
295
+
296
+ expect(store.token).toBe('tok_cookie')
297
+ expect(store.refreshToken).toBe('rt_cookie')
298
+ expect(store.user?.email).toBe('cookie@example.com')
299
+ })
300
+ })
@@ -0,0 +1,61 @@
1
+ // ---------------------------------------------------------------------------
2
+ // AuraErrorCode (LOT 3, finding I4) -- taxonomie d'erreurs typée
3
+ // ---------------------------------------------------------------------------
4
+
5
+ // NOTE : le test type-level proprement dit (vérifié par `tsc`, pas seulement par
6
+ // vitest qui transpile/strip les types) vit dans
7
+ // `packages/aurabase/tests/types/error-code.test-d.ts` (chaîné à `pnpm --filter
8
+ // aurabase test` via `tsconfig.test-d.json`). Ici : couverture RUNTIME uniquement.
9
+ import { describe, it, expect } from 'vitest'
10
+ import { AuraError, type AuraErrorCode } from '../src/AuraError'
11
+
12
+ describe('AuraErrorCode', () => {
13
+ it('un code connu (littéral de l’union) est accepté et préservé au runtime', () => {
14
+ const e = new AuraError(0, { error: { code: 'timeout', message: 'x' } })
15
+ expect(e.code).toBe('timeout')
16
+ // Sanity : assignable à AuraErrorCode sans cast (autocomplétion sur les littéraux connus).
17
+ const code: AuraErrorCode = e.code
18
+ expect(code).toBe('timeout')
19
+ })
20
+
21
+ it('reste ouvert à un code backend arbitraire non listé (pattern LiteralUnion)', () => {
22
+ const e = new AuraError(422, { error: { code: 'some_backend_specific_code', message: 'x' } })
23
+ expect(e.code).toBe('some_backend_specific_code')
24
+ const code: AuraErrorCode = e.code
25
+ expect(code).toBe('some_backend_specific_code')
26
+ })
27
+
28
+ it('fallback http_<status> quand aucun code n’est fourni par le backend', () => {
29
+ const e = new AuraError(503)
30
+ expect(e.code).toBe('http_503')
31
+ })
32
+
33
+ it('couvre tous les codes SDK-side connus (échantillon représentatif)', () => {
34
+ const knownCodes: AuraErrorCode[] = [
35
+ 'no_session',
36
+ 'timeout',
37
+ 'network_error',
38
+ 'invalid_response',
39
+ 'refresh_discarded',
40
+ 'unverifiable_token',
41
+ 'no_code_verifier',
42
+ 'pkce_generation_failed',
43
+ 'missing_email',
44
+ 'jwks_fetch_failed',
45
+ 'signature_invalid',
46
+ 'token_expired',
47
+ 'token_not_yet_valid',
48
+ 'invalid_jwt',
49
+ 'unsupported_alg',
50
+ 'no_matching_key',
51
+ 'invalid_issuer',
52
+ 'invalid_audience',
53
+ 'invalid_path',
54
+ 'PGRST116',
55
+ ]
56
+ for (const code of knownCodes) {
57
+ const e = new AuraError(0, { error: { code, message: 'x' } })
58
+ expect(e.code).toBe(code)
59
+ }
60
+ })
61
+ })
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { AuraError, isAuraError } from '../src/AuraError'
3
+
4
+ describe('AuraError / isAuraError', () => {
5
+ it('reconnaît une vraie instance et expose les helpers', () => {
6
+ const e = new AuraError(429, { error: { code: 'rate_limited', message: 'slow down' } })
7
+ expect(isAuraError(e)).toBe(true)
8
+ expect(e.isRateLimited).toBe(true)
9
+ expect(e.status).toBe(429)
10
+ })
11
+ it('les instances portent le marqueur __isAuraError (détection cross-realm réelle)', () => {
12
+ // Le correctif : une vraie instance doit exposer le marqueur au runtime,
13
+ // pour être détectée même quand `instanceof` échoue (autre bundle/realm).
14
+ expect((new AuraError(500) as unknown as Record<string, unknown>).__isAuraError).toBe(true)
15
+ })
16
+ it('reconnaît un objet marqué __isAuraError (cross-realm simulé)', () => {
17
+ expect(isAuraError({ __isAuraError: true })).toBe(true)
18
+ })
19
+ it('rejette un objet quelconque', () => {
20
+ expect(isAuraError({})).toBe(false)
21
+ expect(isAuraError(null)).toBe(false)
22
+ expect(isAuraError(new Error('x'))).toBe(false)
23
+ })
24
+ })
@@ -0,0 +1,286 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest'
2
+ import { fetchWithAuth, type FetchInit } from '../src/fetch'
3
+ import { BaseAuthStore } from '../src/stores/BaseAuthStore'
4
+ import type { AuraClientLike } from '../src/client'
5
+ import type { AuraObservabilityEvent } from '../src/types'
6
+
7
+ // fetch séquentiel : renvoie les réponses dans l'ordre, compte les appels.
8
+ function seqFetch(
9
+ responses: Array<{ status: number; body?: unknown; headers?: Record<string, string>; throw?: unknown }>,
10
+ ) {
11
+ const calls: Array<{ url: string; init: RequestInit }> = []
12
+ let i = 0
13
+ const fn = async (url: string, init: RequestInit): Promise<Response> => {
14
+ calls.push({ url, init })
15
+ const r = responses[Math.min(i, responses.length - 1)]
16
+ i++
17
+ if (r.throw) throw r.throw
18
+ return new Response(r.body !== undefined ? JSON.stringify(r.body) : null, {
19
+ status: r.status,
20
+ headers: { 'Content-Type': 'application/json', ...r.headers },
21
+ })
22
+ }
23
+ return { fn, calls }
24
+ }
25
+
26
+ function makeClient(
27
+ fetchFn: typeof globalThis.fetch,
28
+ retryConfig?: { enabled: boolean; maxRetries: number },
29
+ onEvent?: (e: AuraObservabilityEvent) => void,
30
+ ): AuraClientLike {
31
+ return {
32
+ baseUrl: 'http://localhost:8080',
33
+ projectId: 'proj-1',
34
+ apiKey: 'aura_anon_x',
35
+ fetchFn,
36
+ authStore: new BaseAuthStore(),
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ auth: { refreshSession: async () => ({ data: null, error: null }) as any },
39
+ ...(retryConfig ? { retryConfig } : {}),
40
+ ...(onEvent ? { onEvent } : {}),
41
+ }
42
+ }
43
+
44
+ afterEach(() => vi.useRealTimers())
45
+
46
+ describe('fetchWithAuth — minor 6b : corps JSON non-objet (null/primitif) sans throw', () => {
47
+ it('200 avec corps primitif (number) → data = valeur, pas de throw ni perte', async () => {
48
+ const { fn } = seqFetch([{ status: 200, body: 42 }])
49
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
50
+ const res = await fetchWithAuth<number>('http://x/y', { method: 'GET' }, client)
51
+ expect(res.error).toBeNull()
52
+ expect(res.data).toBe(42)
53
+ })
54
+
55
+ it('200 avec corps JSON `null` → data = null, error = null (pas de `in` sur null)', async () => {
56
+ const { fn } = seqFetch([{ status: 200, body: null }])
57
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
58
+ const res = await fetchWithAuth<unknown>('http://x/y', { method: 'GET' }, client)
59
+ expect(res.error).toBeNull()
60
+ expect(res.data).toBeNull()
61
+ })
62
+
63
+ it('200 avec corps tableau (pas d’enveloppe) → data = tableau tel quel', async () => {
64
+ const { fn } = seqFetch([{ status: 200, body: [1, 2, 3] }])
65
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
66
+ const res = await fetchWithAuth<number[]>('http://x/y', { method: 'GET' }, client)
67
+ expect(res.error).toBeNull()
68
+ expect(res.data).toEqual([1, 2, 3])
69
+ })
70
+ })
71
+
72
+ describe('fetchWithAuth — retry transitoire', () => {
73
+ it('GET 503 puis 200 → retry puis succès', async () => {
74
+ vi.useFakeTimers()
75
+ const { fn, calls } = seqFetch([{ status: 503 }, { status: 200, body: { ok: true } }])
76
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
77
+ const p = fetchWithAuth<{ ok: boolean }>('http://x/y', { method: 'GET' }, client)
78
+ await vi.advanceTimersByTimeAsync(600)
79
+ const res = await p
80
+ expect(res.error).toBeNull()
81
+ expect(res.data).toEqual({ ok: true })
82
+ expect(calls.length).toBe(2)
83
+ expect(calls[1].init.headers).toMatchObject({ 'X-Retry-Count': '1' })
84
+ })
85
+
86
+ it('GET erreur réseau puis 200 → retry puis succès', async () => {
87
+ vi.useFakeTimers()
88
+ const { fn, calls } = seqFetch([{ status: 0, throw: new Error('ECONNRESET') }, { status: 200, body: { ok: 1 } }])
89
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
90
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
91
+ await vi.advanceTimersByTimeAsync(600)
92
+ const res = await p
93
+ expect(res.error).toBeNull()
94
+ expect(calls.length).toBe(2)
95
+ })
96
+
97
+ it('POST 503 → PAS de retry', async () => {
98
+ const { fn, calls } = seqFetch([{ status: 503, body: { error: { code: 'x', message: 'down' } } }])
99
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
100
+ const res = await fetchWithAuth('http://x/y', { method: 'POST', body: { a: 1 } }, client)
101
+ expect(res.error?.status).toBe(503)
102
+ expect(calls.length).toBe(1)
103
+ })
104
+
105
+ it('GET 503 en boucle → borné à maxRetries (3) puis erreur', async () => {
106
+ vi.useFakeTimers()
107
+ const { fn, calls } = seqFetch([{ status: 503 }])
108
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
109
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
110
+ await vi.advanceTimersByTimeAsync(60000)
111
+ const res = await p
112
+ expect(res.error?.status).toBe(503)
113
+ expect(calls.length).toBe(4) // 1 + 3 retries
114
+ })
115
+
116
+ it('Retry-After prioritaire sur le backoff', async () => {
117
+ vi.useFakeTimers()
118
+ const { fn, calls } = seqFetch([
119
+ { status: 503, headers: { 'Retry-After': '2' } },
120
+ { status: 200, body: { ok: true } },
121
+ ])
122
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
123
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
124
+ await vi.advanceTimersByTimeAsync(600) // > backoff max (500) mais < 2000
125
+ expect(calls.length).toBe(1)
126
+ await vi.advanceTimersByTimeAsync(1500) // total 2100 > 2000
127
+ await p
128
+ expect(calls.length).toBe(2)
129
+ })
130
+
131
+ it('AbortError → aucun retry', async () => {
132
+ const { fn, calls } = seqFetch([
133
+ { status: 0, throw: Object.assign(new Error('aborted'), { name: 'AbortError' }) },
134
+ ])
135
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
136
+ const res = await fetchWithAuth('http://x/y', { method: 'GET' }, client)
137
+ expect(res.error?.code).toBe('network_error')
138
+ expect(calls.length).toBe(1)
139
+ })
140
+
141
+ it('retry désactivé (client) → aucun retry sur 503', async () => {
142
+ const { fn, calls } = seqFetch([{ status: 503, body: {} }])
143
+ const client = makeClient(fn as unknown as typeof globalThis.fetch, { enabled: false, maxRetries: 0 })
144
+ const res = await fetchWithAuth('http://x/y', { method: 'GET' }, client)
145
+ expect(res.error?.status).toBe(503)
146
+ expect(calls.length).toBe(1)
147
+ })
148
+
149
+ it('skipRetry par requête → aucun retry', async () => {
150
+ const { fn, calls } = seqFetch([{ status: 503, body: {} }])
151
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
152
+ const init: FetchInit = { method: 'GET', skipRetry: true }
153
+ const res = await fetchWithAuth('http://x/y', init, client)
154
+ expect(res.error?.status).toBe(503)
155
+ expect(calls.length).toBe(1)
156
+ })
157
+
158
+ it('LOT 3 (I5) : `{data:null,meta:null}` (enveloppe vide honnête) → res.data === null (pas l’enveloppe)', async () => {
159
+ const { fn } = seqFetch([{ status: 200, body: { data: null, meta: null } }])
160
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
161
+ const res = await fetchWithAuth('http://x/y', { method: 'POST', body: {} }, client)
162
+ expect(res.error).toBeNull()
163
+ expect(res.data).toBeNull()
164
+ })
165
+
166
+ it('LOT 3 (I5) : corps SANS clé `data` (legacy non enveloppé) → res.data === le corps entier', async () => {
167
+ const { fn } = seqFetch([{ status: 200, body: { message: 'ok', extra: 1 } }])
168
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
169
+ const res = await fetchWithAuth('http://x/y', { method: 'GET' }, client)
170
+ expect(res.error).toBeNull()
171
+ expect(res.data).toEqual({ message: 'ok', extra: 1 })
172
+ })
173
+
174
+ it('corps avec bigint → sérialisé en string', async () => {
175
+ const { fn, calls } = seqFetch([{ status: 200, body: { ok: true } }])
176
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
177
+ await fetchWithAuth('http://x/y', { method: 'POST', body: { id: 7n } }, client)
178
+ expect(calls[0].init.body).toBe('{"id":"7"}')
179
+ })
180
+
181
+ it('LOT 3 (I8) : GET 503 puis 200 → onEvent reçoit {kind:"retry", attempt:1, status:503, url}', async () => {
182
+ vi.useFakeTimers()
183
+ const events: AuraObservabilityEvent[] = []
184
+ const { fn } = seqFetch([{ status: 503 }, { status: 200, body: { ok: true } }])
185
+ const client = makeClient(fn as unknown as typeof globalThis.fetch, undefined, (e) => events.push(e))
186
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
187
+ await vi.advanceTimersByTimeAsync(600)
188
+ await p
189
+ expect(events).toContainEqual({ kind: 'retry', attempt: 1, status: 503, url: 'http://x/y' })
190
+ })
191
+
192
+ it('LOT 3 (I8) : erreur réseau puis 200 → onEvent reçoit {kind:"retry", attempt:1, url} (sans status)', async () => {
193
+ vi.useFakeTimers()
194
+ const events: AuraObservabilityEvent[] = []
195
+ const { fn } = seqFetch([{ status: 0, throw: new Error('ECONNRESET') }, { status: 200, body: { ok: 1 } }])
196
+ const client = makeClient(fn as unknown as typeof globalThis.fetch, undefined, (e) => events.push(e))
197
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
198
+ await vi.advanceTimersByTimeAsync(600)
199
+ await p
200
+ expect(events).toContainEqual({ kind: 'retry', attempt: 1, url: 'http://x/y' })
201
+ })
202
+
203
+ it('LOT 3 (I8) : plusieurs retries → un événement par tentative, attempt croissant', async () => {
204
+ vi.useFakeTimers()
205
+ const events: AuraObservabilityEvent[] = []
206
+ const { fn } = seqFetch([{ status: 503 }, { status: 503 }, { status: 200, body: { ok: true } }])
207
+ const client = makeClient(fn as unknown as typeof globalThis.fetch, undefined, (e) => events.push(e))
208
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
209
+ await vi.advanceTimersByTimeAsync(60000)
210
+ await p
211
+ const retries = events.filter((e) => e.kind === 'retry')
212
+ expect(retries.map((e) => (e as { attempt: number }).attempt)).toEqual([1, 2])
213
+ })
214
+
215
+ it('LOT 3 (I8) : onEvent absent → aucun throw, comportement inchangé', async () => {
216
+ vi.useFakeTimers()
217
+ const { fn, calls } = seqFetch([{ status: 503 }, { status: 200, body: { ok: true } }])
218
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
219
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
220
+ await vi.advanceTimersByTimeAsync(600)
221
+ const res = await p
222
+ expect(res.error).toBeNull()
223
+ expect(calls.length).toBe(2)
224
+ })
225
+
226
+ it('LOT 3 (I8) : un onEvent qui throw ne casse pas la requête', async () => {
227
+ vi.useFakeTimers()
228
+ const { fn } = seqFetch([{ status: 503 }, { status: 200, body: { ok: true } }])
229
+ const client = makeClient(fn as unknown as typeof globalThis.fetch, undefined, () => {
230
+ throw new Error('consumer onEvent boom')
231
+ })
232
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
233
+ await vi.advanceTimersByTimeAsync(600)
234
+ const res = await p
235
+ expect(res.error).toBeNull()
236
+ expect(res.data).toEqual({ ok: true })
237
+ })
238
+
239
+ it('LOT D : authToken override gagne sur store.token (Authorization = Bearer authToken)', async () => {
240
+ const { fn, calls } = seqFetch([{ status: 200, body: { ok: true } }])
241
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
242
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
243
+ client.authStore.save('store-token', 'refresh', { id: 'u_store' } as any)
244
+ const init: FetchInit = { method: 'GET', authToken: 'foreign-token', skipAuthRefresh: true }
245
+ await fetchWithAuth('http://x/y', init, client)
246
+ const headers = calls[0].init.headers as Record<string, string>
247
+ expect(headers['Authorization']).toBe('Bearer foreign-token')
248
+ // apikey toujours envoyé (parité Supabase), inchangé.
249
+ expect(headers['apikey']).toBe('aura_anon_x')
250
+ })
251
+
252
+ it('LOT D : sans authToken → store.token (comportement inchangé)', async () => {
253
+ const { fn, calls } = seqFetch([{ status: 200, body: { ok: true } }])
254
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
+ client.authStore.save('store-token', 'refresh', { id: 'u_store' } as any)
257
+ const init: FetchInit = { method: 'GET', skipAuthRefresh: true }
258
+ await fetchWithAuth('http://x/y', init, client)
259
+ const headers = calls[0].init.headers as Record<string, string>
260
+ expect(headers['Authorization']).toBe('Bearer store-token')
261
+ })
262
+
263
+ it('LOT D : authToken sans session (store vide) → Authorization = Bearer authToken', async () => {
264
+ const { fn, calls } = seqFetch([{ status: 200, body: { ok: true } }])
265
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
266
+ const init: FetchInit = { method: 'GET', authToken: 'foreign-token', skipAuthRefresh: true }
267
+ await fetchWithAuth('http://x/y', init, client)
268
+ const headers = calls[0].init.headers as Record<string, string>
269
+ expect(headers['Authorization']).toBe('Bearer foreign-token')
270
+ })
271
+
272
+ it('Retry-After énorme (86400) est plafonné à 60000ms', async () => {
273
+ vi.useFakeTimers()
274
+ const { fn, calls } = seqFetch([
275
+ { status: 503, headers: { 'Retry-After': '86400' } },
276
+ { status: 200, body: { ok: true } },
277
+ ])
278
+ const client = makeClient(fn as unknown as typeof globalThis.fetch)
279
+ const p = fetchWithAuth('http://x/y', { method: 'GET' }, client)
280
+ await vi.advanceTimersByTimeAsync(59_999)
281
+ expect(calls.length).toBe(1) // pas encore rejoué
282
+ await vi.advanceTimersByTimeAsync(1) // total 60000 → plafond atteint
283
+ await p
284
+ expect(calls.length).toBe(2)
285
+ })
286
+ })