akanjs 2.2.4-rc.4 → 2.2.4

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 (608) hide show
  1. package/base/base.test.ts +135 -0
  2. package/base/base.ts +1 -1
  3. package/base/baseEnv.test.ts +146 -0
  4. package/base/primitiveRegistry.test.ts +127 -0
  5. package/base/primitiveRegistry.ts +10 -6
  6. package/base/symbols.test.ts +29 -0
  7. package/base/types.ts +4 -1
  8. package/base/utils.test.ts +30 -0
  9. package/build.ts +318 -0
  10. package/capacitor.base.config.test.ts +83 -0
  11. package/capacitor.base.config.ts +1 -1
  12. package/client/capacitor.ts +1 -1
  13. package/client/client.auth-storage.test.ts +210 -0
  14. package/client/client.decorators.test.ts +74 -0
  15. package/client/client.device.test.ts +181 -0
  16. package/client/client.page.test.tsx +175 -0
  17. package/client/client.pure.test.ts +146 -0
  18. package/client/client.router.test.ts +290 -0
  19. package/client/cookie.ts +4 -2
  20. package/client/createFont.ts +2 -2
  21. package/client/csrTypes.ts +8 -2
  22. package/client/makePageProto.tsx +10 -4
  23. package/client/router.ts +8 -3
  24. package/client/translator.ts +12 -4
  25. package/common/Logger.test.ts +45 -0
  26. package/common/Logger.ts +4 -1
  27. package/common/formatAndValidation.test.ts +61 -0
  28. package/common/formatNumber.ts +5 -2
  29. package/common/hmrPhase.ts +1 -1
  30. package/common/httpClient.test.ts +91 -0
  31. package/common/isValidDate.ts +3 -0
  32. package/common/objectPath.test.ts +108 -0
  33. package/common/routeConvention.test.ts +132 -0
  34. package/common/runtimeConfig.test.ts +84 -0
  35. package/constant/constant.test.ts +374 -0
  36. package/constant/fieldInfo.ts +3 -3
  37. package/constant/purify.ts +2 -1
  38. package/constant/types.ts +4 -0
  39. package/constant/via.ts +12 -6
  40. package/dictionary/dictionary.test.ts +352 -0
  41. package/document/databaseRegistry.ts +8 -0
  42. package/document/document.test.ts +698 -0
  43. package/document/into.ts +1 -0
  44. package/fetch/client/fetchClient.ts +6 -1
  45. package/fetch/client/httpClient.ts +2 -1
  46. package/fetch/fetch.test.ts +838 -0
  47. package/fetch/fetchType/appliedReturn.type.ts +1 -0
  48. package/fetch/fetchType/endpointFetch.type.ts +3 -0
  49. package/fetch/fetchType/sliceFetch.type.ts +8 -0
  50. package/fetch/requestStorage.ts +10 -2
  51. package/package.json +24 -67
  52. package/server/akanApp.test.ts +130 -0
  53. package/server/akanApp.ts +5 -2
  54. package/server/akanLib.ts +1 -0
  55. package/server/akanServer.test.ts +157 -0
  56. package/server/akanServer.ts +4 -3
  57. package/server/artifact/builderRpc.ts +2 -2
  58. package/server/artifact/ipcTypes.ts +4 -0
  59. package/server/artifact/routeClientCache.test.ts +265 -0
  60. package/server/artifact/routeSeedIndex.test.ts +56 -0
  61. package/server/artifact/routesManifest.test.ts +64 -0
  62. package/server/decorators.ts +1 -1
  63. package/server/di/diLifecycle.contract.test.ts +168 -0
  64. package/server/di/diLifecycle.ts +6 -3
  65. package/server/hmr/clientScript.ts +20 -2
  66. package/server/hmr/devHmrController.ts +4 -0
  67. package/server/logging/rotatingLogWriter.test.ts +119 -0
  68. package/server/processMetrics.test.ts +24 -0
  69. package/server/processMetricsCollector.ts +1 -1
  70. package/server/proxy/webProxyRunner.test.ts +66 -0
  71. package/server/resolver/database.resolver.ts +2 -2
  72. package/server/resolver/resolver.contract.fixture.ts +1 -0
  73. package/server/resolver/resolver.contract.test.ts +594 -0
  74. package/server/resolver/signal.resolver.ts +10 -2
  75. package/server/robots.test.ts +16 -0
  76. package/server/routeTree.test.tsx +198 -0
  77. package/server/routing/apiRouter.test.ts +136 -0
  78. package/server/routing/apiRouter.ts +4 -2
  79. package/server/rscClient.tsx +7 -1
  80. package/server/rscWorkerHost.ts +33 -11
  81. package/server/sitemap.test.ts +61 -0
  82. package/server/ssrFromRscRenderer.tsx +35 -3
  83. package/server/systemPages.test.tsx +93 -0
  84. package/server/types/react-server-dom-webpack.d.ts +7 -1
  85. package/server/vendor/react-dom-client.ts +1 -1
  86. package/server/vendor/react-dom.ts +3 -2
  87. package/server/vendor/react-jsx-dev-runtime.ts +1 -1
  88. package/server/vendor/react-jsx-runtime.ts +1 -1
  89. package/server/vendor/react-refresh-runtime.ts +2 -2
  90. package/server/vendor/react-server-dom-webpack-client-browser.ts +1 -1
  91. package/server/vendor/react.ts +9 -2
  92. package/server/vendor/scheduler.ts +3 -2
  93. package/server/webRouter.test.ts +49 -0
  94. package/server/webRouter.ts +3 -2
  95. package/service/adapt.ts +5 -1
  96. package/service/predefinedAdaptor/cache.adaptor.ts +2 -1
  97. package/service/predefinedAdaptor/compress.adaptor.test.ts +184 -0
  98. package/service/predefinedAdaptor/database.adaptor.ts +20 -8
  99. package/service/predefinedAdaptor/solidCache.adaptor.test.ts +107 -0
  100. package/service/predefinedAdaptor/solidSqlite.test.ts +457 -0
  101. package/service/predefinedAdaptor/solidSqlite.ts +2 -1
  102. package/service/predefinedAdaptor/websocket.adaptor.ts +15 -3
  103. package/service/serve.ts +7 -3
  104. package/service/service.test.ts +732 -0
  105. package/signal/base.signal.ts +1 -0
  106. package/signal/endpointInfo.ts +7 -0
  107. package/signal/intercept.ts +2 -0
  108. package/signal/middleware.ts +2 -1
  109. package/signal/serverSignal.ts +15 -1
  110. package/signal/signal.test.ts +892 -0
  111. package/signal/signalContext.ts +1 -1
  112. package/signal/signalRegistry.ts +5 -0
  113. package/signal/sliceInfo.ts +3 -0
  114. package/signal/trace.ts +11 -0
  115. package/signal/types.ts +11 -2
  116. package/store/action.ts +5 -3
  117. package/store/stateBuilder.test.ts +168 -0
  118. package/store/stateBuilder.ts +2 -0
  119. package/store/stateInfo.ts +2 -1
  120. package/store/store.test.ts +427 -0
  121. package/store/types.ts +1 -1
  122. package/test/playwright.config.base.ts +28 -5
  123. package/test/playwright.pageAgent.ts +1 -1
  124. package/test/sampleOf.ts +1 -1
  125. package/test/testServer.ts +1 -1
  126. package/tsconfig.json +5 -0
  127. package/ui/BottomSheet.tsx +1 -1
  128. package/ui/Clipboard.tsx +1 -1
  129. package/ui/Constant/schemaDoc.test.ts +113 -0
  130. package/ui/CsrImage.tsx +14 -3
  131. package/ui/Data/ListContainer.tsx +1 -1
  132. package/ui/Data/Pagination.tsx +1 -1
  133. package/ui/Data/QueryMaker.tsx +339 -0
  134. package/ui/Data/index.ts +1 -0
  135. package/ui/DatePicker.tsx +95 -1
  136. package/ui/Dialog/Modal.tsx +1 -1
  137. package/ui/DraggableList.tsx +5 -5
  138. package/ui/Field.tsx +3 -2
  139. package/ui/Image.tsx +3 -2
  140. package/ui/InfiniteScroll.test.tsx +155 -0
  141. package/ui/InfiniteScroll.tsx +1 -0
  142. package/ui/Input.tsx +8 -1
  143. package/ui/Layout/BottomTab.tsx +2 -2
  144. package/ui/Link/SsrLink.tsx +2 -1
  145. package/ui/Load/Page.tsx +1 -1
  146. package/ui/Load/PageCSR.tsx +1 -0
  147. package/ui/Load/Units.tsx +2 -1
  148. package/ui/Load/View.tsx +2 -1
  149. package/ui/Menu.tsx +8 -5
  150. package/ui/Model/EditModal.tsx +5 -2
  151. package/ui/Popconfirm.tsx +4 -2
  152. package/ui/ScreenNavigator.tsx +3 -2
  153. package/ui/Signal/Message.tsx +3 -2
  154. package/ui/Signal/PubSub.tsx +2 -0
  155. package/ui/Signal/Response.tsx +1 -1
  156. package/ui/Signal/WebSocket.tsx +11 -2
  157. package/ui/System/Client.tsx +41 -7
  158. package/ui/System/Common.test.ts +37 -0
  159. package/ui/System/Gtag.tsx +5 -2
  160. package/ui/System/Messages.tsx +2 -2
  161. package/ui/System/Reconnect.tsx +6 -5
  162. package/ui/System/Root.tsx +1 -0
  163. package/ui/System/SSR.tsx +7 -1
  164. package/ui/ToggleSelect.tsx +8 -3
  165. package/ui/Unauthorized.tsx +1 -1
  166. package/webkit/bootCsr.test.tsx +153 -0
  167. package/webkit/bootCsr.tsx +6 -3
  168. package/webkit/lazy.tsx +8 -1
  169. package/webkit/native.test.tsx +341 -0
  170. package/webkit/useCamera.tsx +1 -0
  171. package/webkit/useCodepush.tsx +9 -4
  172. package/webkit/useContact.tsx +1 -0
  173. package/webkit/useCsrValues.ts +10 -6
  174. package/webkit/useDebounce.ts +2 -2
  175. package/webkit/usePurchase.tsx +9 -5
  176. package/webkit/usePushNoti.tsx +1 -0
  177. package/webkit/webkit.test.tsx +531 -0
  178. package/types/base/base.d.ts +0 -53
  179. package/types/base/baseEnv.d.ts +0 -68
  180. package/types/base/index.d.ts +0 -6
  181. package/types/base/primitiveRegistry.d.ts +0 -134
  182. package/types/base/symbols.d.ts +0 -11
  183. package/types/base/types.d.ts +0 -68
  184. package/types/base/utils.d.ts +0 -4
  185. package/types/base.d.ts +0 -1
  186. package/types/capacitor.base.config.d.ts +0 -3
  187. package/types/client/capacitor.d.ts +0 -233
  188. package/types/client/clientRuntime.d.ts +0 -63
  189. package/types/client/cookie.d.ts +0 -33
  190. package/types/client/createFont.d.ts +0 -6
  191. package/types/client/csrTypes.d.ts +0 -262
  192. package/types/client/decorators.d.ts +0 -7
  193. package/types/client/device.d.ts +0 -41
  194. package/types/client/fetch.d.ts +0 -1
  195. package/types/client/index.d.ts +0 -14
  196. package/types/client/locale.d.ts +0 -5
  197. package/types/client/makePageProto.d.ts +0 -17002
  198. package/types/client/router.d.ts +0 -66
  199. package/types/client/rscNavigation.d.ts +0 -19
  200. package/types/client/storage.d.ts +0 -5
  201. package/types/client/translator.d.ts +0 -18
  202. package/types/client/types.d.ts +0 -145
  203. package/types/client/useClient.d.ts +0 -1
  204. package/types/client.d.ts +0 -1
  205. package/types/common/Logger.d.ts +0 -41
  206. package/types/common/applyMixins.d.ts +0 -3
  207. package/types/common/capitalize.d.ts +0 -2
  208. package/types/common/deepObjectify.d.ts +0 -4
  209. package/types/common/fileUpload.d.ts +0 -28
  210. package/types/common/formatNumber.d.ts +0 -1
  211. package/types/common/formatPhone.d.ts +0 -2
  212. package/types/common/getAllPropertyDescriptors.d.ts +0 -5
  213. package/types/common/hmrPhase.d.ts +0 -6
  214. package/types/common/httpClient.d.ts +0 -20
  215. package/types/common/index.d.ts +0 -29
  216. package/types/common/isDayjs.d.ts +0 -2
  217. package/types/common/isEmail.d.ts +0 -2
  218. package/types/common/isPhoneNumber.d.ts +0 -2
  219. package/types/common/isQueryEqual.d.ts +0 -1
  220. package/types/common/isValidDate.d.ts +0 -2
  221. package/types/common/jwtDecode.d.ts +0 -2
  222. package/types/common/localeConfig.d.ts +0 -11
  223. package/types/common/lowerlize.d.ts +0 -2
  224. package/types/common/mergeVersion.d.ts +0 -10
  225. package/types/common/objectify.d.ts +0 -1
  226. package/types/common/pathGet.d.ts +0 -1
  227. package/types/common/pathSet.d.ts +0 -3
  228. package/types/common/randomPick.d.ts +0 -1
  229. package/types/common/randomPicks.d.ts +0 -1
  230. package/types/common/routeConvention.d.ts +0 -35
  231. package/types/common/sleep.d.ts +0 -2
  232. package/types/common/splitVersion.d.ts +0 -11
  233. package/types/common/subRoute.d.ts +0 -7
  234. package/types/common/types.d.ts +0 -14
  235. package/types/common.d.ts +0 -1
  236. package/types/constant/constantRegistry.d.ts +0 -89
  237. package/types/constant/crystalize.d.ts +0 -4
  238. package/types/constant/deserialize.d.ts +0 -7
  239. package/types/constant/fieldInfo.d.ts +0 -153
  240. package/types/constant/getDefault.d.ts +0 -3
  241. package/types/constant/immerify.d.ts +0 -2
  242. package/types/constant/index.d.ts +0 -10
  243. package/types/constant/purify.d.ts +0 -12
  244. package/types/constant/serialize.d.ts +0 -11
  245. package/types/constant/types.d.ts +0 -77
  246. package/types/constant/via.d.ts +0 -52
  247. package/types/constant.d.ts +0 -1
  248. package/types/dictionary/base.dictionary.d.ts +0 -1
  249. package/types/dictionary/dictInfo.d.ts +0 -206
  250. package/types/dictionary/dictionary.d.ts +0 -11
  251. package/types/dictionary/index.d.ts +0 -4
  252. package/types/dictionary/locale.d.ts +0 -128
  253. package/types/dictionary/trans.d.ts +0 -82
  254. package/types/dictionary.d.ts +0 -1
  255. package/types/document/by.d.ts +0 -20
  256. package/types/document/dataLoader.d.ts +0 -36
  257. package/types/document/database.d.ts +0 -96
  258. package/types/document/databaseRegistry.d.ts +0 -36
  259. package/types/document/documentQuery.d.ts +0 -105
  260. package/types/document/documentSchema.d.ts +0 -33
  261. package/types/document/filterMeta.d.ts +0 -84
  262. package/types/document/index.d.ts +0 -11
  263. package/types/document/into.d.ts +0 -65
  264. package/types/document/loaderInfo.d.ts +0 -35
  265. package/types/document/schema.d.ts +0 -5
  266. package/types/document/types.d.ts +0 -36
  267. package/types/document.d.ts +0 -1
  268. package/types/fetch/client/fetchClient.d.ts +0 -78
  269. package/types/fetch/client/httpClient.d.ts +0 -39
  270. package/types/fetch/client/index.d.ts +0 -3
  271. package/types/fetch/client/wsClient.d.ts +0 -37
  272. package/types/fetch/client.d.ts +0 -1
  273. package/types/fetch/fetchType/appliedReturn.type.d.ts +0 -67
  274. package/types/fetch/fetchType/buildFetch.type.d.ts +0 -11
  275. package/types/fetch/fetchType/clientSignal.type.d.ts +0 -16
  276. package/types/fetch/fetchType/endpointFetch.type.d.ts +0 -29
  277. package/types/fetch/fetchType/index.d.ts +0 -5
  278. package/types/fetch/fetchType/sliceFetch.type.d.ts +0 -76
  279. package/types/fetch/fetchType.d.ts +0 -1
  280. package/types/fetch/index.d.ts +0 -4
  281. package/types/fetch/requestStorage.d.ts +0 -29
  282. package/types/fetch/serializer/fetch.serializer.d.ts +0 -14
  283. package/types/fetch/serializer/index.d.ts +0 -1
  284. package/types/fetch/serializer.d.ts +0 -1
  285. package/types/fetch/types.d.ts +0 -19
  286. package/types/fetch.d.ts +0 -1
  287. package/types/index.d.ts +0 -213
  288. package/types/server/akanApp.d.ts +0 -17
  289. package/types/server/akanAppHeaders.d.ts +0 -1
  290. package/types/server/akanLib.d.ts +0 -33
  291. package/types/server/akanOption.d.ts +0 -15
  292. package/types/server/akanServer.d.ts +0 -47
  293. package/types/server/artifact/builderRpc.d.ts +0 -42
  294. package/types/server/artifact/index.d.ts +0 -6
  295. package/types/server/artifact/ipcTypes.d.ts +0 -73
  296. package/types/server/artifact/manifestTypes.d.ts +0 -16
  297. package/types/server/artifact/routeClientCache.d.ts +0 -45
  298. package/types/server/artifact/routeSeedIndexStore.d.ts +0 -43
  299. package/types/server/artifact/routesManifestStore.d.ts +0 -16
  300. package/types/server/artifact.d.ts +0 -1
  301. package/types/server/decorators.d.ts +0 -6
  302. package/types/server/di/diLifecycle.d.ts +0 -45
  303. package/types/server/di/index.d.ts +0 -4
  304. package/types/server/di/predefinedAdaptor.d.ts +0 -33
  305. package/types/server/di/resolveAdaptorHierarchy.d.ts +0 -21
  306. package/types/server/di/resolveHierarchy.d.ts +0 -11
  307. package/types/server/di/resolveServiceHierarchy.d.ts +0 -13
  308. package/types/server/di/utils.d.ts +0 -14
  309. package/types/server/di.d.ts +0 -1
  310. package/types/server/hmr/changeBatch.d.ts +0 -13
  311. package/types/server/hmr/clientScript.d.ts +0 -1
  312. package/types/server/hmr/devHmrController.d.ts +0 -24
  313. package/types/server/hmr/index.d.ts +0 -4
  314. package/types/server/hmr/wsHub.d.ts +0 -45
  315. package/types/server/hmr.d.ts +0 -1
  316. package/types/server/imageOptimizer.d.ts +0 -12
  317. package/types/server/imageOptimizerError.d.ts +0 -4
  318. package/types/server/index.d.ts +0 -14
  319. package/types/server/lifecycle/shutdownManager.d.ts +0 -13
  320. package/types/server/logging/rotatingLogWriter.d.ts +0 -20
  321. package/types/server/processMetricsCollector.d.ts +0 -10
  322. package/types/server/proxy/akanResponse.d.ts +0 -7
  323. package/types/server/proxy/bunRequestFields.d.ts +0 -1
  324. package/types/server/proxy/hostBasePathWebProxy.d.ts +0 -6
  325. package/types/server/proxy/index.d.ts +0 -6
  326. package/types/server/proxy/localeWebProxy.d.ts +0 -5
  327. package/types/server/proxy/types.d.ts +0 -37
  328. package/types/server/proxy/webProxyRunner.d.ts +0 -7
  329. package/types/server/proxy.d.ts +0 -1
  330. package/types/server/resolver/database.resolver.d.ts +0 -6
  331. package/types/server/resolver/index.d.ts +0 -3
  332. package/types/server/resolver/service.resolver.d.ts +0 -7
  333. package/types/server/resolver/signal.resolver.d.ts +0 -29
  334. package/types/server/resolver.d.ts +0 -1
  335. package/types/server/robots.d.ts +0 -1
  336. package/types/server/routeElementComposer.d.ts +0 -41
  337. package/types/server/routeTreeBuilder.d.ts +0 -28
  338. package/types/server/routing/apiRouter.d.ts +0 -61
  339. package/types/server/rscClient.d.ts +0 -15
  340. package/types/server/rscWorker.d.ts +0 -3
  341. package/types/server/rscWorkerHost.d.ts +0 -83
  342. package/types/server/sitemap.d.ts +0 -13
  343. package/types/server/ssrFromRscRenderer.d.ts +0 -6
  344. package/types/server/ssrTypes.d.ts +0 -47
  345. package/types/server/systemPages.d.ts +0 -27
  346. package/types/server/types/react-server-dom-webpack.d.ts +0 -91
  347. package/types/server/types.d.ts +0 -67
  348. package/types/server/vendor/akanjs-base.d.ts +0 -1
  349. package/types/server/vendor/akanjs-common.d.ts +0 -1
  350. package/types/server/vendor/akanjs-constant.d.ts +0 -1
  351. package/types/server/vendor/akanjs-store.d.ts +0 -1
  352. package/types/server/vendor/react-dom-client.d.ts +0 -3
  353. package/types/server/vendor/react-dom.d.ts +0 -3
  354. package/types/server/vendor/react-jsx-dev-runtime.d.ts +0 -3
  355. package/types/server/vendor/react-jsx-runtime.d.ts +0 -3
  356. package/types/server/vendor/react-refresh-runtime.d.ts +0 -3
  357. package/types/server/vendor/react-server-dom-webpack-client-browser.d.ts +0 -3
  358. package/types/server/vendor/react.d.ts +0 -11
  359. package/types/server/vendor/scheduler.d.ts +0 -3
  360. package/types/server/webRouter.d.ts +0 -44
  361. package/types/server.d.ts +0 -1
  362. package/types/service/adapt.d.ts +0 -15
  363. package/types/service/base.service.d.ts +0 -18
  364. package/types/service/index.d.ts +0 -10
  365. package/types/service/injectInfo.d.ts +0 -102
  366. package/types/service/ipcTypes.d.ts +0 -194
  367. package/types/service/predefinedAdaptor/cache.adaptor.d.ts +0 -46
  368. package/types/service/predefinedAdaptor/compress.adaptor.d.ts +0 -22
  369. package/types/service/predefinedAdaptor/database.adaptor.d.ts +0 -329
  370. package/types/service/predefinedAdaptor/index.d.ts +0 -13
  371. package/types/service/predefinedAdaptor/logging.adaptor.d.ts +0 -20
  372. package/types/service/predefinedAdaptor/queue.adaptor.d.ts +0 -22
  373. package/types/service/predefinedAdaptor/role.adaptor.d.ts +0 -17
  374. package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +0 -60
  375. package/types/service/predefinedAdaptor/solidCache.adaptor.d.ts +0 -25
  376. package/types/service/predefinedAdaptor/solidPubsub.adaptor.d.ts +0 -20
  377. package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +0 -19
  378. package/types/service/predefinedAdaptor/solidSqlite.d.ts +0 -24
  379. package/types/service/predefinedAdaptor/sqlitePath.d.ts +0 -9
  380. package/types/service/predefinedAdaptor/storage.adaptor.d.ts +0 -80
  381. package/types/service/predefinedAdaptor/websocket.adaptor.d.ts +0 -49
  382. package/types/service/predefinedAdaptor.d.ts +0 -1
  383. package/types/service/serve.d.ts +0 -31
  384. package/types/service/serviceModule.d.ts +0 -50
  385. package/types/service/serviceRegistry.d.ts +0 -9
  386. package/types/service/types.d.ts +0 -55
  387. package/types/service.d.ts +0 -1
  388. package/types/signal/base.signal.d.ts +0 -41
  389. package/types/signal/endpoint.d.ts +0 -28
  390. package/types/signal/endpointInfo.d.ts +0 -77
  391. package/types/signal/exception.d.ts +0 -54786
  392. package/types/signal/guard.d.ts +0 -10
  393. package/types/signal/guards.d.ts +0 -10
  394. package/types/signal/index.d.ts +0 -19
  395. package/types/signal/intercept.d.ts +0 -17
  396. package/types/signal/internal.d.ts +0 -18
  397. package/types/signal/internalArg.d.ts +0 -31
  398. package/types/signal/internalInfo.d.ts +0 -45
  399. package/types/signal/middleware.d.ts +0 -51
  400. package/types/signal/serializer/fetch.serializer.d.ts +0 -14
  401. package/types/signal/serializer/index.d.ts +0 -1
  402. package/types/signal/serializer.d.ts +0 -1
  403. package/types/signal/serverSignal.d.ts +0 -36
  404. package/types/signal/signalContext.d.ts +0 -96
  405. package/types/signal/signalRegistry.d.ts +0 -49
  406. package/types/signal/slice.d.ts +0 -42
  407. package/types/signal/sliceInfo.d.ts +0 -51
  408. package/types/signal/trace.d.ts +0 -97
  409. package/types/signal/types.d.ts +0 -142
  410. package/types/signal.d.ts +0 -1
  411. package/types/store/action.d.ts +0 -145
  412. package/types/store/baseSt.d.ts +0 -412
  413. package/types/store/hooks.d.ts +0 -1
  414. package/types/store/index.d.ts +0 -8
  415. package/types/store/rootStore.d.ts +0 -22
  416. package/types/store/state.d.ts +0 -91
  417. package/types/store/stateBuilder.d.ts +0 -141
  418. package/types/store/stateInfo.d.ts +0 -23
  419. package/types/store/store.d.ts +0 -36
  420. package/types/store/storeInstance.d.ts +0 -38
  421. package/types/store/storeRegistry.d.ts +0 -13
  422. package/types/store/types.d.ts +0 -42
  423. package/types/store/withSelector.d.ts +0 -42
  424. package/types/store.d.ts +0 -1
  425. package/types/test/index.d.ts +0 -4
  426. package/types/test/playwright.config.base.d.ts +0 -9
  427. package/types/test/playwright.pageAgent.d.ts +0 -12
  428. package/types/test/sample.d.ts +0 -15
  429. package/types/test/sampleOf.d.ts +0 -5
  430. package/types/test/signalTest.preload.d.ts +0 -1
  431. package/types/test/signalTestRuntime.d.ts +0 -29
  432. package/types/test/testServer.d.ts +0 -26
  433. package/types/test.d.ts +0 -1
  434. package/types/ui/BottomSheet.d.ts +0 -13
  435. package/types/ui/Button.d.ts +0 -11
  436. package/types/ui/ClientSide.d.ts +0 -8
  437. package/types/ui/Clipboard.d.ts +0 -7
  438. package/types/ui/Constant/Doc.d.ts +0 -29
  439. package/types/ui/Constant/Mermaid.d.ts +0 -8
  440. package/types/ui/Constant/index.d.ts +0 -6
  441. package/types/ui/Constant/schemaDoc.d.ts +0 -84
  442. package/types/ui/Constant.d.ts +0 -1
  443. package/types/ui/Copy.d.ts +0 -10
  444. package/types/ui/CsrImage.d.ts +0 -18
  445. package/types/ui/Data/CardList.d.ts +0 -36
  446. package/types/ui/Data/Dashboard.d.ts +0 -11
  447. package/types/ui/Data/Insight.d.ts +0 -9
  448. package/types/ui/Data/Item.d.ts +0 -75
  449. package/types/ui/Data/ListContainer.d.ts +0 -49
  450. package/types/ui/Data/Pagination.d.ts +0 -9
  451. package/types/ui/Data/QueryMaker.d.ts +0 -8
  452. package/types/ui/Data/TableList.d.ts +0 -26
  453. package/types/ui/Data/index.d.ts +0 -9
  454. package/types/ui/Data/index_.d.ts +0 -8
  455. package/types/ui/Data.d.ts +0 -1
  456. package/types/ui/DatePicker.d.ts +0 -36
  457. package/types/ui/Dialog/Action.d.ts +0 -5
  458. package/types/ui/Dialog/Close.d.ts +0 -6
  459. package/types/ui/Dialog/Content.d.ts +0 -6
  460. package/types/ui/Dialog/Modal.d.ts +0 -9
  461. package/types/ui/Dialog/Provider.d.ts +0 -11
  462. package/types/ui/Dialog/Title.d.ts +0 -5
  463. package/types/ui/Dialog/Trigger.d.ts +0 -6
  464. package/types/ui/Dialog/context.d.ts +0 -10
  465. package/types/ui/Dialog/index.d.ts +0 -9
  466. package/types/ui/Dialog.d.ts +0 -1
  467. package/types/ui/DragAction.d.ts +0 -24
  468. package/types/ui/DraggableList.d.ts +0 -31
  469. package/types/ui/Dropdown.d.ts +0 -14
  470. package/types/ui/Empty.d.ts +0 -12
  471. package/types/ui/Field.d.ts +0 -411
  472. package/types/ui/FontFace.d.ts +0 -6
  473. package/types/ui/Image.d.ts +0 -49
  474. package/types/ui/InfiniteScroll.d.ts +0 -10
  475. package/types/ui/Input.d.ts +0 -95
  476. package/types/ui/KeyboardAvoiding.d.ts +0 -8
  477. package/types/ui/Layout/BottomAction.d.ts +0 -6
  478. package/types/ui/Layout/BottomInset.d.ts +0 -7
  479. package/types/ui/Layout/BottomTab.d.ts +0 -14
  480. package/types/ui/Layout/Header.d.ts +0 -8
  481. package/types/ui/Layout/LeftSider.d.ts +0 -9
  482. package/types/ui/Layout/Navbar.d.ts +0 -11
  483. package/types/ui/Layout/RightSider.d.ts +0 -10
  484. package/types/ui/Layout/Sider.d.ts +0 -7
  485. package/types/ui/Layout/Template.d.ts +0 -7
  486. package/types/ui/Layout/TopLeftAction.d.ts +0 -6
  487. package/types/ui/Layout/Unit.d.ts +0 -9
  488. package/types/ui/Layout/View.d.ts +0 -8
  489. package/types/ui/Layout/Zone.d.ts +0 -6
  490. package/types/ui/Layout/index.d.ts +0 -14
  491. package/types/ui/Layout.d.ts +0 -1
  492. package/types/ui/Link/Back.d.ts +0 -7
  493. package/types/ui/Link/Close.d.ts +0 -7
  494. package/types/ui/Link/CsrLink.d.ts +0 -2
  495. package/types/ui/Link/Lang.d.ts +0 -8
  496. package/types/ui/Link/SsrLink.d.ts +0 -2
  497. package/types/ui/Link/index.d.ts +0 -10
  498. package/types/ui/Link/types.d.ts +0 -42
  499. package/types/ui/Link.d.ts +0 -1
  500. package/types/ui/Load/Edit.d.ts +0 -4
  501. package/types/ui/Load/Edit_Client.d.ts +0 -27
  502. package/types/ui/Load/Page.d.ts +0 -15
  503. package/types/ui/Load/PageCSR.d.ts +0 -3
  504. package/types/ui/Load/Pagination.d.ts +0 -8
  505. package/types/ui/Load/Units.d.ts +0 -30
  506. package/types/ui/Load/View.d.ts +0 -22
  507. package/types/ui/Load/index.d.ts +0 -8
  508. package/types/ui/Load/index_.d.ts +0 -3
  509. package/types/ui/Load.d.ts +0 -1
  510. package/types/ui/Loading/Area.d.ts +0 -1
  511. package/types/ui/Loading/Button.d.ts +0 -7
  512. package/types/ui/Loading/Input.d.ts +0 -7
  513. package/types/ui/Loading/ProgressBar.d.ts +0 -6
  514. package/types/ui/Loading/Skeleton.d.ts +0 -7
  515. package/types/ui/Loading/Spin.d.ts +0 -7
  516. package/types/ui/Loading/index.d.ts +0 -8
  517. package/types/ui/Loading.d.ts +0 -1
  518. package/types/ui/Menu.d.ts +0 -26
  519. package/types/ui/Modal.d.ts +0 -19
  520. package/types/ui/Model/AdminPanel.d.ts +0 -31
  521. package/types/ui/Model/Edit.d.ts +0 -16
  522. package/types/ui/Model/EditModal.d.ts +0 -52
  523. package/types/ui/Model/EditWrapper.d.ts +0 -13
  524. package/types/ui/Model/LoadInit.d.ts +0 -10
  525. package/types/ui/Model/New.d.ts +0 -16
  526. package/types/ui/Model/NewWrapper.d.ts +0 -13
  527. package/types/ui/Model/NewWrapper_Client.d.ts +0 -13
  528. package/types/ui/Model/Remove.d.ts +0 -13
  529. package/types/ui/Model/RemoveWrapper.d.ts +0 -12
  530. package/types/ui/Model/SureToRemove.d.ts +0 -11
  531. package/types/ui/Model/View.d.ts +0 -19
  532. package/types/ui/Model/ViewEditModal.d.ts +0 -12
  533. package/types/ui/Model/ViewModal.d.ts +0 -14
  534. package/types/ui/Model/ViewWrapper.d.ts +0 -12
  535. package/types/ui/Model/index.d.ts +0 -16
  536. package/types/ui/Model/index_.d.ts +0 -14
  537. package/types/ui/Model.d.ts +0 -1
  538. package/types/ui/More.d.ts +0 -12
  539. package/types/ui/ObjectId.d.ts +0 -5
  540. package/types/ui/Pagination.d.ts +0 -20
  541. package/types/ui/Popconfirm.d.ts +0 -28
  542. package/types/ui/Portal.d.ts +0 -6
  543. package/types/ui/Radio.d.ts +0 -19
  544. package/types/ui/RecentTime.d.ts +0 -12
  545. package/types/ui/Refresh.d.ts +0 -7
  546. package/types/ui/ScreenNavigator.d.ts +0 -18
  547. package/types/ui/Select.d.ts +0 -43
  548. package/types/ui/Signal/Arg.d.ts +0 -78
  549. package/types/ui/Signal/Doc.d.ts +0 -33
  550. package/types/ui/Signal/Listener.d.ts +0 -9
  551. package/types/ui/Signal/Message.d.ts +0 -23
  552. package/types/ui/Signal/Object.d.ts +0 -20
  553. package/types/ui/Signal/PubSub.d.ts +0 -24
  554. package/types/ui/Signal/Request.d.ts +0 -8
  555. package/types/ui/Signal/Response.d.ts +0 -14
  556. package/types/ui/Signal/RestApi.d.ts +0 -40
  557. package/types/ui/Signal/WebSocket.d.ts +0 -11
  558. package/types/ui/Signal/index.d.ts +0 -18
  559. package/types/ui/Signal/makeExample.d.ts +0 -4
  560. package/types/ui/Signal/style.d.ts +0 -15
  561. package/types/ui/Signal.d.ts +0 -1
  562. package/types/ui/System/CSR.d.ts +0 -32
  563. package/types/ui/System/Client.d.ts +0 -47
  564. package/types/ui/System/Common.d.ts +0 -47
  565. package/types/ui/System/DevModeToggle.d.ts +0 -1
  566. package/types/ui/System/Gtag.d.ts +0 -4
  567. package/types/ui/System/Messages.d.ts +0 -1
  568. package/types/ui/System/Reconnect.d.ts +0 -1
  569. package/types/ui/System/Root.d.ts +0 -6
  570. package/types/ui/System/SSR.d.ts +0 -24
  571. package/types/ui/System/SelectLanguage.d.ts +0 -5
  572. package/types/ui/System/ThemeToggle.d.ts +0 -4
  573. package/types/ui/System/index.d.ts +0 -12
  574. package/types/ui/System.d.ts +0 -1
  575. package/types/ui/Tab/Menu.d.ts +0 -12
  576. package/types/ui/Tab/Menus.d.ts +0 -6
  577. package/types/ui/Tab/Panel.d.ts +0 -8
  578. package/types/ui/Tab/Provider.d.ts +0 -10
  579. package/types/ui/Tab/context.d.ts +0 -9
  580. package/types/ui/Tab/index.d.ts +0 -7
  581. package/types/ui/Tab.d.ts +0 -1
  582. package/types/ui/Table.d.ts +0 -41
  583. package/types/ui/ToggleSelect.d.ts +0 -33
  584. package/types/ui/Unauthorized.d.ts +0 -9
  585. package/types/ui/animated.d.ts +0 -5
  586. package/types/ui/fontCss.d.ts +0 -11
  587. package/types/ui/index.d.ts +0 -44
  588. package/types/ui.d.ts +0 -1
  589. package/types/webkit/bootCsr.d.ts +0 -16
  590. package/types/webkit/createRobotPage.d.ts +0 -15
  591. package/types/webkit/createSitemapPage.d.ts +0 -4
  592. package/types/webkit/index.d.ts +0 -17
  593. package/types/webkit/lazy.d.ts +0 -11
  594. package/types/webkit/types.d.ts +0 -7
  595. package/types/webkit/useCamera.d.ts +0 -19
  596. package/types/webkit/useCodepush.d.ts +0 -16
  597. package/types/webkit/useContact.d.ts +0 -11
  598. package/types/webkit/useCsrValues.d.ts +0 -30
  599. package/types/webkit/useDebounce.d.ts +0 -2
  600. package/types/webkit/useFetch.d.ts +0 -18
  601. package/types/webkit/useGeoLocation.d.ts +0 -8
  602. package/types/webkit/useHistory.d.ts +0 -23
  603. package/types/webkit/useInterval.d.ts +0 -2
  604. package/types/webkit/useLocation.d.ts +0 -8
  605. package/types/webkit/usePurchase.d.ts +0 -19
  606. package/types/webkit/usePushNoti.d.ts +0 -7
  607. package/types/webkit/useThrottle.d.ts +0 -2
  608. package/types/webkit.d.ts +0 -1
@@ -0,0 +1,838 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { DataList, Int } from "akanjs/base";
3
+ import { ConstantRegistry, via } from "akanjs/constant";
4
+ import type { SerializedArg, SerializedSignal } from "akanjs/signal";
5
+ import { FetchClient } from "./client/fetchClient";
6
+ import { HttpClient } from "./client/httpClient";
7
+ import { WsClient } from "./client/wsClient";
8
+ import {
9
+ cookies,
10
+ getRequestTheme,
11
+ headers,
12
+ memoizeRequestQuery,
13
+ parseCookieHeader,
14
+ requestStorage,
15
+ setRequestTheme,
16
+ } from "./requestStorage";
17
+
18
+ type FetchCall = { url: string; init?: RequestInit };
19
+ const originalFetch = globalThis.fetch;
20
+ const originalWebSocket = globalThis.WebSocket;
21
+ const originalSetTimeout = globalThis.setTimeout;
22
+ const originalClearTimeout = globalThis.clearTimeout;
23
+ const originalEnv = {
24
+ appName: process.env.AKAN_PUBLIC_APP_NAME,
25
+ repoName: process.env.AKAN_PUBLIC_REPO_NAME,
26
+ serveDomain: process.env.AKAN_PUBLIC_SERVE_DOMAIN,
27
+ operationMode: process.env.AKAN_PUBLIC_OPERATION_MODE,
28
+ };
29
+ const fetchCalls: FetchCall[] = [];
30
+ const jsonResponses: unknown[] = [];
31
+ const responseStatuses: number[] = [];
32
+
33
+ const setMockFetch = () => {
34
+ fetchCalls.length = 0;
35
+ jsonResponses.length = 0;
36
+ globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
37
+ fetchCalls.push({ url: String(url), init });
38
+ const value = jsonResponses.length ? jsonResponses.shift() : { ok: true };
39
+ const status = responseStatuses.length ? responseStatuses.shift() : 200;
40
+ return Response.json(value, { status });
41
+ }) as typeof fetch;
42
+ };
43
+ const setAkanPublicEnv = () => {
44
+ process.env.AKAN_PUBLIC_APP_NAME = "fetchTest";
45
+ process.env.AKAN_PUBLIC_REPO_NAME = "akan";
46
+ process.env.AKAN_PUBLIC_SERVE_DOMAIN = "example.test";
47
+ process.env.AKAN_PUBLIC_OPERATION_MODE = "local";
48
+ };
49
+
50
+ afterEach(() => {
51
+ globalThis.fetch = originalFetch;
52
+ globalThis.WebSocket = originalWebSocket;
53
+ globalThis.setTimeout = originalSetTimeout;
54
+ globalThis.clearTimeout = originalClearTimeout;
55
+ if (originalEnv.appName === undefined) delete process.env.AKAN_PUBLIC_APP_NAME;
56
+ else process.env.AKAN_PUBLIC_APP_NAME = originalEnv.appName;
57
+ if (originalEnv.repoName === undefined) delete process.env.AKAN_PUBLIC_REPO_NAME;
58
+ else process.env.AKAN_PUBLIC_REPO_NAME = originalEnv.repoName;
59
+ if (originalEnv.serveDomain === undefined) delete process.env.AKAN_PUBLIC_SERVE_DOMAIN;
60
+ else process.env.AKAN_PUBLIC_SERVE_DOMAIN = originalEnv.serveDomain;
61
+ if (originalEnv.operationMode === undefined) delete process.env.AKAN_PUBLIC_OPERATION_MODE;
62
+ else process.env.AKAN_PUBLIC_OPERATION_MODE = originalEnv.operationMode;
63
+ fetchCalls.length = 0;
64
+ jsonResponses.length = 0;
65
+ responseStatuses.length = 0;
66
+ });
67
+
68
+ const arg = (type: SerializedArg["type"], name: string, extra: Partial<SerializedArg> = {}): SerializedArg => ({
69
+ type,
70
+ name,
71
+ refName: "String",
72
+ ...extra,
73
+ });
74
+
75
+ class FakeWebSocket {
76
+ static CONNECTING = 0;
77
+ static OPEN = 1;
78
+ static CLOSING = 2;
79
+ static CLOSED = 3;
80
+ static instances: FakeWebSocket[] = [];
81
+ readyState = FakeWebSocket.CONNECTING;
82
+ sent: string[] = [];
83
+ onopen: ((event: unknown) => void) | null = null;
84
+ onmessage: ((event: { data: unknown }) => void) | null = null;
85
+ onerror: ((event: unknown) => void) | null = null;
86
+ onclose: ((event: { code: number; reason: string }) => void) | null = null;
87
+
88
+ constructor(readonly url: string) {
89
+ FakeWebSocket.instances.push(this);
90
+ }
91
+
92
+ open() {
93
+ this.readyState = FakeWebSocket.OPEN;
94
+ this.onopen?.({});
95
+ }
96
+
97
+ receive(data: unknown) {
98
+ this.onmessage?.({ data: typeof data === "string" ? data : JSON.stringify(data) });
99
+ }
100
+
101
+ close() {
102
+ this.readyState = FakeWebSocket.CLOSED;
103
+ this.onclose?.({ code: 1000, reason: "closed" });
104
+ }
105
+
106
+ send(data: string) {
107
+ this.sent.push(data);
108
+ }
109
+ }
110
+
111
+ class TestErr extends Error {
112
+ readonly error: string;
113
+ readonly statusCode?: number;
114
+ readonly data?: Record<string, unknown>;
115
+ readonly details?: unknown;
116
+ readonly path?: string;
117
+ readonly timestamp?: string;
118
+
119
+ constructor(
120
+ key: string,
121
+ data?: Record<string, unknown>,
122
+ option: { statusCode?: number; details?: unknown; path?: string; timestamp?: string } = {},
123
+ ) {
124
+ super(key);
125
+ this.error = key;
126
+ this.statusCode = option.statusCode ?? 400;
127
+ this.data = data;
128
+ this.details = option.details;
129
+ this.path = option.path;
130
+ this.timestamp = option.timestamp;
131
+ }
132
+
133
+ static fromJSON(payload: {
134
+ error: string;
135
+ statusCode?: number;
136
+ data?: Record<string, unknown>;
137
+ details?: unknown;
138
+ path?: string;
139
+ timestamp?: string;
140
+ }) {
141
+ return new TestErr(payload.error, payload.data, payload);
142
+ }
143
+ }
144
+
145
+ const setFakeWebSocket = () => {
146
+ FakeWebSocket.instances = [];
147
+ globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket;
148
+ Object.assign(globalThis.WebSocket, {
149
+ CONNECTING: FakeWebSocket.CONNECTING,
150
+ OPEN: FakeWebSocket.OPEN,
151
+ CLOSING: FakeWebSocket.CLOSING,
152
+ CLOSED: FakeWebSocket.CLOSED,
153
+ });
154
+ };
155
+
156
+ const FetchTestNested = via((f) => ({
157
+ label: f(String),
158
+ }));
159
+ ConstantRegistry.buildScalar("fetchTestNested", FetchTestNested, { FetchTestNested });
160
+ const FetchTestInput = via((f) => ({
161
+ title: f(String),
162
+ count: f(Int, { default: 0 }),
163
+ nested: f(FetchTestNested),
164
+ }));
165
+ const FetchTestObject = via(FetchTestInput, (f) => ({
166
+ memo: f(String).optional(),
167
+ }));
168
+ const FetchTestLight = via(FetchTestObject, ["title"] as const, () => ({}));
169
+ const FetchTestFull = via(FetchTestObject, FetchTestLight, () => ({}));
170
+ const FetchTestInsight = via(FetchTestFull, (f) => ({
171
+ count: f(Int, { default: 0, accumulate: {} }),
172
+ }));
173
+ ConstantRegistry.buildModel(
174
+ "fetchTestItem",
175
+ FetchTestInput,
176
+ FetchTestObject,
177
+ FetchTestFull,
178
+ FetchTestLight,
179
+ FetchTestInsight,
180
+ {
181
+ FetchTestInput,
182
+ FetchTestObject,
183
+ FetchTestLight,
184
+ FetchTestFull,
185
+ FetchTestInsight,
186
+ },
187
+ );
188
+
189
+ const serviceSignal: SerializedSignal = {
190
+ endpoint: {
191
+ getThing: {
192
+ type: "query",
193
+ path: "/custom/:id",
194
+ args: [
195
+ arg("param", "id", { refName: "ID" }),
196
+ arg("search", "tags", { arrDepth: 1 }),
197
+ arg("search", "empty", { nullable: true }),
198
+ ],
199
+ returns: { refName: "String" },
200
+ },
201
+ createThing: {
202
+ type: "mutation",
203
+ args: [arg("body", "title"), arg("body", "count", { refName: "Int" })],
204
+ returns: { refName: "fetchTestItem", modelType: "full" },
205
+ },
206
+ uploadThing: {
207
+ type: "mutation",
208
+ args: [arg("upload", "file", { refName: "Upload" }), arg("body", "title")],
209
+ returns: { refName: "String" },
210
+ },
211
+ sendThing: {
212
+ type: "message",
213
+ args: [arg("msg", "text")],
214
+ returns: { refName: "String" },
215
+ },
216
+ roomThing: {
217
+ type: "pubsub",
218
+ args: [arg("room", "roomId")],
219
+ returns: { refName: "fetchTestItem", modelType: "light" },
220
+ },
221
+ },
222
+ };
223
+
224
+ const databaseSignal: SerializedSignal = {
225
+ prefix: "fetchTest",
226
+ getGuards: ["Public"],
227
+ cruGuards: ["Admin"],
228
+ slice: {
229
+ "": { args: [arg("search", "query", { refName: "Any", nullable: true })] },
230
+ byOwner: { args: [arg("param", "ownerId", { refName: "ID" })], guards: ["Public"] },
231
+ },
232
+ filter: {
233
+ sortKeys: ["latest", "oldest"],
234
+ filter: {
235
+ byTitle: [arg("param", "title")],
236
+ },
237
+ },
238
+ endpoint: {
239
+ custom: {
240
+ type: "query",
241
+ args: [arg("search", "q")],
242
+ returns: { refName: "String" },
243
+ },
244
+ },
245
+ };
246
+
247
+ describe("HttpClient", () => {
248
+ test("builds paths, urls, and bodies", () => {
249
+ const argMap = new Map<string, unknown>([
250
+ ["id", "id-1"],
251
+ ["tags", ["a", "b"]],
252
+ ["q", "hello"],
253
+ ]);
254
+
255
+ expect(HttpClient.makePath("getThing", [], undefined)).toBe("/getThing");
256
+ expect(HttpClient.makePath("getThing", [arg("param", "id"), arg("param", "childId")], "api")).toBe(
257
+ "/api/getThing/:id/:childId",
258
+ );
259
+ expect(
260
+ HttpClient.makeUrl("/items/:id/:missing", [arg("search", "tags", { arrDepth: 1 }), arg("search", "q")], argMap),
261
+ ).toBe("/items/id-1/:missing?tags=a&tags=b&q=hello");
262
+ expect(HttpClient.makeUrl("/items", [arg("search", "empty", { nullable: true })], new Map())).toBe("/items");
263
+ expect(
264
+ FetchClient.makeHttpUrl(
265
+ "ping",
266
+ { type: "query", args: [], returns: { refName: "String" } },
267
+ undefined,
268
+ new Map(),
269
+ ),
270
+ ).toBe("/ping");
271
+ expect(
272
+ FetchClient.makeHttpUrl(
273
+ "createUser",
274
+ { type: "mutation", args: [], returns: { refName: "String" } },
275
+ "user",
276
+ new Map(),
277
+ ),
278
+ ).toBe("/user/createUser");
279
+ expect(
280
+ HttpClient.makeBody(
281
+ [arg("body", "title"), arg("body", "optional", { nullable: true })],
282
+ [],
283
+ new Map([["title", "T"]]),
284
+ ),
285
+ ).toEqual({
286
+ title: "T",
287
+ optional: undefined,
288
+ });
289
+ expect(() => HttpClient.makeBody([arg("body", "title")], [], new Map())).toThrow("Argument title is required");
290
+
291
+ const blob = new Blob(["hello"]);
292
+ const formData = HttpClient.makeBody([], [arg("upload", "file", { refName: "Upload" })], new Map([["file", blob]]));
293
+ expect(formData).toBeInstanceOf(FormData);
294
+ expect((formData as FormData).get("file")).toBeInstanceOf(Blob);
295
+ });
296
+
297
+ test("calls fetch with expected methods, headers, and bodies", async () => {
298
+ setMockFetch();
299
+ jsonResponses.push({ value: "get" }, { value: "post" }, { value: "put" }, { value: "delete" });
300
+ const client = new HttpClient("https://api.example");
301
+ const formData = new FormData();
302
+ formData.append("file", new Blob(["x"]));
303
+
304
+ expect(await client.get<{ value: string }>("/items", { headers: { Authorization: "Bearer t" } })).toEqual({
305
+ value: "get",
306
+ });
307
+ expect(await client.post<{ value: string }>("/items", { title: "A" })).toEqual({ value: "post" });
308
+ expect(await client.put<{ value: string }>("/items/1", formData)).toEqual({ value: "put" });
309
+ expect(await client.delete<{ value: string }>("/items/1")).toEqual({ value: "delete" });
310
+
311
+ expect(fetchCalls[0]).toMatchObject({
312
+ url: "https://api.example/items",
313
+ init: { headers: { "Content-Type": "application/json", Authorization: "Bearer t" } },
314
+ });
315
+ expect(fetchCalls[1]?.init).toMatchObject({
316
+ method: "POST",
317
+ body: JSON.stringify({ title: "A" }),
318
+ headers: { "Content-Type": "application/json" },
319
+ });
320
+ expect(fetchCalls[2]?.init?.headers).toEqual({});
321
+ expect(fetchCalls[3]?.init).toMatchObject({ method: "DELETE" });
322
+ });
323
+
324
+ test("restores non-ok responses with the provided error constructor", async () => {
325
+ setMockFetch();
326
+ responseStatuses.push(409);
327
+ jsonResponses.push({
328
+ error: "fetchTest.error.conflict",
329
+ statusCode: 409,
330
+ data: { id: "1" },
331
+ path: "/items/1",
332
+ timestamp: "2026-05-25T00:00:00.000Z",
333
+ });
334
+ const client = new HttpClient("https://api.example", TestErr);
335
+
336
+ const error = (await client.get("/items/1").catch((error) => error)) as TestErr;
337
+
338
+ expect(error).toBeInstanceOf(TestErr);
339
+ expect(error.message).toBe("fetchTest.error.conflict");
340
+ expect(error).toMatchObject({
341
+ error: "fetchTest.error.conflict",
342
+ statusCode: 409,
343
+ data: { id: "1" },
344
+ path: "/items/1",
345
+ timestamp: "2026-05-25T00:00:00.000Z",
346
+ });
347
+ });
348
+ });
349
+
350
+ describe("requestStorage utilities", () => {
351
+ test("parses cookies and returns empty request data outside context", () => {
352
+ const parsed = parseCookieHeader('jwt=abc; theme=j:"dark"; malformed; badJson=j:{nope}; spaced = value ');
353
+
354
+ expect(parsed.get("jwt")).toEqual({ name: "jwt", value: "abc" });
355
+ expect(parsed.get("theme")).toEqual({ name: "theme", value: "dark" });
356
+ expect(parsed.get("badJson")).toEqual({ name: "badJson", value: "j:{nope}" });
357
+ expect(parsed.has("malformed")).toBe(false);
358
+ expect(headers()).toEqual(new Map());
359
+ expect(cookies()).toEqual(new Map());
360
+ expect(getRequestTheme()).toBeUndefined();
361
+ });
362
+
363
+ test("scopes headers, cookies, theme, and memoized queries per request", async () => {
364
+ if (!requestStorage) return;
365
+ const reqA = new Request("https://example.test/a", {
366
+ headers: { authorization: "Bearer request", cookie: "jwt=requestJwt; theme=system" },
367
+ });
368
+ const reqB = new Request("https://example.test/b", {
369
+ headers: { cookie: "jwt=otherJwt" },
370
+ });
371
+ let calls = 0;
372
+
373
+ const first = await requestStorage.run(reqA, async () => {
374
+ setRequestTheme("css");
375
+ const a = await memoizeRequestQuery("same", async () => {
376
+ calls += 1;
377
+ return "a";
378
+ });
379
+ const b = await memoizeRequestQuery("same", async () => {
380
+ calls += 1;
381
+ return "b";
382
+ });
383
+ return { a, b, auth: headers().get("authorization"), jwt: cookies().get("jwt")?.value, theme: getRequestTheme() };
384
+ });
385
+ const second = await requestStorage.run(reqB, async () => {
386
+ const value = await memoizeRequestQuery("same", async () => {
387
+ calls += 1;
388
+ return "b";
389
+ });
390
+ return { value, jwt: cookies().get("jwt")?.value, theme: getRequestTheme() };
391
+ });
392
+ const outsideA = await memoizeRequestQuery("outside", async () => {
393
+ calls += 1;
394
+ return "outside-a";
395
+ });
396
+ const outsideB = await memoizeRequestQuery("outside", async () => {
397
+ calls += 1;
398
+ return "outside-b";
399
+ });
400
+
401
+ expect(first).toEqual({ a: "a", b: "a", auth: "Bearer request", jwt: "requestJwt", theme: "css" });
402
+ expect(second).toEqual({ value: "b", jwt: "otherJwt", theme: undefined });
403
+ expect(outsideA).toBe("outside-a");
404
+ expect(outsideB).toBe("outside-b");
405
+ expect(calls).toBe(4);
406
+ });
407
+ });
408
+
409
+ describe("FetchClient HTTP generation", () => {
410
+ test("classifies args and registers service query/mutation handlers", async () => {
411
+ setMockFetch();
412
+ jsonResponses.push("ok", { title: "Created", count: 2, nested: { label: "N" } });
413
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
414
+ client.setJwt("jwt-token");
415
+
416
+ expect(
417
+ FetchClient.classifyHttpArgs([
418
+ arg("param", "id"),
419
+ arg("search", "q"),
420
+ arg("body", "payload"),
421
+ arg("upload", "file"),
422
+ ]),
423
+ ).toMatchObject({
424
+ paramArgs: [{ name: "id" }],
425
+ searchArgs: [{ name: "q" }],
426
+ bodyArgs: [{ name: "payload" }],
427
+ uploadArgs: [{ name: "file" }],
428
+ });
429
+
430
+ expect(await client.handler.getThing("1234567890abcdef12345678", ["x", "y"], null)).toBe("ok");
431
+ const created = await client.handler.createThing("Created", 2);
432
+
433
+ expect(created).toBeInstanceOf(FetchTestFull);
434
+ expect((created as InstanceType<typeof FetchTestFull>).title).toBe("Created");
435
+ expect(fetchCalls[0]).toMatchObject({
436
+ url: "https://api.example/custom/1234567890abcdef12345678?tags=x&tags=y",
437
+ init: { headers: { "Content-Type": "application/json", Authorization: "Bearer jwt-token" } },
438
+ });
439
+ expect(fetchCalls[1]).toMatchObject({
440
+ url: "https://api.example/createThing",
441
+ init: {
442
+ method: "POST",
443
+ body: JSON.stringify({ title: "Created", count: 2 }),
444
+ headers: { "Content-Type": "application/json", Authorization: "Bearer jwt-token" },
445
+ },
446
+ });
447
+ });
448
+
449
+ test("supports explicit auth token, request auth, crystalize false, upload bodies, and clone", async () => {
450
+ setMockFetch();
451
+ jsonResponses.push({ title: "Raw", count: 1, nested: { label: "N" } }, "RequestAuth", "uploaded", "cloned");
452
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
453
+ const file = new Blob(["file"]);
454
+
455
+ const raw = await client.handler.createThing("Raw", 1, { token: "explicit", crystalize: false });
456
+ const requestResult = requestStorage
457
+ ? await requestStorage.run(
458
+ new Request("https://example.test", { headers: { authorization: "Bearer request-token" } }),
459
+ async () => {
460
+ setAkanPublicEnv();
461
+ return await client.handler.getThing("abcdefabcdefabcdefabcdef", [], null);
462
+ },
463
+ )
464
+ : "RequestAuth";
465
+ const uploaded = await client.handler.uploadThing(file, "file-title");
466
+ const cloned = client.clone({ origin: "https://clone.example", connect: false, jwt: "clone-jwt" });
467
+ const cloneResult = await (
468
+ cloned as unknown as { getThing: (id: string, tags: string[], empty: null) => Promise<unknown> }
469
+ ).getThing("bbbbbbbbbbbbbbbbbbbbbbbb", [], null);
470
+
471
+ expect(raw).toEqual({ title: "Raw", count: 1, nested: { label: "N" } });
472
+ expect(requestResult).toBe("RequestAuth");
473
+ expect(uploaded).toBe("uploaded");
474
+ expect(cloneResult).toBe("cloned");
475
+ expect(fetchCalls[0]?.init?.headers).toMatchObject({ Authorization: "Bearer explicit" });
476
+ expect(fetchCalls[1]?.init?.headers).toMatchObject({ Authorization: "Bearer request-token" });
477
+ expect(fetchCalls[2]?.init?.body).toBeInstanceOf(FormData);
478
+ expect(fetchCalls[2]?.init?.headers).toEqual({});
479
+ expect(fetchCalls[3]).toMatchObject({
480
+ url: "https://clone.example/custom/bbbbbbbbbbbbbbbbbbbbbbbb",
481
+ init: { headers: { "Content-Type": "application/json", Authorization: "Bearer clone-jwt" } },
482
+ });
483
+ });
484
+
485
+ test("sends requests to the FetchPolicy.origin host instead of the client origin", async () => {
486
+ setMockFetch();
487
+ jsonResponses.push("pong", { title: "Created", count: 2, nested: { label: "N" } });
488
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
489
+
490
+ // trailing slash on the override host must be normalized away
491
+ const queried = await client.handler.getThing("1234567890abcdef12345678", [], null, {
492
+ origin: "https://akasys-debug.akamir.com/",
493
+ });
494
+ const created = await client.handler.createThing("Created", 2, { origin: "https://akasys-debug.akamir.com" });
495
+
496
+ expect(queried).toBe("pong");
497
+ expect(created).toBeInstanceOf(FetchTestFull);
498
+ expect(fetchCalls[0]?.url).toBe("https://akasys-debug.akamir.com/custom/1234567890abcdef12345678");
499
+ expect(fetchCalls[1]?.url).toBe("https://akasys-debug.akamir.com/createThing");
500
+ });
501
+
502
+ test("uses FetchPolicy.origin verbatim, including the global api prefix supplied by the caller", async () => {
503
+ setMockFetch();
504
+ jsonResponses.push("pong", { title: "Created", count: 2, nested: { label: "N" } });
505
+ const client = new FetchClient("https://api.example/api", {}, { service: serviceSignal });
506
+
507
+ // the caller is responsible for including the server global prefix (e.g. "/api")
508
+ const queried = await client.handler.getThing("1234567890abcdef12345678", [], null, {
509
+ origin: "https://edge.example.com/api",
510
+ });
511
+ const created = await client.handler.createThing("Created", 2, { origin: "https://edge.example.com/api" });
512
+
513
+ expect(queried).toBe("pong");
514
+ expect(created).toBeInstanceOf(FetchTestFull);
515
+ expect(fetchCalls[0]?.url).toBe("https://edge.example.com/api/custom/1234567890abcdef12345678");
516
+ expect(fetchCalls[1]?.url).toBe("https://edge.example.com/api/createThing");
517
+ });
518
+
519
+ test("bypasses the request-query cache when FetchPolicy.origin is set", async () => {
520
+ if (!requestStorage) return;
521
+ setMockFetch();
522
+ jsonResponses.push("origin", "override-1", "override-2");
523
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
524
+
525
+ const result = await requestStorage.run(new Request("https://example.test"), async () => {
526
+ const cachedA = await client.handler.getThing("1234567890abcdef12345678", [], null);
527
+ const cachedB = await client.handler.getThing("1234567890abcdef12345678", [], null);
528
+ const overrideA = await client.handler.getThing("1234567890abcdef12345678", [], null, {
529
+ origin: "https://akasys-debug.akamir.com",
530
+ });
531
+ const overrideB = await client.handler.getThing("1234567890abcdef12345678", [], null, {
532
+ origin: "https://akasys-debug.akamir.com",
533
+ });
534
+ return { cachedA, cachedB, overrideA, overrideB };
535
+ });
536
+
537
+ // origin requests share the memoized cache: the second call reuses the first response
538
+ expect(result.cachedA).toBe("origin");
539
+ expect(result.cachedB).toBe("origin");
540
+ // url overrides skip the cache entirely: each call performs a fresh fetch
541
+ expect(result.overrideA).toBe("override-1");
542
+ expect(result.overrideB).toBe("override-2");
543
+ expect(fetchCalls.map((call) => call.url)).toEqual([
544
+ "https://api.example/custom/1234567890abcdef12345678",
545
+ "https://akasys-debug.akamir.com/custom/1234567890abcdef12345678",
546
+ "https://akasys-debug.akamir.com/custom/1234567890abcdef12345678",
547
+ ]);
548
+ });
549
+
550
+ test("clone targets the new origin for queries and mutations while leaving the original intact", async () => {
551
+ setMockFetch();
552
+ jsonResponses.push({ title: "Created", count: 2, nested: { label: "N" } }, "clone-query", "origin-query");
553
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
554
+ client.setJwt("origin-jwt");
555
+ const cloned = client.clone({ origin: "https://clone.example", connect: false, jwt: "clone-jwt" }) as unknown as {
556
+ getThing: (id: string, tags: string[], empty: null) => Promise<unknown>;
557
+ createThing: (title: string, count: number) => Promise<unknown>;
558
+ };
559
+
560
+ const created = await cloned.createThing("Created", 2);
561
+ const cloneQuery = await cloned.getThing("1234567890abcdef12345678", [], null);
562
+ const originQuery = await client.handler.getThing("1234567890abcdef12345678", [], null);
563
+
564
+ expect(created).toBeInstanceOf(FetchTestFull);
565
+ expect(cloneQuery).toBe("clone-query");
566
+ expect(originQuery).toBe("origin-query");
567
+ // clone routes mutations and queries to the new origin with its own jwt
568
+ expect(fetchCalls[0]).toMatchObject({
569
+ url: "https://clone.example/createThing",
570
+ init: { headers: { Authorization: "Bearer clone-jwt" } },
571
+ });
572
+ expect(fetchCalls[1]).toMatchObject({ url: "https://clone.example/custom/1234567890abcdef12345678" });
573
+ // original client keeps its own origin and jwt, unaffected by the clone
574
+ expect(fetchCalls[2]).toMatchObject({
575
+ url: "https://api.example/custom/1234567890abcdef12345678",
576
+ init: { headers: { Authorization: "Bearer origin-jwt" } },
577
+ });
578
+ });
579
+
580
+ test("clone with a different origin does not share the request-query cache with the original", async () => {
581
+ if (!requestStorage) return;
582
+ setMockFetch();
583
+ jsonResponses.push("origin-resp", "clone-resp");
584
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
585
+ const cloned = client.clone({ origin: "https://clone.example", connect: false }) as unknown as {
586
+ getThing: (id: string, tags: string[], empty: null) => Promise<unknown>;
587
+ };
588
+
589
+ const result = await requestStorage.run(new Request("https://example.test"), async () => {
590
+ const originA = await client.handler.getThing("1234567890abcdef12345678", [], null);
591
+ const cloneA = await cloned.getThing("1234567890abcdef12345678", [], null);
592
+ const originB = await client.handler.getThing("1234567890abcdef12345678", [], null);
593
+ const cloneB = await cloned.getThing("1234567890abcdef12345678", [], null);
594
+ return { originA, cloneA, originB, cloneB };
595
+ });
596
+
597
+ // the cache key is scoped by origin, so origin and clone resolve to different responses
598
+ expect(result.originA).toBe("origin-resp");
599
+ expect(result.cloneA).toBe("clone-resp");
600
+ // repeated calls reuse each client's own cached response, so no extra fetches happen
601
+ expect(result.originB).toBe("origin-resp");
602
+ expect(result.cloneB).toBe("clone-resp");
603
+ expect(fetchCalls.map((call) => call.url)).toEqual([
604
+ "https://api.example/custom/1234567890abcdef12345678",
605
+ "https://clone.example/custom/1234567890abcdef12345678",
606
+ ]);
607
+ });
608
+ });
609
+
610
+ describe("FetchClient database signal helpers", () => {
611
+ test("registers database base model, slice, and filter metadata", async () => {
612
+ setMockFetch();
613
+ jsonResponses.push(
614
+ { title: "Full", count: 1, nested: { label: "N" } },
615
+ { title: "Light", count: 1, nested: { label: "N" } },
616
+ { title: "Created", count: 2, nested: { label: "N" } },
617
+ { title: "Updated", count: 3, nested: { label: "N" } },
618
+ { title: "Removed", count: 4, nested: { label: "N" } },
619
+ [{ title: "List", count: 1, nested: { label: "N" } }],
620
+ { count: 1 },
621
+ [{ title: "Init", count: 1, nested: { label: "N" } }],
622
+ { count: 1 },
623
+ [{ title: "DefaultInit", count: 1, nested: { label: "N" } }],
624
+ { count: 1 },
625
+ );
626
+ const client = new FetchClient("https://api.example", {}, { fetchTestItem: databaseSignal });
627
+
628
+ const full = await client.handler.fetchTestItem("1234567890abcdef12345678");
629
+ const light = await client.handler.lightFetchTestItem("1234567890abcdef12345678");
630
+ const created = await client.handler.createFetchTestItem({ title: "Created", count: 2, nested: { label: "N" } });
631
+ const updated = await client.handler.updateFetchTestItem("1234567890abcdef12345678", {
632
+ title: "Updated",
633
+ count: 3,
634
+ nested: { label: "N" },
635
+ });
636
+ const removed = await client.handler.removeFetchTestItem("1234567890abcdef12345678");
637
+ const list = (await client.handler.fetchTestItemListByOwner(
638
+ "abcdefabcdefabcdefabcdef",
639
+ 0,
640
+ 10,
641
+ "latest",
642
+ )) as unknown[];
643
+ const insight = await client.handler.fetchTestItemInsightByOwner("abcdefabcdefabcdefabcdef");
644
+ const init = (await client.handler.initFetchTestItemByOwner("abcdefabcdefabcdefabcdef", {
645
+ page: 1,
646
+ limit: 10,
647
+ sort: "latest",
648
+ })) as Record<string, unknown>;
649
+ const defaultInit = (await client.handler.initFetchTestItem()) as Record<string, Record<string, unknown>>;
650
+
651
+ expect(full).toBeInstanceOf(FetchTestFull);
652
+ expect(light).toBeInstanceOf(FetchTestLight);
653
+ expect(created).toBeInstanceOf(FetchTestFull);
654
+ expect(updated).toBeInstanceOf(FetchTestFull);
655
+ expect(removed).toBeInstanceOf(FetchTestFull);
656
+ expect(list[0]).toBeInstanceOf(FetchTestLight);
657
+ expect(insight).toBeInstanceOf(FetchTestInsight);
658
+ expect(init.fetchTestItemListByOwner).toBeInstanceOf(DataList);
659
+ expect(init.fetchTestItemInsightByOwner).toBeInstanceOf(FetchTestInsight);
660
+ expect(defaultInit.fetchTestItemInit.queryArgsOfFetchTestItem).toEqual([]);
661
+ expect(client.slice.fetchTestItemByOwner).toEqual({
662
+ refName: "fetchTestItem",
663
+ sliceName: "fetchTestItemByOwner",
664
+ argLength: 1,
665
+ });
666
+ expect(client.sortKeyMap.get("fetchTestItem")).toEqual(["latest", "oldest"]);
667
+ expect(fetchCalls.map((call) => call.url)).toEqual([
668
+ "https://api.example/fetchTest/fetchTestItem/1234567890abcdef12345678",
669
+ "https://api.example/fetchTest/lightFetchTestItem/1234567890abcdef12345678",
670
+ "https://api.example/fetchTest/createFetchTestItem",
671
+ "https://api.example/fetchTest/updateFetchTestItem/1234567890abcdef12345678",
672
+ "https://api.example/fetchTest/removeFetchTestItem/1234567890abcdef12345678",
673
+ "https://api.example/fetchTest/fetchTestItemListByOwner/abcdefabcdefabcdefabcdef?skip=0&limit=10&sort=latest",
674
+ "https://api.example/fetchTest/fetchTestItemInsightByOwner/abcdefabcdefabcdefabcdef",
675
+ "https://api.example/fetchTest/fetchTestItemListByOwner/abcdefabcdefabcdefabcdef?skip=0&limit=10&sort=latest",
676
+ "https://api.example/fetchTest/fetchTestItemInsightByOwner/abcdefabcdefabcdefabcdef",
677
+ "https://api.example/fetchTest/fetchTestItemList?skip=0&limit=20&sort=latest",
678
+ "https://api.example/fetchTest/fetchTestItemInsight",
679
+ ]);
680
+ });
681
+
682
+ test("creates view/edit helpers and merge helper from base model endpoints", async () => {
683
+ setMockFetch();
684
+ jsonResponses.push(
685
+ { title: "View", count: 1, nested: { label: "N" } },
686
+ { title: "ViewRaw", count: 1, nested: { label: "N" } },
687
+ { title: "Updated", count: 2, nested: { label: "N" } },
688
+ );
689
+ const client = new FetchClient("https://api.example", {}, { fetchTestItem: databaseSignal });
690
+
691
+ const view = (await client.handler.viewFetchTestItem("1234567890abcdef12345678")) as {
692
+ fetchTestItem: unknown;
693
+ fetchTestItemView: { fetchTestItemObj: unknown };
694
+ };
695
+ const edit = (await client.handler.getFetchTestItemEdit("abcdefabcdefabcdefabcdef")) as {
696
+ fetchTestItemObj: unknown;
697
+ };
698
+ const merged = await client.handler.mergeFetchTestItem(
699
+ { id: "bbbbbbbbbbbbbbbbbbbbbbbb" },
700
+ { title: "Merged", count: 2, nested: { label: "N" } },
701
+ );
702
+
703
+ expect(view.fetchTestItem).toBeInstanceOf(FetchTestFull);
704
+ expect(view.fetchTestItemView.fetchTestItemObj).toMatchObject({ title: "View" });
705
+ expect(edit.fetchTestItemObj).toMatchObject({ title: "ViewRaw" });
706
+ expect(merged).toBeInstanceOf(FetchTestFull);
707
+ expect(fetchCalls.at(-1)?.url).toBe("https://api.example/fetchTest/updateFetchTestItem/bbbbbbbbbbbbbbbbbbbbbbbb");
708
+ });
709
+ });
710
+
711
+ describe("WsClient", () => {
712
+ test("manages websocket lifecycle, messages, listeners, and subscriptions", () => {
713
+ setFakeWebSocket();
714
+ const client = new WsClient("ws://example/ws");
715
+ const messages: unknown[] = [];
716
+ const onceMessages: unknown[] = [];
717
+ const pubsubMessages: unknown[] = [];
718
+
719
+ expect(WsClient.makeRoomId("room", ["a", "b"])).toBe("room-a-b");
720
+ client.connect();
721
+ const ws = FakeWebSocket.instances[0];
722
+ ws.open();
723
+ expect(client.connected).toBe(true);
724
+
725
+ client.emit("send", ["hello"]);
726
+ expect(JSON.parse(ws.sent.at(-1) ?? "{}")).toEqual({ key: "send", data: ["hello"] });
727
+
728
+ const listener = (data: unknown) => messages.push(data);
729
+ client.on("messageKey", listener).once("messageKey", (data) => onceMessages.push(data));
730
+ expect(client.hasListeners("messageKey")).toBe(true);
731
+ ws.receive({ type: "msg", key: "messageKey", data: { value: 1 } });
732
+ ws.receive({ type: "msg", key: "messageKey", data: { value: 2 } });
733
+ expect(messages).toEqual([{ value: 1 }, { value: 2 }]);
734
+ expect(onceMessages).toEqual([{ value: 1 }]);
735
+ client.off("messageKey", listener);
736
+ expect(client.hasListeners("messageKey")).toBe(false);
737
+
738
+ const handlePubsub = (data: unknown) => pubsubMessages.push(data);
739
+ client.subscribe({ key: "roomKey", data: ["r1"], handleEvent: handlePubsub });
740
+ expect(JSON.parse(ws.sent.at(-1) ?? "{}")).toEqual({ key: "roomKey", data: ["r1"], subscribe: true });
741
+ ws.receive({ type: "pub", roomId: "roomKey-r1", data: { title: "event" } });
742
+ expect(pubsubMessages).toEqual([{ title: "event" }]);
743
+ expect(client.hasListeners("roomKey-r1")).toBe(true);
744
+ client.unsubscribe({ key: "roomKey", data: ["r1"], handleEvent: handlePubsub });
745
+ expect(JSON.parse(ws.sent.at(-1) ?? "{}")).toEqual({ key: "roomKey", data: ["r1"], subscribe: false });
746
+ expect(client.hasListeners("roomKey-r1")).toBe(false);
747
+
748
+ client.removeAllListeners("messageKey");
749
+ client.destroy();
750
+ expect(FakeWebSocket.instances[0]?.readyState).toBe(FakeWebSocket.CLOSED);
751
+ });
752
+
753
+ test("restores websocket error payloads with the provided error constructor", () => {
754
+ setFakeWebSocket();
755
+ const originalConsoleError = console.error;
756
+ const errors: unknown[] = [];
757
+ console.error = ((error: unknown) => {
758
+ errors.push(error);
759
+ }) as typeof console.error;
760
+ try {
761
+ const client = new WsClient("ws://example/ws", TestErr);
762
+ client.connect();
763
+ const ws = FakeWebSocket.instances[0];
764
+ ws.open();
765
+
766
+ ws.receive({
767
+ error: "chatRoom.error.notMember",
768
+ statusCode: 403,
769
+ data: { chatRoomId: "room-1" },
770
+ timestamp: "2026-05-25T00:00:00.000Z",
771
+ });
772
+
773
+ expect(errors[0]).toBeInstanceOf(TestErr);
774
+ expect(errors[0]).toMatchObject({
775
+ error: "chatRoom.error.notMember",
776
+ statusCode: 403,
777
+ data: { chatRoomId: "room-1" },
778
+ timestamp: "2026-05-25T00:00:00.000Z",
779
+ });
780
+ } finally {
781
+ console.error = originalConsoleError;
782
+ }
783
+ });
784
+
785
+ test("resubscribes rooms on reconnect and destroy prevents reconnect", async () => {
786
+ setFakeWebSocket();
787
+ globalThis.setTimeout = ((handler: TimerHandler, _timeout?: number, ...args: unknown[]) =>
788
+ originalSetTimeout(handler, 0, ...args)) as typeof setTimeout;
789
+ const client = new WsClient("ws://example/ws");
790
+ const handleEvent = () => undefined;
791
+ client.connect();
792
+ const firstWs = FakeWebSocket.instances[0];
793
+ firstWs.open();
794
+ client.subscribe({ key: "roomKey", data: ["r1"], handleEvent });
795
+
796
+ firstWs.close();
797
+ await new Promise((resolve) => originalSetTimeout(resolve, 5));
798
+ const secondWs = FakeWebSocket.instances[1];
799
+ secondWs.open();
800
+ expect(JSON.parse(secondWs.sent[0] ?? "{}")).toEqual({ key: "roomKey", data: ["r1"], subscribe: true });
801
+
802
+ client.destroy();
803
+ const instanceCount = FakeWebSocket.instances.length;
804
+ secondWs.close();
805
+ await new Promise((resolve) => originalSetTimeout(resolve, 5));
806
+ expect(FakeWebSocket.instances).toHaveLength(instanceCount);
807
+ });
808
+ });
809
+
810
+ describe("FetchClient websocket generation", () => {
811
+ test("registers message and pubsub handlers from serialized endpoints", async () => {
812
+ setFakeWebSocket();
813
+ const client = new FetchClient("https://api.example", {}, { service: serviceSignal });
814
+ client.connect();
815
+ const ws = FakeWebSocket.instances[0];
816
+ ws.open();
817
+ const listened: unknown[] = [];
818
+ const published: unknown[] = [];
819
+
820
+ await client.handler.sendThing("hello");
821
+ const cleanupMessage = client.handler.listenSendThing((data: unknown) => listened.push(data)) as () => void;
822
+ ws.receive({ type: "msg", key: "sendThing", data: "server-message" });
823
+ cleanupMessage();
824
+ ws.receive({ type: "msg", key: "sendThing", data: "after-cleanup" });
825
+ const cleanupPubsub = (await client.handler.subscribeRoomThing("room-1", (data: unknown) =>
826
+ published.push(data),
827
+ )) as () => void;
828
+ ws.receive({ type: "pub", roomId: "roomThing-room-1", data: { title: "Published" } });
829
+ cleanupPubsub();
830
+
831
+ expect(JSON.parse(ws.sent[0] ?? "{}")).toEqual({ key: "sendThing", data: ["hello"] });
832
+ expect(listened).toEqual(["server-message"]);
833
+ expect(JSON.parse(ws.sent[1] ?? "{}")).toEqual({ key: "roomThing", data: ["room-1"], subscribe: true });
834
+ expect(published[0]).toBeInstanceOf(FetchTestLight);
835
+ expect((published[0] as InstanceType<typeof FetchTestLight>).title).toBe("Published");
836
+ expect(JSON.parse(ws.sent[2] ?? "{}")).toEqual({ key: "roomThing", data: ["room-1"], subscribe: false });
837
+ });
838
+ });