@snail-js/api 0.1.28 → 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 -4871
  296. package/dist/snail-api.umd.cjs +0 -4875
  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,742 @@
1
+ import { AxiosHeaders } from "axios";
2
+ //#region src/error/base.ts
3
+ /**
4
+ * Base class of every error `@snail-js/api` throws.
5
+ *
6
+ * A stable `code` is attached to each subclass so application code can branch
7
+ * on `error.code` without importing the class, and so the localization layer can
8
+ * render a translated message.
9
+ */
10
+ var SnailError = class SnailError extends Error {
11
+ constructor(message, options = {}) {
12
+ super(message);
13
+ this.name = new.target.name;
14
+ this.code = options.code ?? "SNAIL_ERROR";
15
+ this.cause = options.cause;
16
+ if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, new.target);
17
+ }
18
+ /** `true` for every error originating from this library. */
19
+ static isSnailError(value) {
20
+ return value instanceof SnailError;
21
+ }
22
+ };
23
+ //#endregion
24
+ //#region src/error/decorator.ts
25
+ /**
26
+ * Thrown when a decorator is applied incorrectly — two request-method decorators
27
+ * on one method, a parameter decorator on a constructor, and so on.
28
+ *
29
+ * These are programmer errors and always surface eagerly, as early as the
30
+ * decorated class is evaluated.
31
+ */
32
+ var SnailDecoratorError = class extends SnailError {
33
+ constructor(message, options = {}) {
34
+ super(message, {
35
+ code: "SNAIL_DECORATOR_ERROR",
36
+ cause: options.cause
37
+ });
38
+ }
39
+ };
40
+ //#endregion
41
+ //#region src/utils/is.ts
42
+ /** Runtime type predicates shared across the library. */
43
+ /** `true` for a non-null object (including arrays, dates, class instances). */
44
+ function isObject(value) {
45
+ return typeof value === "object" && value !== null;
46
+ }
47
+ /**
48
+ * `true` for a plain object literal — not an array, `Date`, `Map`, `FormData`,
49
+ * `Blob`, class instance, and so on.
50
+ *
51
+ * Used to decide whether a `@Query()` / `@Data()` argument should be spread
52
+ * into the outgoing payload.
53
+ */
54
+ function isPlainObject(value) {
55
+ if (Object.prototype.toString.call(value) !== "[object Object]") return false;
56
+ const prototype = Object.getPrototypeOf(value);
57
+ return prototype === null || prototype === Object.prototype;
58
+ }
59
+ /** `true` for a callable value. */
60
+ function isFunction(value) {
61
+ return typeof value === "function";
62
+ }
63
+ /** `true` for a thenable. */
64
+ function isPromise(value) {
65
+ return isObject(value) && isFunction(value.then);
66
+ }
67
+ /** `true` when `value` is neither `null` nor `undefined`. */
68
+ function isDefined(value) {
69
+ return value !== null && value !== void 0;
70
+ }
71
+ /** `true` when a `Response`-like body should be passed through untouched. */
72
+ function isBinaryBody(value) {
73
+ if (typeof Blob !== "undefined" && value instanceof Blob) return true;
74
+ if (typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer) return true;
75
+ if (typeof FormData !== "undefined" && value instanceof FormData) return true;
76
+ if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) return true;
77
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) return true;
78
+ return false;
79
+ }
80
+ /** `true` when the code is executing in a DOM-capable browser. */
81
+ function isBrowser() {
82
+ return typeof window !== "undefined" && typeof document !== "undefined";
83
+ }
84
+ //#endregion
85
+ //#region src/utils/url.ts
86
+ /** Uppercase the first character, leaving the rest untouched. */
87
+ function capitalize(value) {
88
+ return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);
89
+ }
90
+ /** `true` when the url carries its own scheme or protocol-relative prefix. */
91
+ function isAbsoluteURL(url) {
92
+ return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
93
+ }
94
+ /** Drop trailing slashes without touching a bare `"/"`. */
95
+ function trimEndSlash(url) {
96
+ return url.replace(/\/+$/, "");
97
+ }
98
+ /** Drop leading slashes, keeping a bare `"/"` intact. */
99
+ function trimStartSlash(url) {
100
+ return url.replace(/^\/+/, "");
101
+ }
102
+ /**
103
+ * Join url segments into one path, normalising the slashes between them.
104
+ *
105
+ * An absolute `segment` wins outright, matching axios' `baseURL` semantics.
106
+ *
107
+ * ```ts
108
+ * joinURL("/api/", "/user/", "/list") // "/api/user/list"
109
+ * joinURL("/api", "https://cdn.x/y") // "https://cdn.x/y"
110
+ * ```
111
+ */
112
+ function joinURL(...segments) {
113
+ const parts = segments.filter((segment) => typeof segment === "string" && segment.length > 0);
114
+ if (parts.length === 0) return "";
115
+ let result = parts[0];
116
+ for (const part of parts.slice(1)) {
117
+ if (isAbsoluteURL(part)) {
118
+ result = part;
119
+ continue;
120
+ }
121
+ if (result.length === 0) {
122
+ result = part;
123
+ continue;
124
+ }
125
+ result = `${trimEndSlash(result)}/${trimStartSlash(part)}`;
126
+ }
127
+ return result.replace(/([^:]\/)\/+/g, "$1");
128
+ }
129
+ /** Matches `:id` style placeholders, ignoring a leading `::` escape. */
130
+ var PATH_PARAM_PATTERN = /:([A-Za-z_$][\w$]*)/g;
131
+ /** Names of every `:placeholder` present in a route template. */
132
+ function pathParamNames(route) {
133
+ const names = [];
134
+ for (const match of route.matchAll(PATH_PARAM_PATTERN)) names.push(match[1]);
135
+ return names;
136
+ }
137
+ /**
138
+ * Substitute `:placeholder` segments with values.
139
+ *
140
+ * Values are URL-encoded. A placeholder with no value throws, because silently
141
+ * leaving `:id` in a request path produces a confusing 404 far from the cause —
142
+ * the pre-rewrite code logged to `console.error` and then threw anyway.
143
+ */
144
+ function replacePathParams(route, values, onMissing) {
145
+ return route.replace(PATH_PARAM_PATTERN, (match, name) => {
146
+ const value = values[name];
147
+ if (value === void 0 || value === null) {
148
+ if (onMissing) onMissing(name);
149
+ return match;
150
+ }
151
+ return encodeURIComponent(String(value));
152
+ });
153
+ }
154
+ /** Build a full url, appending absolute urls verbatim. */
155
+ function buildRequestURL(baseURL, url) {
156
+ if (isAbsoluteURL(url)) return url;
157
+ return joinURL(baseURL, url);
158
+ }
159
+ /** Strip the query string and hash, for logging. */
160
+ function stripQuery(url) {
161
+ return url.split(/[?#]/, 1)[0];
162
+ }
163
+ /**
164
+ * Deterministic, recursively stable JSON serialisation.
165
+ *
166
+ * Object keys are sorted so two logically identical payloads always produce the
167
+ * same string — essential for cache keys and for request de-duplication.
168
+ */
169
+ function stableStringify(value) {
170
+ const seen = /* @__PURE__ */ new WeakSet();
171
+ const walk = (input) => {
172
+ if (input === null) return "null";
173
+ const type = typeof input;
174
+ if (type === "number") return Number.isFinite(input) ? String(input) : "null";
175
+ if (type === "boolean") return String(input);
176
+ if (type === "bigint") return `"${String(input)}"`;
177
+ if (type === "string") return JSON.stringify(input);
178
+ if (type === "undefined") return "undefined";
179
+ if (type === "function" || type === "symbol") return "undefined";
180
+ if (input instanceof Date) return `"${input.toISOString()}"`;
181
+ if (input instanceof RegExp) return `"${input.toString()}"`;
182
+ if (Array.isArray(input)) return `[${input.map(walk).join(",")}]`;
183
+ if (isPlainObject(input)) {
184
+ const object = input;
185
+ if (seen.has(object)) return "\"[Circular]\"";
186
+ seen.add(object);
187
+ const body = Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${walk(object[key])}`).join(",");
188
+ seen.delete(object);
189
+ return `{${body}}`;
190
+ }
191
+ return JSON.stringify(String(input));
192
+ };
193
+ return walk(value);
194
+ }
195
+ /**
196
+ * 32-bit FNV-1a hash rendered as base36.
197
+ *
198
+ * Cheap, allocation-light and stable across runs — used to turn a long request
199
+ * signature into a compact in-memory cache key. It is *not* a security hash.
200
+ */
201
+ function shortHash(input) {
202
+ let hash = 2166136261;
203
+ for (let index = 0; index < input.length; index++) {
204
+ hash ^= input.charCodeAt(index);
205
+ hash = Math.imul(hash, 16777619);
206
+ }
207
+ return (hash >>> 0).toString(36);
208
+ }
209
+ //#endregion
210
+ //#region src/locale/en.ts
211
+ /** English message catalogue. */
212
+ var en = {
213
+ "error.decorator.method.duplicate": "method [%s] already has a request-method decorator (@Get/@Post/...)",
214
+ "error.decorator.method.missing": "method [%s] has no request-method decorator (@Get/@Post/...), cannot send a request",
215
+ "error.decorator.param.context": "@%s may only be used on an instance method parameter",
216
+ "error.decorator.param.empty": "bad parameter on method [%s]: a key-less @%s argument must be a plain object",
217
+ "error.decorator.param.untyped": "@%s needs a string key, or no key at all to spread the whole object",
218
+ "error.decorator.class.target": "@%s may only be applied to a class",
219
+ "error.decorator.server.notFound": "api class [%s] is missing the @Api() decorator",
220
+ "error.decorator.stream.duplicate": "class [%s] already has a connection decorator (@Sse/@WebSocket)",
221
+ "error.options.server.missing": "server class [%s] is missing the @Server() decorator",
222
+ "error.options.server.baseURL": "@Server() baseURL must be a non-empty string",
223
+ "error.options.api.url": "@Api() url must be a string",
224
+ "error.options.plugin.notFound": "plugin [%s] is not registered on server [%s]",
225
+ "error.options.plugin.missing": "plugin [%s] depends on [%s], which has not been registered yet — call use() first",
226
+ "error.options.plugin.exists": "plugin [%s] is already registered on server [%s]",
227
+ "error.hook.next.multiple": "plugin [%s] called next() more than once inside the %s hook",
228
+ "error.hook.unknown": "unknown plugin lifecycle hook [%s]",
229
+ "error.request.failed": "[%s] request failed: %s",
230
+ "error.request.timeout": "[%s] request timed out (%sms)",
231
+ "error.request.cancelled": "[%s] request cancelled",
232
+ "error.response.code": "[%s] business status code rejected: code=%s",
233
+ "error.response.shape": "[%s] response does not match the agreed envelope: missing [%s]",
234
+ "error.response.json": "[%s] failed to parse the JSON response: %s",
235
+ "error.path.missing": "route [%s] has no value for placeholder [:%s] — add @Params('%s') to the method parameter",
236
+ "error.plugin.validate.request": "[%s] request payload failed validation",
237
+ "error.plugin.validate.response": "[%s] response payload failed validation",
238
+ "error.plugin.transform": "[%s] payload transform failed: %s",
239
+ "error.plugin.cache.adapter": "unknown cache adapter [%s]; expected one of memory, localStorage, sessionStorage, indexedDB",
240
+ "info.request.start": "→ %s %s [%s]",
241
+ "info.request.success": "← %s %s [%s] %s",
242
+ "info.request.codeError": "← %s %s [%s] bad business code %s",
243
+ "info.request.error": "← %s %s [%s] %s",
244
+ "info.cache.hit": "[%s] cache hit",
245
+ "info.cache.set": "[%s] cached",
246
+ "info.cache.invalidate": "[%s] cache invalidated (source: %s)",
247
+ "info.version.default": "[%s] using default version %s",
248
+ "info.version.change": "[%s] version switched to %s",
249
+ "warn.version.change": "[%s] version differs from the default: %s → %s",
250
+ "info.sse.open": "[%s] SSE connection opened",
251
+ "info.sse.close": "[%s] SSE connection closed",
252
+ "info.ws.open": "[%s] WebSocket connection opened",
253
+ "info.ws.close": "[%s] WebSocket connection closed (code=%s)"
254
+ };
255
+ //#endregion
256
+ //#region src/locale/zh.ts
257
+ /** Chinese (zh-CN) message catalogue. */
258
+ var zh = {
259
+ "error.decorator.method.duplicate": "方法[%s]上只能使用一个请求方式装饰器(@Get/@Post/...)",
260
+ "error.decorator.method.missing": "方法[%s]缺少请求方式装饰器(@Get/@Post/...),无法发送请求",
261
+ "error.decorator.param.context": "@%s 只能用于类的实例方法参数,不能用于构造函数或静态成员",
262
+ "error.decorator.param.empty": "方法[%s]的参数标记错误:未传入 key 时该参数必须是普通对象",
263
+ "error.decorator.param.untyped": "参数装饰器 @%s 需要一个字符串 key,或省略 key 以展开整个对象",
264
+ "error.decorator.class.target": "@%s 只能用于类",
265
+ "error.decorator.server.notFound": "接口类[%s]缺少 @Api() 装饰器",
266
+ "error.decorator.stream.duplicate": "类[%s]上只能使用一个连接类装饰器(@Sse/@WebSocket)",
267
+ "error.options.server.missing": "服务类[%s]缺少 @Server() 装饰器",
268
+ "error.options.server.baseURL": "@Server() 的 baseURL 必须是非空字符串",
269
+ "error.options.api.url": "@Api() 的 url 必须是字符串",
270
+ "error.options.plugin.notFound": "插件[%s]未在服务[%s]上注册",
271
+ "error.options.plugin.missing": "插件[%s]依赖的插件[%s]尚未注册,请先 use() 它",
272
+ "error.options.plugin.exists": "插件[%s]已在服务[%s]上注册,请勿重复注册",
273
+ "error.hook.next.multiple": "插件[%s]的 %s 钩子多次调用了 next()",
274
+ "error.hook.unknown": "未知的插件生命周期钩子[%s]",
275
+ "error.request.failed": "[%s] 请求失败:%s",
276
+ "error.request.timeout": "[%s] 请求超时(%sms)",
277
+ "error.request.cancelled": "[%s] 请求已取消",
278
+ "error.response.code": "[%s] 业务状态码校验未通过:code=%s",
279
+ "error.response.shape": "[%s] 服务端返回数据不符合约定结构:缺少字段[%s]",
280
+ "error.response.json": "[%s] 服务端返回的 JSON 解析失败:%s",
281
+ "error.path.missing": "路由[%s]中的占位符[:%s]没有对应的参数值,请在方法参数上添加 @Params('%s')",
282
+ "error.plugin.validate.request": "[%s] 请求数据校验失败",
283
+ "error.plugin.validate.response": "[%s] 响应数据校验失败",
284
+ "error.plugin.transform": "[%s] 数据转换失败:%s",
285
+ "error.plugin.cache.adapter": "未知的缓存适配器[%s],可选:memory、localStorage、sessionStorage、indexedDB",
286
+ "info.request.start": "→ %s %s [%s]",
287
+ "info.request.success": "← %s %s [%s] %s",
288
+ "info.request.codeError": "← %s %s [%s] 业务码异常 %s",
289
+ "info.request.error": "← %s %s [%s] %s",
290
+ "info.cache.hit": "[%s] 缓存命中",
291
+ "info.cache.set": "[%s] 写入缓存",
292
+ "info.cache.invalidate": "[%s] 缓存失效(失效源:%s)",
293
+ "info.version.default": "[%s] 使用默认版本 %s",
294
+ "info.version.change": "[%s] 版本切换到 %s",
295
+ "warn.version.change": "[%s] 版本与默认版本不一致:%s → %s",
296
+ "info.sse.open": "[%s] SSE 连接已建立",
297
+ "info.sse.close": "[%s] SSE 连接已关闭",
298
+ "info.ws.open": "[%s] WebSocket 连接已建立",
299
+ "info.ws.close": "[%s] WebSocket 连接已关闭(code=%s)"
300
+ };
301
+ //#endregion
302
+ //#region src/locale/index.ts
303
+ var BUILT_IN = {
304
+ zh,
305
+ en
306
+ };
307
+ /**
308
+ * Detects the ambient language without assuming a browser.
309
+ *
310
+ * The pre-rewrite implementation read `navigator.language` unconditionally,
311
+ * which threw a `ReferenceError` in Node, in a worker and during SSR. Every
312
+ * probe here is guarded, and the fallback is `en`.
313
+ */
314
+ function detectLanguage() {
315
+ const globals = globalThis;
316
+ const fromNavigator = globals.navigator?.languages?.[0] ?? globals.navigator?.language;
317
+ if (typeof fromNavigator === "string" && fromNavigator.length > 0) return fromNavigator;
318
+ const fromEnv = globals.process?.env?.SNAIL_LOCALE ?? globals.process?.env?.LC_ALL ?? globals.process?.env?.LC_MESSAGES ?? globals.process?.env?.LANG;
319
+ if (typeof fromEnv === "string" && fromEnv.length > 0) return fromEnv;
320
+ return "en";
321
+ }
322
+ function isCatalogue(value) {
323
+ return typeof value === "object" && value !== null;
324
+ }
325
+ /** Resolve `zh-CN` / `zh_TW` / `en-US` down to a shipped catalogue. */
326
+ function resolveBuiltIn(language) {
327
+ const normalized = language.toLowerCase().replace("_", "-");
328
+ if (normalized.startsWith("zh")) return zh;
329
+ if (normalized.startsWith("en")) return en;
330
+ }
331
+ /**
332
+ * Tiny message catalogue with `%s` placeholders.
333
+ *
334
+ * A single shared instance backs the module-level `t()` helper, so plugins can
335
+ * contribute their own messages with `registerMessages()`.
336
+ */
337
+ var Localization = class {
338
+ constructor(language) {
339
+ this.language = "en";
340
+ this.catalogue = en;
341
+ this.setLocale(language ?? detectLanguage());
342
+ }
343
+ /** Currently active language tag or catalogue name. */
344
+ get locale() {
345
+ return this.language;
346
+ }
347
+ /**
348
+ * Switch language.
349
+ *
350
+ * - `"zh"`, `"zh-CN"`, `"en-US"` → pick a shipped catalogue
351
+ * - a message object → merge over the current catalogue (great for plugins
352
+ * and for applications that want their own wording)
353
+ */
354
+ setLocale(input) {
355
+ if (typeof input === "string") {
356
+ this.language = input;
357
+ const builtIn = resolveBuiltIn(input);
358
+ if (builtIn) this.catalogue = builtIn;
359
+ return this;
360
+ }
361
+ if (isCatalogue(input)) {
362
+ this.catalogue = {
363
+ ...this.catalogue,
364
+ ...input
365
+ };
366
+ return this;
367
+ }
368
+ return this;
369
+ }
370
+ /** Merge extra messages into the active catalogue. */
371
+ registerMessages(messages) {
372
+ this.catalogue = {
373
+ ...this.catalogue,
374
+ ...messages
375
+ };
376
+ return this;
377
+ }
378
+ /** Current catalogue contents (a snapshot copy). */
379
+ get messages() {
380
+ return { ...this.catalogue };
381
+ }
382
+ /**
383
+ * Translate `key`, replacing each `%s` with the matching extra argument.
384
+ *
385
+ * A missing key returns the key itself rather than an empty string: the old
386
+ * implementation returned `""`, which silently swallowed typos and produced
387
+ * blank error messages.
388
+ */
389
+ t(key, ...args) {
390
+ const template = this.catalogue[key];
391
+ if (template === void 0) return key;
392
+ let index = 0;
393
+ return template.replace(/%s/g, () => {
394
+ const value = args[index++];
395
+ return value === void 0 ? "" : String(value);
396
+ });
397
+ }
398
+ };
399
+ /** Shared catalogue instance used by the whole library. */
400
+ var localization = new Localization();
401
+ /**
402
+ * Translate a message using the shared catalogue.
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * t("info.cache.hit", "default.UserApi.list"); // "[…] cache hit"
407
+ * ```
408
+ */
409
+ function t(key, ...args) {
410
+ return localization.t(key, ...args);
411
+ }
412
+ /** Switch the language of the shared catalogue. */
413
+ function setLocale(input) {
414
+ localization.setLocale(input);
415
+ }
416
+ /** Read the active language of the shared catalogue. */
417
+ function getLocale() {
418
+ return localization.locale;
419
+ }
420
+ /** Contribute extra messages to the shared catalogue (used by plugins). */
421
+ function registerMessages(messages) {
422
+ localization.registerMessages(messages);
423
+ }
424
+ var languages = Object.keys(BUILT_IN);
425
+ //#endregion
426
+ //#region src/core/args.ts
427
+ /**
428
+ * Built-in parameter resolvers.
429
+ *
430
+ * Each one mutates the live request on the context, which keeps the argument
431
+ * decorators free of any knowledge about how the request is eventually sent.
432
+ *
433
+ * A key-less decorator (`@Query() query: SomeShape`) spreads the whole object;
434
+ * a keyed one (`@Query("page") page: number`) places a single value. Getting
435
+ * that wrong is a programmer error and throws immediately, with a message that
436
+ * names the method and the offending parameter.
437
+ */
438
+ function assertPlainObject(input, source) {
439
+ if (!isPlainObject(input.value)) throw new SnailDecoratorError(t("error.decorator.param.empty", `${input.methodName}.${source}`));
440
+ }
441
+ /** Place a value into the mutable header bag, creating it when absent. */
442
+ function headerBag(ctx) {
443
+ if (!(ctx.request.headers instanceof AxiosHeaders)) ctx.request.headers = AxiosHeaders.from(ctx.request.headers ?? {});
444
+ return ctx.request.headers;
445
+ }
446
+ /** `@Params()` — fills `:placeholder` values used to build the final url. */
447
+ var resolvePathParams = (input) => {
448
+ if (input.key !== void 0) {
449
+ input.ctx.pathParams[input.key] = input.value;
450
+ return;
451
+ }
452
+ assertPlainObject(input, "params");
453
+ Object.assign(input.ctx.pathParams, input.value);
454
+ };
455
+ /** `@Query()` — merges into the query string. */
456
+ var resolveQuery = (input) => {
457
+ const current = input.ctx.request.params;
458
+ if (input.key !== void 0) {
459
+ input.ctx.request.params = {
460
+ ...isPlainObject(current) ? current : {},
461
+ [input.key]: input.value
462
+ };
463
+ return;
464
+ }
465
+ assertPlainObject(input, "query");
466
+ input.ctx.request.params = {
467
+ ...isPlainObject(current) ? current : {},
468
+ ...input.value
469
+ };
470
+ };
471
+ /**
472
+ * `@Data()` — builds the request body.
473
+ *
474
+ * With a key the value is merged into an object body. Without a key a plain
475
+ * object is merged, while anything else (`FormData`, `Blob`, a raw string,
476
+ * an array) *replaces* the body so non-JSON uploads stay possible.
477
+ */
478
+ var resolveBody = (input) => {
479
+ const current = input.ctx.request.data;
480
+ if (input.key !== void 0) {
481
+ const base = isPlainObject(current) ? current : {};
482
+ input.ctx.request.data = {
483
+ ...base,
484
+ [input.key]: input.value
485
+ };
486
+ return;
487
+ }
488
+ if (isPlainObject(input.value)) {
489
+ const base = isPlainObject(current) ? current : {};
490
+ input.ctx.request.data = {
491
+ ...base,
492
+ ...input.value
493
+ };
494
+ return;
495
+ }
496
+ input.ctx.request.data = input.value;
497
+ };
498
+ /** `@Header()` — merges into the request headers. */
499
+ var resolveHeader = (input) => {
500
+ const headers = headerBag(input.ctx);
501
+ if (input.key !== void 0) {
502
+ headers.set(input.key, input.value);
503
+ return;
504
+ }
505
+ assertPlainObject(input, "header");
506
+ for (const [key, value] of Object.entries(input.value)) headers.set(key, value);
507
+ };
508
+ /**
509
+ * The resolver registry.
510
+ *
511
+ * `createParamDecorator` looks sources up here, so a plugin can either reuse a
512
+ * built-in source or register its own.
513
+ */
514
+ var paramResolvers = {
515
+ params: resolvePathParams,
516
+ query: resolveQuery,
517
+ data: resolveBody,
518
+ header: resolveHeader
519
+ };
520
+ /** Register a resolver under a custom source name. */
521
+ function registerParamResolver(source, resolver) {
522
+ paramResolvers[source] = resolver;
523
+ }
524
+ /** Human-readable label used in decorator error messages. */
525
+ function sourceLabel(source) {
526
+ return capitalize(source);
527
+ }
528
+ /** `true` when `source` has a registered resolver. */
529
+ function hasParamResolver(source) {
530
+ return typeof paramResolvers[source] === "function";
531
+ }
532
+ /** Names of every registered parameter source. */
533
+ function paramSources() {
534
+ return Object.keys(paramResolvers);
535
+ }
536
+ /**
537
+ * Apply every decorated argument to the request.
538
+ *
539
+ * Descriptors run in ascending parameter-index order so `@Data("a") a` followed
540
+ * by `@Data("b") b` produces `{ a, b }` regardless of decorator evaluation order
541
+ * (TypeScript applies parameter decorators in *reverse* index order).
542
+ */
543
+ function applyParamDescriptors(ctx, args) {
544
+ if (ctx.descriptors.length === 0) return;
545
+ const ordered = [...ctx.descriptors].sort((a, b) => a.index - b.index);
546
+ for (const descriptor of ordered) descriptor.resolve({
547
+ ctx,
548
+ value: args[descriptor.index],
549
+ key: descriptor.key,
550
+ options: descriptor.options,
551
+ index: descriptor.index,
552
+ methodName: ctx.methodName
553
+ });
554
+ }
555
+ /**
556
+ * Substitute `:placeholder` segments and write the final url onto the request.
557
+ *
558
+ * Runs after the argument decorators, because the whole point is to have their
559
+ * values available.
560
+ */
561
+ function finalizeRequestURL(ctx) {
562
+ const route = ctx.route;
563
+ if (!route.includes(":")) {
564
+ ctx.request.url = route;
565
+ return;
566
+ }
567
+ const missing = [];
568
+ const resolved = replacePathParams(route, ctx.pathParams, (name) => {
569
+ missing.push(name);
570
+ throw new SnailDecoratorError(t("error.path.missing", route, name, name));
571
+ });
572
+ if (missing.length === 0) ctx.request.url = resolved;
573
+ }
574
+ //#endregion
575
+ //#region src/error/response.ts
576
+ /**
577
+ * Thrown when the backend answered with HTTP success but a business status code
578
+ * that the configured {@link ServerOptions.validateCode} rule rejected.
579
+ *
580
+ * The whole parsed body is preserved on `payload`, so an application error
581
+ * handler can still read `payload.message` / `payload.data`.
582
+ */
583
+ var SnailResponseError = class extends SnailError {
584
+ constructor(message, options) {
585
+ super(message, {
586
+ code: "SNAIL_RESPONSE_ERROR",
587
+ cause: options.cause
588
+ });
589
+ this.businessCode = options.businessCode;
590
+ this.payload = options.payload;
591
+ }
592
+ };
593
+ /**
594
+ * Thrown when the transport failed: non-2xx status, network failure, timeout or
595
+ * abort. `status` is present only when a response actually came back.
596
+ */
597
+ var SnailHttpError = class extends SnailError {
598
+ constructor(message, options = {}) {
599
+ super(message, {
600
+ code: options.code ?? "SNAIL_HTTP_ERROR",
601
+ cause: options.cause
602
+ });
603
+ this.status = options.status;
604
+ this.statusText = options.statusText;
605
+ this.payload = options.payload;
606
+ }
607
+ };
608
+ //#endregion
609
+ //#region src/error/hook.ts
610
+ /**
611
+ * Thrown when a plugin lifecycle chain is driven illegally — calling `next()`
612
+ * twice from one hook, or calling `next()` after the chain already settled.
613
+ *
614
+ * This is the Koa `compose` invariant, and it catches a very common plugin bug:
615
+ * a hook that both `await next()` *and* falls through into a second `next()`.
616
+ */
617
+ var SnailHookError = class extends SnailError {
618
+ constructor(hook, message) {
619
+ super(message, { code: "SNAIL_HOOK_ERROR" });
620
+ this.hook = hook;
621
+ }
622
+ };
623
+ //#endregion
624
+ //#region src/core/plugin.ts
625
+ /**
626
+ * Plugin machinery.
627
+ *
628
+ * ## Everything is a plugin
629
+ *
630
+ * The core ships *no* optional behaviour: caching, versioning, interceptors,
631
+ * validation, transformation and framework adapters are all plugins. Core owns
632
+ * exactly three things — the metadata written by decorators, the request
633
+ * pipeline, and this plugin lifecycle.
634
+ *
635
+ * ## Two kinds of hook
636
+ *
637
+ * - **Chain hooks** (`beforeRequest`, `afterResponse`) are Koa-style middleware:
638
+ * a hook receives `next`, may `await` it, and may decline to call it to stop
639
+ * the request. This is what makes cross-cutting concerns composable.
640
+ * - **Effect hooks** (`initMeta`, `beforeCreate`, `onError`, `afterRequest`) run
641
+ * sequentially and cannot influence control flow.
642
+ */
643
+ /** Identity helper that preserves the option type of a plugin factory. */
644
+ function definePlugin(factory) {
645
+ return factory;
646
+ }
647
+ /**
648
+ * Create a third-party plugin.
649
+ *
650
+ * This is the supported entry point for plugin authors. It validates the name,
651
+ * wires `install`/`uninstall`, exposes a scoped {@link PluginSetupApi} and keeps
652
+ * the hook object fully typed.
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * interface TraceOptions { header?: string }
657
+ *
658
+ * export const Trace = createPlugin<TraceOptions, { onSend?: (url: string) => void }>({
659
+ * name: "trace",
660
+ * priority: 20,
661
+ * setup(options, api) {
662
+ * const header = options?.header ?? "x-trace-id";
663
+ * api.addMessages({ "trace.missing": "trace header %s is missing" });
664
+ * return {
665
+ * beforeRequest(ctx) {
666
+ * ctx.request.headers.set(header, crypto.randomUUID());
667
+ * }
668
+ * };
669
+ * }
670
+ * });
671
+ *
672
+ * Service.use(Trace({ header: "x-trace-id" }));
673
+ * ```
674
+ */
675
+ function createPlugin(definition) {
676
+ if (!definition || typeof definition.name !== "string" || definition.name.length === 0) throw new TypeError("[snail] createPlugin() requires a non-empty `name`");
677
+ const { name, priority = 0, dependsOn, setup } = definition;
678
+ return (options) => {
679
+ const disposers = [];
680
+ const plugin = {
681
+ name,
682
+ priority,
683
+ dependsOn,
684
+ options,
685
+ install(installCtx) {
686
+ if (!setup) return;
687
+ const hooks = setup(options, {
688
+ serverName: installCtx.serverName,
689
+ serverOptions: installCtx.serverOptions,
690
+ installedPlugins: installCtx.pluginNames,
691
+ defineParamSource: registerParamResolver,
692
+ addMessages: registerMessages,
693
+ onDispose: (dispose) => {
694
+ disposers.push(dispose);
695
+ }
696
+ });
697
+ if (hooks) Object.assign(plugin, hooks);
698
+ },
699
+ async uninstall() {
700
+ while (disposers.length > 0) await disposers.pop()();
701
+ }
702
+ };
703
+ return plugin;
704
+ };
705
+ }
706
+ /**
707
+ * Compose chain hooks into a single Koa-style function.
708
+ *
709
+ * The returned function runs `entries[0]` first (they are pre-sorted by the
710
+ * manager), handing each hook a `next` that advances the chain. When the chain
711
+ * is exhausted, `downstream` runs — that is where the manager splices in the
712
+ * actual HTTP request between `beforeRequest` and `afterResponse`.
713
+ *
714
+ * Calling `next()` twice from one hook throws {@link SnailHookError}; without
715
+ * that guard the second call silently re-runs the rest of the chain.
716
+ */
717
+ function composeChain(hookName, entries) {
718
+ return async function runChain(ctx, downstream) {
719
+ let lastIndex = -1;
720
+ const dispatch = async (index) => {
721
+ if (index <= lastIndex) throw new SnailHookError(hookName, t("error.hook.next.multiple", entries[Math.max(lastIndex, 0)]?.pluginName ?? "unknown", hookName));
722
+ lastIndex = index;
723
+ const entry = entries[index];
724
+ if (!entry) {
725
+ await downstream?.();
726
+ return;
727
+ }
728
+ let advanced = false;
729
+ const next = async () => {
730
+ if (advanced) throw new SnailHookError(hookName, t("error.hook.next.multiple", entry.pluginName, hookName));
731
+ advanced = true;
732
+ await dispatch(index + 1);
733
+ };
734
+ await entry.hook(ctx, next);
735
+ };
736
+ await dispatch(0);
737
+ };
738
+ }
739
+ //#endregion
740
+ export { stableStringify as A, SnailError as B, buildRequestURL as C, pathParamNames as D, joinURL as E, isFunction as F, isObject as I, isPlainObject as L, isBinaryBody as M, isBrowser as N, replacePathParams as O, isDefined as P, isPromise as R, en as S, isAbsoluteURL as T, localization as _, SnailHttpError as a, t as b, finalizeRequestURL as c, paramSources as d, registerParamResolver as f, languages as g, getLocale as h, SnailHookError as i, stripQuery as j, shortHash as k, hasParamResolver as l, Localization as m, createPlugin as n, SnailResponseError as o, sourceLabel as p, definePlugin as r, applyParamDescriptors as s, composeChain as t, paramResolvers as u, registerMessages as v, capitalize as w, zh as x, setLocale as y, SnailDecoratorError as z };
741
+
742
+ //# sourceMappingURL=plugin-D3RDffDG.js.map