@snail-js/api 0.1.27 → 1.0.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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -605
  3. package/dist/adapter/plain.d.ts +12 -0
  4. package/dist/adapter/plain.d.ts.map +1 -0
  5. package/dist/adapter/react.d.ts +14 -0
  6. package/dist/adapter/react.d.ts.map +1 -0
  7. package/dist/adapter/registry.d.ts +16 -0
  8. package/dist/adapter/registry.d.ts.map +1 -0
  9. package/dist/adapter/vue.d.ts +15 -0
  10. package/dist/adapter/vue.d.ts.map +1 -0
  11. package/dist/chunks/custom-UchOSg_s.js +466 -0
  12. package/dist/chunks/custom-UchOSg_s.js.map +1 -0
  13. package/dist/chunks/download-lBC7oOTT.js +160 -0
  14. package/dist/chunks/download-lBC7oOTT.js.map +1 -0
  15. package/dist/chunks/object-Ct0w-h4i.js +74 -0
  16. package/dist/chunks/object-Ct0w-h4i.js.map +1 -0
  17. package/dist/chunks/plugin-D3RDffDG.js +742 -0
  18. package/dist/chunks/plugin-D3RDffDG.js.map +1 -0
  19. package/dist/chunks/react-C1em-gxy.js +62 -0
  20. package/dist/chunks/react-C1em-gxy.js.map +1 -0
  21. package/dist/chunks/request-qeRhihEF.js +40 -0
  22. package/dist/chunks/request-qeRhihEF.js.map +1 -0
  23. package/dist/chunks/response-D-ubkAFl.js +127 -0
  24. package/dist/chunks/response-D-ubkAFl.js.map +1 -0
  25. package/dist/chunks/use-download-FAjtdJ58.js +1816 -0
  26. package/dist/chunks/use-download-FAjtdJ58.js.map +1 -0
  27. package/dist/chunks/vue-Dd9EhkQ3.js +34 -0
  28. package/dist/chunks/vue-Dd9EhkQ3.js.map +1 -0
  29. package/dist/core/args.d.ts +54 -0
  30. package/dist/core/args.d.ts.map +1 -0
  31. package/dist/core/connection.d.ts +15 -0
  32. package/dist/core/connection.d.ts.map +1 -0
  33. package/dist/core/context.d.ts +124 -0
  34. package/dist/core/context.d.ts.map +1 -0
  35. package/dist/core/http-stream.d.ts +28 -0
  36. package/dist/core/http-stream.d.ts.map +1 -0
  37. package/dist/core/logger.d.ts +22 -0
  38. package/dist/core/logger.d.ts.map +1 -0
  39. package/dist/core/metadata.d.ts +92 -0
  40. package/dist/core/metadata.d.ts.map +1 -0
  41. package/dist/core/metadata.keys.d.ts +41 -0
  42. package/dist/core/metadata.keys.d.ts.map +1 -0
  43. package/dist/core/method.d.ts +177 -0
  44. package/dist/core/method.d.ts.map +1 -0
  45. package/dist/core/plugin-manager.d.ts +106 -0
  46. package/dist/core/plugin-manager.d.ts.map +1 -0
  47. package/dist/core/plugin.d.ts +113 -0
  48. package/dist/core/plugin.d.ts.map +1 -0
  49. package/dist/core/reconnect.d.ts +22 -0
  50. package/dist/core/reconnect.d.ts.map +1 -0
  51. package/dist/core/resolve-stream.d.ts +45 -0
  52. package/dist/core/resolve-stream.d.ts.map +1 -0
  53. package/dist/core/resolve.d.ts +50 -0
  54. package/dist/core/resolve.d.ts.map +1 -0
  55. package/dist/core/response.d.ts +60 -0
  56. package/dist/core/response.d.ts.map +1 -0
  57. package/dist/core/server.d.ts +132 -0
  58. package/dist/core/server.d.ts.map +1 -0
  59. package/dist/core/sse.d.ts +27 -0
  60. package/dist/core/sse.d.ts.map +1 -0
  61. package/dist/core/state-bag.d.ts +36 -0
  62. package/dist/core/state-bag.d.ts.map +1 -0
  63. package/dist/core/websocket.d.ts +29 -0
  64. package/dist/core/websocket.d.ts.map +1 -0
  65. package/dist/decorators/api.d.ts +24 -4
  66. package/dist/decorators/api.d.ts.map +1 -0
  67. package/dist/decorators/args.d.ts +77 -14
  68. package/dist/decorators/args.d.ts.map +1 -0
  69. package/dist/decorators/custom.d.ts +81 -0
  70. package/dist/decorators/custom.d.ts.map +1 -0
  71. package/dist/decorators/header.d.ts +22 -0
  72. package/dist/decorators/header.d.ts.map +1 -0
  73. package/dist/decorators/index.d.ts +17 -0
  74. package/dist/decorators/index.d.ts.map +1 -0
  75. package/dist/decorators/methods.d.ts +33 -0
  76. package/dist/decorators/methods.d.ts.map +1 -0
  77. package/dist/decorators/progress.d.ts +24 -6
  78. package/dist/decorators/progress.d.ts.map +1 -0
  79. package/dist/decorators/server.d.ts +28 -7
  80. package/dist/decorators/server.d.ts.map +1 -0
  81. package/dist/decorators/stream.d.ts +113 -0
  82. package/dist/decorators/stream.d.ts.map +1 -0
  83. package/dist/default/options.d.ts +19 -0
  84. package/dist/default/options.d.ts.map +1 -0
  85. package/dist/error/base.d.ts +20 -0
  86. package/dist/error/base.d.ts.map +1 -0
  87. package/dist/error/decorator.d.ts +14 -0
  88. package/dist/error/decorator.d.ts.map +1 -0
  89. package/dist/error/hook.d.ts +14 -0
  90. package/dist/error/hook.d.ts.map +1 -0
  91. package/dist/error/index.d.ts +8 -0
  92. package/dist/error/index.d.ts.map +1 -0
  93. package/dist/error/options.d.ts +12 -0
  94. package/dist/error/options.d.ts.map +1 -0
  95. package/dist/error/plugin.d.ts +11 -0
  96. package/dist/error/plugin.d.ts.map +1 -0
  97. package/dist/error/request.d.ts +29 -0
  98. package/dist/error/request.d.ts.map +1 -0
  99. package/dist/error/response.d.ts +36 -0
  100. package/dist/error/response.d.ts.map +1 -0
  101. package/dist/index.d.ts +56 -11
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +2034 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/locale/en.d.ts +5 -0
  106. package/dist/locale/en.d.ts.map +1 -0
  107. package/dist/locale/index.d.ts +57 -0
  108. package/dist/locale/index.d.ts.map +1 -0
  109. package/dist/locale/types.d.ts +11 -0
  110. package/dist/locale/types.d.ts.map +1 -0
  111. package/dist/locale/zh.d.ts +5 -0
  112. package/dist/locale/zh.d.ts.map +1 -0
  113. package/dist/plugins/cache/adapters/indexeddb.d.ts +52 -0
  114. package/dist/plugins/cache/adapters/indexeddb.d.ts.map +1 -0
  115. package/dist/plugins/cache/adapters/memory.d.ts +60 -0
  116. package/dist/plugins/cache/adapters/memory.d.ts.map +1 -0
  117. package/dist/plugins/cache/adapters/web-storage.d.ts +43 -0
  118. package/dist/plugins/cache/adapters/web-storage.d.ts.map +1 -0
  119. package/dist/plugins/cache/decorators.d.ts +62 -0
  120. package/dist/plugins/cache/decorators.d.ts.map +1 -0
  121. package/dist/plugins/cache/index.d.ts +41 -0
  122. package/dist/plugins/cache/index.d.ts.map +1 -0
  123. package/dist/plugins/cache/key.d.ts +48 -0
  124. package/dist/plugins/cache/key.d.ts.map +1 -0
  125. package/dist/plugins/cache/manager.d.ts +124 -0
  126. package/dist/plugins/cache/manager.d.ts.map +1 -0
  127. package/dist/plugins/cache/plugin.d.ts +64 -0
  128. package/dist/plugins/cache/plugin.d.ts.map +1 -0
  129. package/dist/plugins/cache/type.d.ts +116 -0
  130. package/dist/plugins/cache/type.d.ts.map +1 -0
  131. package/dist/plugins/index.d.ts +75 -0
  132. package/dist/plugins/index.d.ts.map +1 -0
  133. package/dist/plugins/index.js +2265 -0
  134. package/dist/plugins/index.js.map +1 -0
  135. package/dist/plugins/interceptor/decorators.d.ts +44 -0
  136. package/dist/plugins/interceptor/decorators.d.ts.map +1 -0
  137. package/dist/plugins/interceptor/index.d.ts +31 -0
  138. package/dist/plugins/interceptor/index.d.ts.map +1 -0
  139. package/dist/plugins/interceptor/manager.d.ts +27 -0
  140. package/dist/plugins/interceptor/manager.d.ts.map +1 -0
  141. package/dist/plugins/interceptor/plugin.d.ts +65 -0
  142. package/dist/plugins/interceptor/plugin.d.ts.map +1 -0
  143. package/dist/plugins/interceptor/type.d.ts +45 -0
  144. package/dist/plugins/interceptor/type.d.ts.map +1 -0
  145. package/dist/plugins/pool/index.d.ts +26 -0
  146. package/dist/plugins/pool/index.d.ts.map +1 -0
  147. package/dist/plugins/pool/plugin.d.ts +83 -0
  148. package/dist/plugins/pool/plugin.d.ts.map +1 -0
  149. package/dist/plugins/pool/scheduler.d.ts +153 -0
  150. package/dist/plugins/pool/scheduler.d.ts.map +1 -0
  151. package/dist/plugins/pool/type.d.ts +38 -0
  152. package/dist/plugins/pool/type.d.ts.map +1 -0
  153. package/dist/plugins/react/index.d.ts +20 -0
  154. package/dist/plugins/react/index.d.ts.map +1 -0
  155. package/dist/plugins/react/index.js +145 -0
  156. package/dist/plugins/react/index.js.map +1 -0
  157. package/dist/plugins/react/plugin.d.ts +33 -0
  158. package/dist/plugins/react/plugin.d.ts.map +1 -0
  159. package/dist/plugins/react/type.d.ts +32 -0
  160. package/dist/plugins/react/type.d.ts.map +1 -0
  161. package/dist/plugins/transform/decorators.d.ts +54 -0
  162. package/dist/plugins/transform/decorators.d.ts.map +1 -0
  163. package/dist/plugins/transform/hydrate.d.ts +47 -0
  164. package/dist/plugins/transform/hydrate.d.ts.map +1 -0
  165. package/dist/plugins/transform/index.d.ts +41 -0
  166. package/dist/plugins/transform/index.d.ts.map +1 -0
  167. package/dist/plugins/transform/plugin.d.ts +20 -0
  168. package/dist/plugins/transform/plugin.d.ts.map +1 -0
  169. package/dist/plugins/transform/type.d.ts +67 -0
  170. package/dist/plugins/transform/type.d.ts.map +1 -0
  171. package/dist/plugins/validate/decorators.d.ts +24 -0
  172. package/dist/plugins/validate/decorators.d.ts.map +1 -0
  173. package/dist/plugins/validate/index.d.ts +40 -0
  174. package/dist/plugins/validate/index.d.ts.map +1 -0
  175. package/dist/plugins/validate/plugin.d.ts +13 -0
  176. package/dist/plugins/validate/plugin.d.ts.map +1 -0
  177. package/dist/plugins/validate/type.d.ts +54 -0
  178. package/dist/plugins/validate/type.d.ts.map +1 -0
  179. package/dist/plugins/version/decorators.d.ts +22 -0
  180. package/dist/plugins/version/decorators.d.ts.map +1 -0
  181. package/dist/plugins/version/index.d.ts +23 -0
  182. package/dist/plugins/version/index.d.ts.map +1 -0
  183. package/dist/plugins/version/plugin.d.ts +13 -0
  184. package/dist/plugins/version/plugin.d.ts.map +1 -0
  185. package/dist/plugins/version/type.d.ts +47 -0
  186. package/dist/plugins/version/type.d.ts.map +1 -0
  187. package/dist/plugins/vue/index.d.ts +21 -0
  188. package/dist/plugins/vue/index.d.ts.map +1 -0
  189. package/dist/plugins/vue/index.js +114 -0
  190. package/dist/plugins/vue/index.js.map +1 -0
  191. package/dist/plugins/vue/plugin.d.ts +15 -0
  192. package/dist/plugins/vue/plugin.d.ts.map +1 -0
  193. package/dist/plugins/vue/type.d.ts +34 -0
  194. package/dist/plugins/vue/type.d.ts.map +1 -0
  195. package/dist/strategies/index.d.ts +2 -0
  196. package/dist/strategies/index.d.ts.map +1 -0
  197. package/dist/strategies/index.js +29 -0
  198. package/dist/strategies/index.js.map +1 -0
  199. package/dist/strategies/plain.d.ts +2 -0
  200. package/dist/strategies/plain.d.ts.map +1 -0
  201. package/dist/strategies/plain.js +23 -0
  202. package/dist/strategies/plain.js.map +1 -0
  203. package/dist/strategies/react.d.ts +2 -0
  204. package/dist/strategies/react.d.ts.map +1 -0
  205. package/dist/strategies/react.js +30 -0
  206. package/dist/strategies/react.js.map +1 -0
  207. package/dist/strategies/shared/adapter.d.ts +23 -0
  208. package/dist/strategies/shared/adapter.d.ts.map +1 -0
  209. package/dist/strategies/shared/dom.d.ts +53 -0
  210. package/dist/strategies/shared/dom.d.ts.map +1 -0
  211. package/dist/strategies/shared/error.d.ts +30 -0
  212. package/dist/strategies/shared/error.d.ts.map +1 -0
  213. package/dist/strategies/shared/method.d.ts +51 -0
  214. package/dist/strategies/shared/method.d.ts.map +1 -0
  215. package/dist/strategies/shared/public.d.ts +33 -0
  216. package/dist/strategies/shared/public.d.ts.map +1 -0
  217. package/dist/strategies/shared/queue.d.ts +30 -0
  218. package/dist/strategies/shared/queue.d.ts.map +1 -0
  219. package/dist/strategies/shared/retry.d.ts +51 -0
  220. package/dist/strategies/shared/retry.d.ts.map +1 -0
  221. package/dist/strategies/shared/state.d.ts +117 -0
  222. package/dist/strategies/shared/state.d.ts.map +1 -0
  223. package/dist/strategies/shared/timing.d.ts +53 -0
  224. package/dist/strategies/shared/timing.d.ts.map +1 -0
  225. package/dist/strategies/shared/watcher.d.ts +37 -0
  226. package/dist/strategies/shared/watcher.d.ts.map +1 -0
  227. package/dist/strategies/use-auto-request.d.ts +77 -0
  228. package/dist/strategies/use-auto-request.d.ts.map +1 -0
  229. package/dist/strategies/use-download.d.ts +103 -0
  230. package/dist/strategies/use-download.d.ts.map +1 -0
  231. package/dist/strategies/use-fetcher.d.ts +52 -0
  232. package/dist/strategies/use-fetcher.d.ts.map +1 -0
  233. package/dist/strategies/use-pagination.d.ts +101 -0
  234. package/dist/strategies/use-pagination.d.ts.map +1 -0
  235. package/dist/strategies/use-request.d.ts +50 -0
  236. package/dist/strategies/use-request.d.ts.map +1 -0
  237. package/dist/strategies/use-retriable-request.d.ts +46 -0
  238. package/dist/strategies/use-retriable-request.d.ts.map +1 -0
  239. package/dist/strategies/use-sse.d.ts +103 -0
  240. package/dist/strategies/use-sse.d.ts.map +1 -0
  241. package/dist/strategies/use-token-auth.d.ts +88 -0
  242. package/dist/strategies/use-token-auth.d.ts.map +1 -0
  243. package/dist/strategies/use-uploader.d.ts +88 -0
  244. package/dist/strategies/use-uploader.d.ts.map +1 -0
  245. package/dist/strategies/use-watcher.d.ts +69 -0
  246. package/dist/strategies/use-watcher.d.ts.map +1 -0
  247. package/dist/typings/adapter.d.ts +61 -0
  248. package/dist/typings/adapter.d.ts.map +1 -0
  249. package/dist/typings/api.d.ts +122 -0
  250. package/dist/typings/api.d.ts.map +1 -0
  251. package/dist/typings/args.d.ts +51 -0
  252. package/dist/typings/args.d.ts.map +1 -0
  253. package/dist/typings/index.d.ts +8 -12
  254. package/dist/typings/index.d.ts.map +1 -0
  255. package/dist/typings/meta.d.ts +44 -0
  256. package/dist/typings/meta.d.ts.map +1 -0
  257. package/dist/typings/plugin.d.ts +114 -0
  258. package/dist/typings/plugin.d.ts.map +1 -0
  259. package/dist/typings/response.d.ts +118 -0
  260. package/dist/typings/response.d.ts.map +1 -0
  261. package/dist/typings/server.d.ts +83 -0
  262. package/dist/typings/server.d.ts.map +1 -0
  263. package/dist/typings/stream.d.ts +150 -0
  264. package/dist/typings/stream.d.ts.map +1 -0
  265. package/dist/utils/download.d.ts +97 -0
  266. package/dist/utils/download.d.ts.map +1 -0
  267. package/dist/utils/emitter.d.ts +36 -0
  268. package/dist/utils/emitter.d.ts.map +1 -0
  269. package/dist/utils/index.d.ts +6 -1
  270. package/dist/utils/index.d.ts.map +1 -0
  271. package/dist/utils/is.d.ts +22 -0
  272. package/dist/utils/is.d.ts.map +1 -0
  273. package/dist/utils/object.d.ts +44 -0
  274. package/dist/utils/object.d.ts.map +1 -0
  275. package/dist/utils/url.d.ts +44 -0
  276. package/dist/utils/url.d.ts.map +1 -0
  277. package/package.json +90 -27
  278. package/README_EN.md +0 -607
  279. package/dist/cache/index.d.ts +0 -9
  280. package/dist/cache/indexDBCache.d.ts +0 -13
  281. package/dist/cache/localstorageCache.d.ts +0 -10
  282. package/dist/cache/memoryCache.d.ts +0 -11
  283. package/dist/core/index.d.ts +0 -4
  284. package/dist/core/snailApi.d.ts +0 -22
  285. package/dist/core/snailMethod.d.ts +0 -49
  286. package/dist/core/snailServer.d.ts +0 -35
  287. package/dist/core/snailSse.d.ts +0 -20
  288. package/dist/decorators/cache.d.ts +0 -15
  289. package/dist/decorators/method.d.ts +0 -38
  290. package/dist/decorators/sse.d.ts +0 -30
  291. package/dist/decorators/strategy.d.ts +0 -10
  292. package/dist/decorators/versioning.d.ts +0 -16
  293. package/dist/eventEmmit/index.d.ts +0 -1
  294. package/dist/eventEmmit/snail.event.d.ts +0 -10
  295. package/dist/snail-api.js +0 -4868
  296. package/dist/snail-api.umd.cjs +0 -4872
  297. package/dist/strategies/jwtStrategy.d.ts +0 -4
  298. package/dist/typings/api.option.d.ts +0 -10
  299. package/dist/typings/apiProxy.d.ts +0 -8
  300. package/dist/typings/cache.management.option.d.ts +0 -25
  301. package/dist/typings/cache.type.d.ts +0 -23
  302. package/dist/typings/content.type.d.ts +0 -5
  303. package/dist/typings/request.body.d.ts +0 -3
  304. package/dist/typings/response.data.d.ts +0 -13
  305. package/dist/typings/snail.event.d.ts +0 -6
  306. package/dist/typings/snail.method.d.ts +0 -14
  307. package/dist/typings/snail.option.d.ts +0 -16
  308. package/dist/typings/sse.d.ts +0 -15
  309. package/dist/typings/strategy.d.ts +0 -5
  310. package/dist/typings/versioning.option.d.ts +0 -30
  311. package/dist/utils/function.d.ts +0 -31
  312. package/dist/versioning/index.d.ts +0 -1
  313. package/dist/versioning/versioning.d.ts +0 -12
@@ -0,0 +1,45 @@
1
+ import type { AxiosResponse, InternalAxiosRequestConfig } from "axios";
2
+ import type { SnailContext } from "../../core/context.js";
3
+ /**
4
+ * Shared shapes of the interceptor plugin.
5
+ *
6
+ * ## Why these are not axios' own interceptors
7
+ *
8
+ * axios keeps its interceptor lists on the *instance* (`axios.interceptors`), so
9
+ * `use()` there can only express "every request of this instance". A decorator
10
+ * such as `@BeforeRequest()` needs the opposite granularity — this one method,
11
+ * plus this one api class — and it has to be ordered against the other plugins
12
+ * (see `docs/guide/plugin-lifecycle.md` §2.1). Both are impossible at instance level, which
13
+ * is why interceptors run on the plugin lifecycle instead.
14
+ */
15
+ /**
16
+ * One request or response interceptor.
17
+ *
18
+ * `T` is `InternalAxiosRequestConfig` while the request is being prepared and
19
+ * `AxiosResponse` once a response exists. Both callbacks receive the live
20
+ * {@link SnailContext} as a second argument, so an interceptor may read the
21
+ * method identity (`ctx.fullName`) without the plugin having to inject it.
22
+ *
23
+ * Returning `undefined` from `onFulfilled` keeps the value it received: most
24
+ * interceptors mutate in place, and forcing every one of them to return the
25
+ * config would make the common case noisy.
26
+ */
27
+ export interface InterceptorEntry<T = unknown> {
28
+ /** Runs before the request is handed to axios, or against the response. */
29
+ onFulfilled?: (value: T, ctx: SnailContext) => T | void | Promise<T | void>;
30
+ /**
31
+ * Runs when `onFulfilled` — or an earlier interceptor in the same phase —
32
+ * throws.
33
+ *
34
+ * The plugin calls it with the thrown value while the request is still
35
+ * recoverable, unlike `onError` which only observes a settled failure. Any
36
+ * value other than `undefined` is adopted as the current config/response;
37
+ * returning `undefined` or throwing re-propagates the failure.
38
+ */
39
+ onRejected?: (error: unknown, ctx: SnailContext) => unknown;
40
+ }
41
+ /** An entry that runs while the outgoing axios config is still mutable. */
42
+ export type RequestInterceptorEntry = InterceptorEntry<InternalAxiosRequestConfig>;
43
+ /** An entry that runs against a response that already exists. */
44
+ export type ResponseInterceptorEntry = InterceptorEntry<AxiosResponse>;
45
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/plugins/interceptor/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,0BAA0B,EAAE,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,OAAO;IAC3C,2EAA2E;IAC3E,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,YAAY,KAAK,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAE5E;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC;CAC7D;AAED,2EAA2E;AAC3E,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,0BAA0B,CAAC,CAAC;AAEnF,iEAAiE;AACjE,MAAM,MAAM,wBAAwB,GAAG,gBAAgB,CAAC,aAAa,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Request pool plugin.
3
+ *
4
+ * ```ts
5
+ * import { RequestPool } from "@snail-js/api/plugins";
6
+ *
7
+ * Service.use(RequestPool({ concurrency: 4, maxQueue: 50, queueTimeout: 10_000 }));
8
+ * ```
9
+ *
10
+ * Bounds how many requests are in flight at once, with a priority-ordered, bounded,
11
+ * time-limited queue. It exists because the browser's own request queue is FIFO,
12
+ * invisible and unprioritised — see `RequestPoolOptions` for the full reasoning,
13
+ * including why HTTP/2 does not remove the need for it.
14
+ *
15
+ * `priority: -150` puts it below the cache (`-100`), so it is the last thing to run
16
+ * before the transport and a cache hit never consumes a slot.
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+ export { RequestPool, POOL_PLUGIN_NAME, POOL_PRIORITY, clearPool, isPoolError, poolStats } from "./plugin.js";
21
+ export type { RequestPoolPlugin } from "./plugin.js";
22
+ export { RequestPoolScheduler } from "./scheduler.js";
23
+ export type { AbortLike, PoolTicket, RequestPoolOptions, RequestPoolStats } from "./scheduler.js";
24
+ export { POOL_ERROR_CODES, SnailPoolError } from "./type.js";
25
+ export type { PoolErrorCode } from "./type.js";
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/pool/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,WAAW,EACX,SAAS,EACV,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACV,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { SnailPoolError } from "./type.js";
2
+ import { RequestPoolScheduler, type RequestPoolOptions, type RequestPoolStats } from "./scheduler.js";
3
+ /** Plugin name; also the identity used by `Service.use()` / `Service.remove()`. */
4
+ export declare const POOL_PLUGIN_NAME = "pool";
5
+ /**
6
+ * Priority of the pool band.
7
+ *
8
+ * **Below** the cache (`-100`), which is the whole point: forward hooks run
9
+ * highest-priority first, so the pool is the very last thing to run before the
10
+ * transport. A request that the cache can answer never reaches the pool and never
11
+ * consumes a slot. Putting the pool above the cache would let a handful of cached
12
+ * reads occupy the entire pool and starve the real requests behind them.
13
+ */
14
+ export declare const POOL_PRIORITY = -150;
15
+ /** The pool plugin object plus live access to its scheduler. */
16
+ export interface RequestPoolPlugin {
17
+ readonly name: string;
18
+ readonly priority: number;
19
+ /**
20
+ * The scheduler, available once `install` has run.
21
+ *
22
+ * Exposed so an application can read {@link RequestPoolScheduler.stats} for a
23
+ * loading indicator, or widen `concurrency` when it learns the backend is
24
+ * coping.
25
+ */
26
+ readonly scheduler: RequestPoolScheduler | undefined;
27
+ }
28
+ /**
29
+ * Bound how many requests are in flight at once.
30
+ *
31
+ * ```ts
32
+ * Service.use(RequestPool({ concurrency: 4, maxQueue: 50, queueTimeout: 10_000 }));
33
+ *
34
+ * // `ctx.state` is readable from the priority callback, so an interactive request
35
+ * // can jump ahead of a background prefetch.
36
+ * Service.use(RequestPool({ concurrency: 4, priority: (ctx) => (ctx.state.get("prefetch") ? 100 : 0) }));
37
+ * ```
38
+ *
39
+ * ## What problem this solves
40
+ *
41
+ * See {@link RequestPoolOptions} for why the browser's own queue is not enough.
42
+ * In short: the built-in one is FIFO, invisible and unprioritised, so a burst from
43
+ * one screen can starve the request the user is actually waiting for, and nothing
44
+ * fails fast when the backlog grows without bound.
45
+ *
46
+ * ## Where it sits
47
+ *
48
+ * Last in forward order, immediately before the network call, and it holds its slot
49
+ * only for the transport — `next()` resolves once the response has been received,
50
+ * so validation, transformation and the caller's reactive updates happen after the
51
+ * slot is already back in the pool.
52
+ *
53
+ * A cache hit short-circuits `beforeRequest` upstream of this plugin, so cached
54
+ * reads cost no concurrency.
55
+ *
56
+ * ## One path it does not cover
57
+ *
58
+ * `useTokenAuth` replays a request that came back `401` by re-running the transport
59
+ * directly, deliberately without re-entering `beforeRequest` (re-entering would
60
+ * loop). Those replays therefore **do not pass through this pool** and are not
61
+ * counted against `concurrency`.
62
+ *
63
+ * The practical impact is small — replays are bounded by the number of requests
64
+ * that were in flight when the token expired, which were already uncapped — but it
65
+ * is worth knowing before reaching for the pool to protect a backend from an auth
66
+ * storm. If that matters, cap it at the source: use `useRequest`'s own concurrency
67
+ * control, or let `useTokenAuth` refresh proactively rather than on the first 401.
68
+ */
69
+ export declare function RequestPool(options?: RequestPoolOptions): RequestPoolPlugin;
70
+ /** Read the live counters of an installed pool plugin, or `undefined`. */
71
+ export declare function poolStats(plugin: RequestPoolPlugin): RequestPoolStats | undefined;
72
+ /** Drop every queued request of an installed pool plugin. */
73
+ export declare function clearPool(plugin: RequestPoolPlugin, reason?: unknown): void;
74
+ /**
75
+ * `true` when `error` came from the pool rather than from the transport.
76
+ *
77
+ * Lets a caller tell "refused before it was ever sent" — safe to retry later —
78
+ * apart from a real network failure. Covers all four refusal paths: a full queue,
79
+ * a queue wait that timed out, a request abandoned while waiting, and a queue
80
+ * cleared by an uninstall.
81
+ */
82
+ export declare function isPoolError(error: unknown): error is SnailPoolError;
83
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/pool/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EACL,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AAErB,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,gEAAgE;AAChE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CAgD3E;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,MAAM,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,SAAS,CAEjF;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,MAAM,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3E;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAEnE"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The slice of `AbortSignal` the scheduler uses.
3
+ *
4
+ * Structurally typed rather than declared as `AbortSignal` because axios hands the
5
+ * plugin its own `GenericAbortSignal`, which does not satisfy the DOM interface —
6
+ * it lacks `reason`/`throwIfAborted` and even types the listener methods as
7
+ * optional. In practice the object is a real `AbortSignal`, so the listener path is
8
+ * always available; the optional methods are handled anyway, and a signal that
9
+ * cannot be listened to simply means an already-queued request is not withdrawn
10
+ * early — it still gets its slot and is rejected by the transport.
11
+ */
12
+ export interface AbortLike {
13
+ readonly aborted: boolean;
14
+ addEventListener?(type: "abort", listener: () => void, options?: {
15
+ once?: boolean;
16
+ }): void;
17
+ removeEventListener?(type: "abort", listener: () => void): void;
18
+ }
19
+ /**
20
+ * Options accepted by the request-pool plugin.
21
+ *
22
+ * ## Why a pool exists at all
23
+ *
24
+ * A browser already queues requests: HTTP/1.1 allows about six connections per
25
+ * origin, and everything beyond that waits inside the network stack. That built-in
26
+ * queue is FIFO, invisible, and unprioritised — an application that fires fifty
27
+ * requests from one screen cannot say that the one the user is looking at should
28
+ * go first, cannot observe how many are waiting, and cannot avoid a burst that
29
+ * starves the rest of the page.
30
+ *
31
+ * HTTP/2 does not remove the need either: multiplexing replaces the six-connection
32
+ * cap with a *stream* limit (commonly 100), and the server still has a finite
33
+ * capacity. A burst of five hundred requests over HTTP/2 will happily saturate it.
34
+ *
35
+ * A pool gives three things the browser cannot: a hard ceiling that holds on both
36
+ * protocols, a priority order, and a bounded queue that fails fast instead of
37
+ * silently piling up.
38
+ */
39
+ export interface RequestPoolOptions {
40
+ /**
41
+ * Maximum requests in flight at once. Defaults to `6`.
42
+ *
43
+ * Six is the classic HTTP/1.1 per-origin connection cap, so it is the largest
44
+ * value that cannot make things worse on an old server while still bounding an
45
+ * HTTP/2 burst. Raising it helps only when the backend is known to cope.
46
+ */
47
+ concurrency?: number;
48
+ /**
49
+ * Maximum requests allowed to *wait*. Defaults to `Infinity`.
50
+ *
51
+ * Set a finite value to fail fast: once the queue is full, an extra request
52
+ * rejects immediately rather than joining an unbounded backlog the user will
53
+ * never see resolved.
54
+ */
55
+ maxQueue?: number;
56
+ /**
57
+ * How long a queued request may wait, in milliseconds. Defaults to `0`, meaning
58
+ * no limit.
59
+ *
60
+ * A request the user has stopped waiting for is worse than a failed one — it
61
+ * still consumes a slot eventually and may overwrite fresher data.
62
+ */
63
+ queueTimeout?: number;
64
+ /**
65
+ * Ordering weight for a queued request. **Lower runs first**; ties keep arrival
66
+ * order, and a request that finds a free slot never queues at all.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * // Requests the user is looking at jump ahead of background prefetches.
71
+ * priority: (ctx) => (ctx.state.get("prefetch") ? 100 : 0)
72
+ * ```
73
+ */
74
+ priority?: (ctx: unknown) => number;
75
+ }
76
+ /** Live counters for a {@link RequestPoolScheduler}. */
77
+ export interface RequestPoolStats {
78
+ /** Requests currently holding a slot. */
79
+ active: number;
80
+ /** Requests waiting for a slot. */
81
+ queued: number;
82
+ /** Configured ceiling. */
83
+ concurrency: number;
84
+ }
85
+ /** A resolve/reject pair plus the bookkeeping the queue needs to drop it. */
86
+ export interface PoolTicket {
87
+ /** Give the slot back. Idempotent. */
88
+ readonly release: () => void;
89
+ }
90
+ /**
91
+ * The scheduling core of the request pool.
92
+ *
93
+ * Deterministic and free of any request or plugin concept: it hands out `release`
94
+ * callbacks and knows nothing about what they guard. That makes the hard part —
95
+ * ordering, fairness and the guarantee that a slot is always returned — testable
96
+ * on its own, without a server or a network.
97
+ *
98
+ * ## Messages come from the plugin
99
+ *
100
+ * The refusal messages are contributed by `RequestPool`'s `setup`, following the
101
+ * plugin-owned-strings convention. Using this class **standalone** therefore yields
102
+ * the raw message keys rather than translated text — the `code` on each
103
+ * {@link SnailPoolError} is always correct, and that is what callers should branch
104
+ * on. Registering a catalogue yourself is enough if you want the text too.
105
+ *
106
+ * ## The invariant that matters
107
+ *
108
+ * `active` must never drift from the number of outstanding tickets. Every failure
109
+ * path (a queue that is full, a wait that times out, a request that is aborted,
110
+ * a waiter dropped by `clear`) either never increments `active` or has already
111
+ * been counted, and `release()` is idempotent so a double release from a `finally`
112
+ * plus an abort listener cannot leak a slot. A leaked slot is invisible until the
113
+ * pool permanently deadlocks at its ceiling, at which point every later request
114
+ * hangs — which is why it is enforced here rather than at the call site.
115
+ */
116
+ export declare class RequestPoolScheduler {
117
+ private concurrency;
118
+ private readonly maxQueue;
119
+ private readonly queueTimeout;
120
+ private readonly priorityOf;
121
+ private active;
122
+ private sequence;
123
+ private readonly waiters;
124
+ constructor(options?: RequestPoolOptions);
125
+ /** Current counters. */
126
+ get stats(): RequestPoolStats;
127
+ /** Change the ceiling at runtime; raising it immediately admits queued work. */
128
+ setConcurrency(value: number): void;
129
+ /**
130
+ * Take a slot, or wait for one.
131
+ *
132
+ * Resolves with a ticket whose `release()` returns the slot. Rejects when the
133
+ * queue is full or the wait exceeds `queueTimeout`. `signal` lets an abandoned
134
+ * request leave the queue instead of holding a place it will never use.
135
+ */
136
+ acquire(ctx: unknown, signal?: AbortLike): Promise<PoolTicket>;
137
+ /**
138
+ * Drop every waiter, rejecting each with `reason`.
139
+ *
140
+ * Used on uninstall: a queued request must not sit forever waiting for a pool
141
+ * that will never admit it.
142
+ */
143
+ clear(reason?: unknown): void;
144
+ /** Hand out one slot. */
145
+ private issue;
146
+ /** Admit as many waiters as there are free slots. */
147
+ private pump;
148
+ /** Lowest priority number first, then arrival order. */
149
+ private sort;
150
+ private priorityOfFor;
151
+ private abortError;
152
+ }
153
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/plugins/pool/scheduler.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3F,mBAAmB,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CACjE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;CACrC;AAED,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyC;IAEpE,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IAExC,YAAY,OAAO,GAAE,kBAAuB,EAU3C;IAED,wBAAwB;IACxB,IAAI,KAAK,IAAI,gBAAgB,CAE5B;IAED,gFAAgF;IAChF,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIlC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAqE7D;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAQ5B;IAID,yBAAyB;IACzB,OAAO,CAAC,KAAK;IAcb,qDAAqD;IACrD,OAAO,CAAC,IAAI;IAOZ,wDAAwD;IACxD,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,UAAU;CAGnB"}
@@ -0,0 +1,38 @@
1
+ import { SnailError } from "../../error/base.js";
2
+ /**
3
+ * The error codes a request-pool refusal can carry.
4
+ *
5
+ * `isPoolError()` is the supported way to test them, but the strings are exported
6
+ * so an application can switch on one precisely — for example to distinguish "the
7
+ * queue was full, try again shortly" from "the user navigated away".
8
+ */
9
+ export declare const POOL_ERROR_CODES: {
10
+ /** `maxQueue` was reached; the request was never sent. */
11
+ readonly queueFull: "SNAIL_POOL_QUEUE_FULL";
12
+ /** The request waited longer than `queueTimeout` and was dropped. */
13
+ readonly queueTimeout: "SNAIL_POOL_QUEUE_TIMEOUT";
14
+ /** The request was cancelled while it was still waiting for a slot. */
15
+ readonly aborted: "SNAIL_POOL_ABORTED";
16
+ /** The queue was cleared — usually because the plugin was uninstalled. */
17
+ readonly cleared: "SNAIL_POOL_CLEARED";
18
+ };
19
+ /** Any code {@link SnailPoolError} can carry. */
20
+ export type PoolErrorCode = (typeof POOL_ERROR_CODES)[keyof typeof POOL_ERROR_CODES];
21
+ /**
22
+ * Raised when the pool refuses or drops a request.
23
+ *
24
+ * A dedicated class rather than a reuse of `SnailRequestError`: a pool refusal
25
+ * means the request **never reached the network**, which is a materially different
26
+ * situation from a transport failure. Retrying is safe and usually correct, the
27
+ * payload is untouched, and no server state changed — a caller that cannot tell the
28
+ * two apart will either retry a request the server already processed, or give up on
29
+ * one that was merely queued behind a burst.
30
+ */
31
+ export declare class SnailPoolError extends SnailError {
32
+ /** Which refusal this is. */
33
+ readonly code: PoolErrorCode;
34
+ constructor(message: string, code: PoolErrorCode, options?: {
35
+ cause?: unknown;
36
+ });
37
+ }
38
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/plugins/pool/type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;IAC3B,0DAA0D;aAC1D,SAAS,EAAE,uBAAuB;IAClC,qEAAqE;aACrE,YAAY,EAAE,0BAA0B;IACxC,uEAAuE;aACvE,OAAO,EAAE,oBAAoB;IAC7B,0EAA0E;aAC1E,OAAO,EAAE,oBAAoB;CACrB,CAAC;AAEX,iDAAiD;AACjD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAErF;;;;;;;;;GASG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAC5C,6BAA6B;IAC7B,SAAiB,IAAI,EAAE,aAAa,CAAC;IAErC,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,EAElF;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * React adapter plugin.
3
+ *
4
+ * ```tsx
5
+ * import { ReactAdapter, useMethodState } from "@snail-js/api/plugins/react";
6
+ *
7
+ * Service.use(ReactAdapter());
8
+ *
9
+ * const { data, loading, error } = useMethodState(userApi.getUser("1"));
10
+ * ```
11
+ *
12
+ * This sits behind its own subpath rather than in `@snail-js/api/plugins`: that
13
+ * barrel must not statically import `react`, or an application using only `Cache`
14
+ * would fail to resolve React at all.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ export { ReactAdapter, useMethodState } from "./plugin.js";
19
+ export type { ReactAdapterOptions, ReactMethodState } from "./type.js";
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC"}
@@ -0,0 +1,145 @@
1
+ import { n as createPlugin } from "../../chunks/plugin-D3RDffDG.js";
2
+ import { t as SnailCancelledError } from "../../chunks/request-qeRhihEF.js";
3
+ import { a as readKey, o as unwrapEnvelope } from "../../chunks/response-D-ubkAFl.js";
4
+ import { t as reactStateAdapter } from "../../chunks/react-C1em-gxy.js";
5
+ //#region src/plugins/react/plugin.ts
6
+ /**
7
+ * React adapter plugin.
8
+ *
9
+ * Mirrors every request's state onto `ctx.meta` as subscribable boxes and exposes
10
+ * {@link useMethodState} to bind them during render.
11
+ *
12
+ * ## Why this is not just the Vue plugin with another adapter
13
+ *
14
+ * A Vue `ref` tracks reads by itself, so writing it is enough. A React box has no
15
+ * idea a component rendered it: the component must subscribe during render
16
+ * (`useSyncExternalStore`) or the new value is never painted. That subscription
17
+ * lives in `useMethodState`, not in the plugin hooks.
18
+ *
19
+ * ## Why `initMeta` and not `beforeRequest`
20
+ *
21
+ * `initMeta` runs **once**, when the `SnailMethod` is built, while `beforeCreate`
22
+ * runs per `send()`. Creating the boxes per send is the classic bug where a
23
+ * component keeps rendering the first response forever, because the UI captured
24
+ * box #1 while the second send wrote into box #2.
25
+ */
26
+ /** Key of the `loading` handle. Fixed, and not part of the envelope. */
27
+ var LOADING_KEY = "loading";
28
+ /** Key of the `error` handle. Fixed, and not part of the envelope. */
29
+ var ERROR_KEY = "error";
30
+ /**
31
+ * Stand-in bound when a handle is missing.
32
+ *
33
+ * `useMethodState` must call the same number of hooks in the same order on every
34
+ * render; a missing handle therefore cannot mean "call one hook fewer".
35
+ */
36
+ var MISSING_HANDLE = reactStateAdapter.create(void 0);
37
+ /** `useSyncExternalStore` behind a non-optional name, so it can always be called. */
38
+ var bindHandle = reactStateAdapter.useBind;
39
+ /**
40
+ * Create one box unless the caller already holds one for that key.
41
+ *
42
+ * The guard keeps a re-run of `initMeta` (a context built before the plugin was
43
+ * installed) from replacing boxes the UI is already subscribed to.
44
+ */
45
+ function ensureHandle(ctx, key, initial) {
46
+ if (reactStateAdapter.isState?.(ctx.meta[key])) return;
47
+ ctx.meta[key] = reactStateAdapter.create(initial);
48
+ }
49
+ /** Create the five boxes. Runs once per `SnailMethod`. */
50
+ function createHandles(ctx) {
51
+ const { dataKey, codeKey, messageKey } = ctx.serverOptions;
52
+ ensureHandle(ctx, dataKey, void 0);
53
+ ensureHandle(ctx, codeKey, void 0);
54
+ ensureHandle(ctx, messageKey, void 0);
55
+ ensureHandle(ctx, LOADING_KEY, false);
56
+ ensureHandle(ctx, ERROR_KEY, void 0);
57
+ }
58
+ /** Write one box, ignoring a key this plugin never created. */
59
+ function writeHandle(ctx, key, value) {
60
+ const handle = ctx.meta[key];
61
+ if (handle) reactStateAdapter.write(handle, value);
62
+ }
63
+ /** Copy the envelope of the current response into the caller's boxes. */
64
+ function captureResponse(ctx) {
65
+ const response = ctx.getResponse();
66
+ if (!response) return;
67
+ const { dataKey, codeKey, messageKey } = ctx.serverOptions;
68
+ writeHandle(ctx, dataKey, unwrapEnvelope(response.data, dataKey));
69
+ writeHandle(ctx, codeKey, readKey(response.data, codeKey));
70
+ writeHandle(ctx, messageKey, readKey(response.data, messageKey));
71
+ }
72
+ /**
73
+ * Read a request method's state inside a component, subscribing it to changes.
74
+ *
75
+ * ```tsx
76
+ * function User({ id }: { id: string }) {
77
+ * const method = useMemo(() => userApi.getUser(id), [id]);
78
+ * const { data, loading, error } = useMethodState(method);
79
+ * useEffect(() => { void method.send(); }, [method]);
80
+ * if (loading) return <Spinner />;
81
+ * return <p>{error ? String(error) : data?.name}</p>;
82
+ * }
83
+ * ```
84
+ *
85
+ * The five handles are bound unconditionally and in a fixed order, because React
86
+ * identifies hooks by call position: a conditional `useBind` throws
87
+ * "rendered fewer hooks than expected" as soon as the second render takes a
88
+ * different branch.
89
+ */
90
+ function useMethodState(method) {
91
+ const meta = method.meta;
92
+ const { dataKey, codeKey, messageKey } = method.context.serverOptions;
93
+ const data = bindHandle(meta[dataKey] ?? MISSING_HANDLE);
94
+ const code = bindHandle(meta[codeKey] ?? MISSING_HANDLE);
95
+ const message = bindHandle(meta[messageKey] ?? MISSING_HANDLE);
96
+ const loading = bindHandle(meta[LOADING_KEY] ?? MISSING_HANDLE);
97
+ const error = bindHandle(meta[ERROR_KEY] ?? MISSING_HANDLE);
98
+ return {
99
+ data,
100
+ loading: Boolean(loading),
101
+ error,
102
+ code,
103
+ message
104
+ };
105
+ }
106
+ /**
107
+ * Create the React adapter plugin.
108
+ *
109
+ * ```tsx
110
+ * Service.use(ReactAdapter());
111
+ *
112
+ * const method = Service.createApi(UserApi).getUser("1");
113
+ * const { data, loading } = useMethodState(method);
114
+ * ```
115
+ */
116
+ var ReactAdapter = createPlugin({
117
+ name: "react-adapter",
118
+ priority: 0,
119
+ setup() {
120
+ return {
121
+ initMeta(ctx) {
122
+ createHandles(ctx);
123
+ },
124
+ beforeCreate(ctx) {
125
+ writeHandle(ctx, LOADING_KEY, true);
126
+ writeHandle(ctx, ERROR_KEY, void 0);
127
+ },
128
+ afterResponse(ctx, next) {
129
+ captureResponse(ctx);
130
+ return next();
131
+ },
132
+ onError(ctx, error) {
133
+ if (error instanceof SnailCancelledError) return;
134
+ writeHandle(ctx, ERROR_KEY, error);
135
+ },
136
+ afterRequest(ctx) {
137
+ writeHandle(ctx, LOADING_KEY, false);
138
+ }
139
+ };
140
+ }
141
+ });
142
+ //#endregion
143
+ export { ReactAdapter, useMethodState };
144
+
145
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/plugins/react/plugin.ts"],"sourcesContent":["import { reactStateAdapter } from \"../../adapter/react\";\nimport { createPlugin } from \"../../core/plugin\";\nimport { readKey, unwrapEnvelope } from \"../../core/response\";\nimport { SnailCancelledError } from \"../../error/request\";\nimport type { SnailContext } from \"../../core/context\";\nimport type { SnailMethod } from \"../../core/method\";\nimport type { SnailStateRef } from \"../../typings/adapter\";\nimport type { ReactAdapterOptions, ReactMethodState } from \"./type\";\n\n/**\n * React adapter plugin.\n *\n * Mirrors every request's state onto `ctx.meta` as subscribable boxes and exposes\n * {@link useMethodState} to bind them during render.\n *\n * ## Why this is not just the Vue plugin with another adapter\n *\n * A Vue `ref` tracks reads by itself, so writing it is enough. A React box has no\n * idea a component rendered it: the component must subscribe during render\n * (`useSyncExternalStore`) or the new value is never painted. That subscription\n * lives in `useMethodState`, not in the plugin hooks.\n *\n * ## Why `initMeta` and not `beforeRequest`\n *\n * `initMeta` runs **once**, when the `SnailMethod` is built, while `beforeCreate`\n * runs per `send()`. Creating the boxes per send is the classic bug where a\n * component keeps rendering the first response forever, because the UI captured\n * box #1 while the second send wrote into box #2.\n */\n\n/** Key of the `loading` handle. Fixed, and not part of the envelope. */\nconst LOADING_KEY = \"loading\";\n\n/** Key of the `error` handle. Fixed, and not part of the envelope. */\nconst ERROR_KEY = \"error\";\n\n/**\n * Stand-in bound when a handle is missing.\n *\n * `useMethodState` must call the same number of hooks in the same order on every\n * render; a missing handle therefore cannot mean \"call one hook fewer\".\n */\nconst MISSING_HANDLE = reactStateAdapter.create<unknown>(undefined);\n\n/** `useSyncExternalStore` behind a non-optional name, so it can always be called. */\nconst bindHandle = reactStateAdapter.useBind as (ref: SnailStateRef) => unknown;\n\n/**\n * Create one box unless the caller already holds one for that key.\n *\n * The guard keeps a re-run of `initMeta` (a context built before the plugin was\n * installed) from replacing boxes the UI is already subscribed to.\n */\nfunction ensureHandle(ctx: SnailContext, key: string, initial: unknown): void {\n if (reactStateAdapter.isState?.(ctx.meta[key])) return;\n ctx.meta[key] = reactStateAdapter.create(initial);\n}\n\n/** Create the five boxes. Runs once per `SnailMethod`. */\nfunction createHandles(ctx: SnailContext): void {\n const { dataKey, codeKey, messageKey } = ctx.serverOptions;\n ensureHandle(ctx, dataKey, undefined);\n ensureHandle(ctx, codeKey, undefined);\n ensureHandle(ctx, messageKey, undefined);\n ensureHandle(ctx, LOADING_KEY, false);\n ensureHandle(ctx, ERROR_KEY, undefined);\n}\n\n/** Write one box, ignoring a key this plugin never created. */\nfunction writeHandle(ctx: SnailContext, key: string, value: unknown): void {\n const handle = ctx.meta[key];\n if (handle) reactStateAdapter.write(handle as SnailStateRef, value);\n}\n\n/** Copy the envelope of the current response into the caller's boxes. */\nfunction captureResponse(ctx: SnailContext): void {\n const response = ctx.getResponse();\n if (!response) return;\n\n const { dataKey, codeKey, messageKey } = ctx.serverOptions;\n writeHandle(ctx, dataKey, unwrapEnvelope(response.data, dataKey));\n writeHandle(ctx, codeKey, readKey(response.data, codeKey));\n writeHandle(ctx, messageKey, readKey(response.data, messageKey));\n}\n\n/**\n * Read a request method's state inside a component, subscribing it to changes.\n *\n * ```tsx\n * function User({ id }: { id: string }) {\n * const method = useMemo(() => userApi.getUser(id), [id]);\n * const { data, loading, error } = useMethodState(method);\n * useEffect(() => { void method.send(); }, [method]);\n * if (loading) return <Spinner />;\n * return <p>{error ? String(error) : data?.name}</p>;\n * }\n * ```\n *\n * The five handles are bound unconditionally and in a fixed order, because React\n * identifies hooks by call position: a conditional `useBind` throws\n * \"rendered fewer hooks than expected\" as soon as the second render takes a\n * different branch.\n */\nexport function useMethodState<TData = unknown>(\n method: SnailMethod<any, TData, any, any, any>\n): ReactMethodState<TData> {\n const meta = method.meta;\n const { dataKey, codeKey, messageKey } = method.context.serverOptions;\n\n // Fixed order and fixed count — do not reorder, do not hoist into a loop.\n const data = bindHandle((meta[dataKey] ?? MISSING_HANDLE) as SnailStateRef);\n const code = bindHandle((meta[codeKey] ?? MISSING_HANDLE) as SnailStateRef);\n const message = bindHandle((meta[messageKey] ?? MISSING_HANDLE) as SnailStateRef);\n const loading = bindHandle((meta[LOADING_KEY] ?? MISSING_HANDLE) as SnailStateRef);\n const error = bindHandle((meta[ERROR_KEY] ?? MISSING_HANDLE) as SnailStateRef);\n\n return {\n data: data as TData | undefined,\n loading: Boolean(loading),\n error,\n code,\n message\n };\n}\n\n/**\n * Create the React adapter plugin.\n *\n * ```tsx\n * Service.use(ReactAdapter());\n *\n * const method = Service.createApi(UserApi).getUser(\"1\");\n * const { data, loading } = useMethodState(method);\n * ```\n */\nexport const ReactAdapter = createPlugin<ReactAdapterOptions>({\n name: \"react-adapter\",\n priority: 0,\n\n setup() {\n return {\n initMeta(ctx) {\n createHandles(ctx);\n },\n\n beforeCreate(ctx) {\n writeHandle(ctx, LOADING_KEY, true);\n // A new attempt clears the previous failure; a stale error would otherwise\n // keep rendering next to data that has just loaded successfully.\n writeHandle(ctx, ERROR_KEY, undefined);\n },\n\n afterResponse(ctx, next) {\n captureResponse(ctx);\n return next();\n },\n\n onError(ctx, error) {\n // Cancellation is expected control flow, not a failure: writing it would\n // flash an error state every time a component unmounts mid-request.\n if (error instanceof SnailCancelledError) return;\n writeHandle(ctx, ERROR_KEY, error);\n },\n\n afterRequest(ctx) {\n writeHandle(ctx, LOADING_KEY, false);\n }\n };\n }\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,cAAc;;AAGpB,IAAM,YAAY;;;;;;;AAQlB,IAAM,iBAAiB,kBAAkB,OAAgB,KAAA,CAAS;;AAGlE,IAAM,aAAa,kBAAkB;;;;;;;AAQrC,SAAS,aAAa,KAAmB,KAAa,SAAwB;CAC5E,IAAI,kBAAkB,UAAU,IAAI,KAAK,IAAI,GAAG;CAChD,IAAI,KAAK,OAAO,kBAAkB,OAAO,OAAO;AAClD;;AAGA,SAAS,cAAc,KAAyB;CAC9C,MAAM,EAAE,SAAS,SAAS,eAAe,IAAI;CAC7C,aAAa,KAAK,SAAS,KAAA,CAAS;CACpC,aAAa,KAAK,SAAS,KAAA,CAAS;CACpC,aAAa,KAAK,YAAY,KAAA,CAAS;CACvC,aAAa,KAAK,aAAa,KAAK;CACpC,aAAa,KAAK,WAAW,KAAA,CAAS;AACxC;;AAGA,SAAS,YAAY,KAAmB,KAAa,OAAsB;CACzE,MAAM,SAAS,IAAI,KAAK;CACxB,IAAI,QAAQ,kBAAkB,MAAM,QAAyB,KAAK;AACpE;;AAGA,SAAS,gBAAgB,KAAyB;CAChD,MAAM,WAAW,IAAI,YAAY;CACjC,IAAI,CAAC,UAAU;CAEf,MAAM,EAAE,SAAS,SAAS,eAAe,IAAI;CAC7C,YAAY,KAAK,SAAS,eAAe,SAAS,MAAM,OAAO,CAAC;CAChE,YAAY,KAAK,SAAS,QAAQ,SAAS,MAAM,OAAO,CAAC;CACzD,YAAY,KAAK,YAAY,QAAQ,SAAS,MAAM,UAAU,CAAC;AACjE;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,eACd,QACyB;CACzB,MAAM,OAAO,OAAO;CACpB,MAAM,EAAE,SAAS,SAAS,eAAe,OAAO,QAAQ;CAGxD,MAAM,OAAO,WAAY,KAAK,YAAY,cAAgC;CAC1E,MAAM,OAAO,WAAY,KAAK,YAAY,cAAgC;CAC1E,MAAM,UAAU,WAAY,KAAK,eAAe,cAAgC;CAChF,MAAM,UAAU,WAAY,KAAK,gBAAgB,cAAgC;CACjF,MAAM,QAAQ,WAAY,KAAK,cAAc,cAAgC;CAE7E,OAAO;EACC;EACN,SAAS,QAAQ,OAAO;EACxB;EACA;EACA;CACF;AACF;;;;;;;;;;;AAYA,IAAa,eAAe,aAAkC;CAC5D,MAAM;CACN,UAAU;CAEV,QAAQ;EACN,OAAO;GACL,SAAS,KAAK;IACZ,cAAc,GAAG;GACnB;GAEA,aAAa,KAAK;IAChB,YAAY,KAAK,aAAa,IAAI;IAGlC,YAAY,KAAK,WAAW,KAAA,CAAS;GACvC;GAEA,cAAc,KAAK,MAAM;IACvB,gBAAgB,GAAG;IACnB,OAAO,KAAK;GACd;GAEA,QAAQ,KAAK,OAAO;IAGlB,IAAI,iBAAiB,qBAAqB;IAC1C,YAAY,KAAK,WAAW,KAAK;GACnC;GAEA,aAAa,KAAK;IAChB,YAAY,KAAK,aAAa,KAAK;GACrC;EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { SnailMethod } from "../../core/method.js";
2
+ import type { ReactAdapterOptions, ReactMethodState } from "./type.js";
3
+ /**
4
+ * Read a request method's state inside a component, subscribing it to changes.
5
+ *
6
+ * ```tsx
7
+ * function User({ id }: { id: string }) {
8
+ * const method = useMemo(() => userApi.getUser(id), [id]);
9
+ * const { data, loading, error } = useMethodState(method);
10
+ * useEffect(() => { void method.send(); }, [method]);
11
+ * if (loading) return <Spinner />;
12
+ * return <p>{error ? String(error) : data?.name}</p>;
13
+ * }
14
+ * ```
15
+ *
16
+ * The five handles are bound unconditionally and in a fixed order, because React
17
+ * identifies hooks by call position: a conditional `useBind` throws
18
+ * "rendered fewer hooks than expected" as soon as the second render takes a
19
+ * different branch.
20
+ */
21
+ export declare function useMethodState<TData = unknown>(method: SnailMethod<any, TData, any, any, any>): ReactMethodState<TData>;
22
+ /**
23
+ * Create the React adapter plugin.
24
+ *
25
+ * ```tsx
26
+ * Service.use(ReactAdapter());
27
+ *
28
+ * const method = Service.createApi(UserApi).getUser("1");
29
+ * const { data, loading } = useMethodState(method);
30
+ * ```
31
+ */
32
+ export declare const ReactAdapter: import("../../index.js").SnailPlugin<ReactAdapterOptions>;
33
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/react/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AA8EpE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,KAAK,GAAG,OAAO,EAC5C,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAC7C,gBAAgB,CAAC,KAAK,CAAC,CAkBzB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,kDAkCvB,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Options accepted by the React adapter plugin.
3
+ *
4
+ * Deliberately empty: the adapter mirrors the envelope keys the server already
5
+ * declares (`dataKey` / `codeKey` / `messageKey`) and adds the two fixed handles
6
+ * `loading` and `error`. A per-plugin key option was avoided on purpose — the
7
+ * companion `useMethodState` hook has to bind the same handles in a fixed order,
8
+ * and a configurable key would have to be threaded through the hook to stay in
9
+ * sync.
10
+ */
11
+ export interface ReactAdapterOptions {
12
+ }
13
+ /**
14
+ * What {@link useMethodState} returns.
15
+ *
16
+ * Deliberately discriminated from a `SnailResult`: the values update over time and
17
+ * each one is read live from its own state box, so `data` can be `undefined`
18
+ * (nothing loaded yet) even though a successful send always has one.
19
+ */
20
+ export interface ReactMethodState<TData = unknown> {
21
+ /** Unwrapped payload of the most recent response. */
22
+ data: TData | undefined;
23
+ /** `true` between `send()` and settlement. */
24
+ loading: boolean;
25
+ /** Failure of the most recent send, cleared at the start of the next one. */
26
+ error: unknown;
27
+ /** Business code of the most recent response. */
28
+ code: unknown;
29
+ /** Business message of the most recent response. */
30
+ message: unknown;
31
+ }
32
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/plugins/react/type.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;CAAG;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO;IAC/C,qDAAqD;IACrD,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IAExB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IAEjB,6EAA6E;IAC7E,KAAK,EAAE,OAAO,CAAC;IAEf,iDAAiD;IACjD,IAAI,EAAE,OAAO,CAAC;IAEd,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB"}