@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 @@
1
+ {"version":3,"file":"plugin-D3RDffDG.js","names":[],"sources":["../../src/error/base.ts","../../src/error/decorator.ts","../../src/utils/is.ts","../../src/utils/url.ts","../../src/locale/en.ts","../../src/locale/zh.ts","../../src/locale/index.ts","../../src/core/args.ts","../../src/error/response.ts","../../src/error/hook.ts","../../src/core/plugin.ts"],"sourcesContent":["/**\n * Base class of every error `@snail-js/api` throws.\n *\n * A stable `code` is attached to each subclass so application code can branch\n * on `error.code` without importing the class, and so the localization layer can\n * render a translated message.\n */\nexport class SnailError extends Error {\n /** Machine-readable, stable identifier of this error kind. */\n readonly code: string;\n\n /** The underlying cause, when one exists (usually an `AxiosError`). */\n override readonly cause: unknown;\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown } = {}\n ) {\n super(message);\n this.name = new.target.name;\n this.code = options.code ?? \"SNAIL_ERROR\";\n this.cause = options.cause;\n\n if (typeof Error.captureStackTrace === \"function\") {\n Error.captureStackTrace(this, new.target);\n }\n }\n\n /** `true` for every error originating from this library. */\n static isSnailError(value: unknown): value is SnailError {\n return value instanceof SnailError;\n }\n}\n","import { SnailError } from \"./base\";\n\n/**\n * Thrown when a decorator is applied incorrectly — two request-method decorators\n * on one method, a parameter decorator on a constructor, and so on.\n *\n * These are programmer errors and always surface eagerly, as early as the\n * decorated class is evaluated.\n */\nexport class SnailDecoratorError extends SnailError {\n constructor(message: string, options: { cause?: unknown } = {}) {\n super(message, { code: \"SNAIL_DECORATOR_ERROR\", cause: options.cause });\n }\n}\n","/** Runtime type predicates shared across the library. */\n\n/** `true` for a non-null object (including arrays, dates, class instances). */\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * `true` for a plain object literal — not an array, `Date`, `Map`, `FormData`,\n * `Blob`, class instance, and so on.\n *\n * Used to decide whether a `@Query()` / `@Data()` argument should be spread\n * into the outgoing payload.\n */\nexport function isPlainObject(value: unknown): value is Record<string, any> {\n if (Object.prototype.toString.call(value) !== \"[object Object]\") return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === null || prototype === Object.prototype;\n}\n\n/** `true` for a callable value. */\nexport function isFunction(value: unknown): value is (...args: any[]) => any {\n return typeof value === \"function\";\n}\n\n/** `true` for a thenable. */\nexport function isPromise<T = unknown>(value: unknown): value is Promise<T> {\n return (\n isObject(value) && isFunction((value as { then?: unknown }).then)\n );\n}\n\n/** `true` when `value` is neither `null` nor `undefined`. */\nexport function isDefined<T>(value: T | null | undefined): value is T {\n return value !== null && value !== undefined;\n}\n\n/** `true` when a `Response`-like body should be passed through untouched. */\nexport function isBinaryBody(value: unknown): boolean {\n if (typeof Blob !== \"undefined\" && value instanceof Blob) return true;\n if (typeof ArrayBuffer !== \"undefined\" && value instanceof ArrayBuffer) return true;\n if (typeof FormData !== \"undefined\" && value instanceof FormData) return true;\n if (typeof URLSearchParams !== \"undefined\" && value instanceof URLSearchParams) return true;\n if (typeof ReadableStream !== \"undefined\" && value instanceof ReadableStream) return true;\n return false;\n}\n\n/** `true` when the code is executing in a DOM-capable browser. */\nexport function isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n","import { isPlainObject } from \"./is\";\n\n/** Uppercase the first character, leaving the rest untouched. */\nexport function capitalize(value: string): string {\n return value.length === 0 ? value : value[0]!.toUpperCase() + value.slice(1);\n}\n\n/** `true` when the url carries its own scheme or protocol-relative prefix. */\nexport function isAbsoluteURL(url: string): boolean {\n return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url);\n}\n\n/** Drop trailing slashes without touching a bare `\"/\"`. */\nfunction trimEndSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Drop leading slashes, keeping a bare `\"/\"` intact. */\nfunction trimStartSlash(url: string): string {\n return url.replace(/^\\/+/, \"\");\n}\n\n/**\n * Join url segments into one path, normalising the slashes between them.\n *\n * An absolute `segment` wins outright, matching axios' `baseURL` semantics.\n *\n * ```ts\n * joinURL(\"/api/\", \"/user/\", \"/list\") // \"/api/user/list\"\n * joinURL(\"/api\", \"https://cdn.x/y\") // \"https://cdn.x/y\"\n * ```\n */\nexport function joinURL(...segments: Array<string | undefined | null>): string {\n const parts = segments.filter(\n (segment): segment is string => typeof segment === \"string\" && segment.length > 0\n );\n if (parts.length === 0) return \"\";\n\n let result = parts[0]!;\n for (const part of parts.slice(1)) {\n if (isAbsoluteURL(part)) {\n result = part;\n continue;\n }\n if (result.length === 0) {\n result = part;\n continue;\n }\n result = `${trimEndSlash(result)}/${trimStartSlash(part)}`;\n }\n\n // Preserve a meaningful leading slash, collapse any accidental duplicates.\n return result.replace(/([^:]\\/)\\/+/g, \"$1\");\n}\n\n/** Matches `:id` style placeholders, ignoring a leading `::` escape. */\nconst PATH_PARAM_PATTERN = /:([A-Za-z_$][\\w$]*)/g;\n\n/** Names of every `:placeholder` present in a route template. */\nexport function pathParamNames(route: string): string[] {\n const names: string[] = [];\n for (const match of route.matchAll(PATH_PARAM_PATTERN)) {\n names.push(match[1]!);\n }\n return names;\n}\n\n/**\n * Substitute `:placeholder` segments with values.\n *\n * Values are URL-encoded. A placeholder with no value throws, because silently\n * leaving `:id` in a request path produces a confusing 404 far from the cause —\n * the pre-rewrite code logged to `console.error` and then threw anyway.\n */\nexport function replacePathParams(\n route: string,\n values: Record<string, unknown>,\n onMissing?: (name: string) => never\n): string {\n return route.replace(PATH_PARAM_PATTERN, (match, name: string) => {\n const value = values[name];\n if (value === undefined || value === null) {\n if (onMissing) onMissing(name);\n return match;\n }\n return encodeURIComponent(String(value));\n });\n}\n\n/** Build a full url, appending absolute urls verbatim. */\nexport function buildRequestURL(baseURL: string, url: string): string {\n if (isAbsoluteURL(url)) return url;\n return joinURL(baseURL, url);\n}\n\n/** Strip the query string and hash, for logging. */\nexport function stripQuery(url: string): string {\n return url.split(/[?#]/, 1)[0]!;\n}\n\n/**\n * Deterministic, recursively stable JSON serialisation.\n *\n * Object keys are sorted so two logically identical payloads always produce the\n * same string — essential for cache keys and for request de-duplication.\n */\nexport function stableStringify(value: unknown): string {\n const seen = new WeakSet<object>();\n\n const walk = (input: unknown): string => {\n if (input === null) return \"null\";\n\n const type = typeof input;\n if (type === \"number\") return Number.isFinite(input as number) ? String(input) : \"null\";\n if (type === \"boolean\") return String(input);\n if (type === \"bigint\") return `\"${String(input)}\"`;\n if (type === \"string\") return JSON.stringify(input);\n if (type === \"undefined\") return \"undefined\";\n if (type === \"function\" || type === \"symbol\") return \"undefined\";\n\n if (input instanceof Date) return `\"${input.toISOString()}\"`;\n if (input instanceof RegExp) return `\"${input.toString()}\"`;\n\n if (Array.isArray(input)) {\n return `[${input.map(walk).join(\",\")}]`;\n }\n\n if (isPlainObject(input)) {\n const object = input as Record<string, unknown>;\n if (seen.has(object)) return '\"[Circular]\"';\n seen.add(object);\n const body = Object.keys(object)\n .sort()\n .map((key) => `${JSON.stringify(key)}:${walk(object[key])}`)\n .join(\",\");\n seen.delete(object);\n return `{${body}}`;\n }\n\n return JSON.stringify(String(input));\n };\n\n return walk(value);\n}\n\n/**\n * 32-bit FNV-1a hash rendered as base36.\n *\n * Cheap, allocation-light and stable across runs — used to turn a long request\n * signature into a compact in-memory cache key. It is *not* a security hash.\n */\nexport function shortHash(input: string): string {\n let hash = 0x811c9dc5;\n for (let index = 0; index < input.length; index++) {\n hash ^= input.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0).toString(36);\n}\n","import type { SnailMessages } from \"./types\";\n\n/** English message catalogue. */\nconst en: SnailMessages = {\n // decorator misuse\n \"error.decorator.method.duplicate\": \"method [%s] already has a request-method decorator (@Get/@Post/...)\",\n \"error.decorator.method.missing\": \"method [%s] has no request-method decorator (@Get/@Post/...), cannot send a request\",\n \"error.decorator.param.context\": \"@%s may only be used on an instance method parameter\",\n \"error.decorator.param.empty\": \"bad parameter on method [%s]: a key-less @%s argument must be a plain object\",\n \"error.decorator.param.untyped\": \"@%s needs a string key, or no key at all to spread the whole object\",\n \"error.decorator.class.target\": \"@%s may only be applied to a class\",\n \"error.decorator.server.notFound\": \"api class [%s] is missing the @Api() decorator\",\n \"error.decorator.stream.duplicate\": \"class [%s] already has a connection decorator (@Sse/@WebSocket)\",\n\n // options\n \"error.options.server.missing\": \"server class [%s] is missing the @Server() decorator\",\n \"error.options.server.baseURL\": \"@Server() baseURL must be a non-empty string\",\n \"error.options.api.url\": \"@Api() url must be a string\",\n \"error.options.plugin.notFound\": \"plugin [%s] is not registered on server [%s]\",\n \"error.options.plugin.missing\": \"plugin [%s] depends on [%s], which has not been registered yet — call use() first\",\n \"error.options.plugin.exists\": \"plugin [%s] is already registered on server [%s]\",\n\n // hooks\n \"error.hook.next.multiple\": \"plugin [%s] called next() more than once inside the %s hook\",\n \"error.hook.unknown\": \"unknown plugin lifecycle hook [%s]\",\n\n // request / response\n \"error.request.failed\": \"[%s] request failed: %s\",\n \"error.request.timeout\": \"[%s] request timed out (%sms)\",\n \"error.request.cancelled\": \"[%s] request cancelled\",\n \"error.response.code\": \"[%s] business status code rejected: code=%s\",\n \"error.response.shape\": \"[%s] response does not match the agreed envelope: missing [%s]\",\n \"error.response.json\": \"[%s] failed to parse the JSON response: %s\",\n\n // path params\n \"error.path.missing\": \"route [%s] has no value for placeholder [:%s] — add @Params('%s') to the method parameter\",\n\n // plugins\n \"error.plugin.validate.request\": \"[%s] request payload failed validation\",\n \"error.plugin.validate.response\": \"[%s] response payload failed validation\",\n \"error.plugin.transform\": \"[%s] payload transform failed: %s\",\n \"error.plugin.cache.adapter\": \"unknown cache adapter [%s]; expected one of memory, localStorage, sessionStorage, indexedDB\",\n\n // info\n \"info.request.start\": \"→ %s %s [%s]\",\n \"info.request.success\": \"← %s %s [%s] %s\",\n \"info.request.codeError\": \"← %s %s [%s] bad business code %s\",\n \"info.request.error\": \"← %s %s [%s] %s\",\n \"info.cache.hit\": \"[%s] cache hit\",\n \"info.cache.set\": \"[%s] cached\",\n \"info.cache.invalidate\": \"[%s] cache invalidated (source: %s)\",\n \"info.version.default\": \"[%s] using default version %s\",\n \"info.version.change\": \"[%s] version switched to %s\",\n \"warn.version.change\": \"[%s] version differs from the default: %s → %s\",\n \"info.sse.open\": \"[%s] SSE connection opened\",\n \"info.sse.close\": \"[%s] SSE connection closed\",\n \"info.ws.open\": \"[%s] WebSocket connection opened\",\n \"info.ws.close\": \"[%s] WebSocket connection closed (code=%s)\"\n};\n\nexport default en;\n","import type { SnailMessages } from \"./types\";\n\n/** Chinese (zh-CN) message catalogue. */\nconst zh: SnailMessages = {\n // decorator misuse\n \"error.decorator.method.duplicate\": \"方法[%s]上只能使用一个请求方式装饰器(@Get/@Post/...)\",\n \"error.decorator.method.missing\": \"方法[%s]缺少请求方式装饰器(@Get/@Post/...),无法发送请求\",\n \"error.decorator.param.context\": \"@%s 只能用于类的实例方法参数,不能用于构造函数或静态成员\",\n \"error.decorator.param.empty\": \"方法[%s]的参数标记错误:未传入 key 时该参数必须是普通对象\",\n \"error.decorator.param.untyped\": \"参数装饰器 @%s 需要一个字符串 key,或省略 key 以展开整个对象\",\n \"error.decorator.class.target\": \"@%s 只能用于类\",\n \"error.decorator.server.notFound\": \"接口类[%s]缺少 @Api() 装饰器\",\n \"error.decorator.stream.duplicate\": \"类[%s]上只能使用一个连接类装饰器(@Sse/@WebSocket)\",\n\n // options\n \"error.options.server.missing\": \"服务类[%s]缺少 @Server() 装饰器\",\n \"error.options.server.baseURL\": \"@Server() 的 baseURL 必须是非空字符串\",\n \"error.options.api.url\": \"@Api() 的 url 必须是字符串\",\n \"error.options.plugin.notFound\": \"插件[%s]未在服务[%s]上注册\",\n \"error.options.plugin.missing\": \"插件[%s]依赖的插件[%s]尚未注册,请先 use() 它\",\n \"error.options.plugin.exists\": \"插件[%s]已在服务[%s]上注册,请勿重复注册\",\n\n // hooks\n \"error.hook.next.multiple\": \"插件[%s]的 %s 钩子多次调用了 next()\",\n \"error.hook.unknown\": \"未知的插件生命周期钩子[%s]\",\n\n // request / response\n \"error.request.failed\": \"[%s] 请求失败:%s\",\n \"error.request.timeout\": \"[%s] 请求超时(%sms)\",\n \"error.request.cancelled\": \"[%s] 请求已取消\",\n \"error.response.code\": \"[%s] 业务状态码校验未通过:code=%s\",\n \"error.response.shape\": \"[%s] 服务端返回数据不符合约定结构:缺少字段[%s]\",\n \"error.response.json\": \"[%s] 服务端返回的 JSON 解析失败:%s\",\n\n // path params\n \"error.path.missing\": \"路由[%s]中的占位符[:%s]没有对应的参数值,请在方法参数上添加 @Params('%s')\",\n\n // plugins\n \"error.plugin.validate.request\": \"[%s] 请求数据校验失败\",\n \"error.plugin.validate.response\": \"[%s] 响应数据校验失败\",\n \"error.plugin.transform\": \"[%s] 数据转换失败:%s\",\n \"error.plugin.cache.adapter\": \"未知的缓存适配器[%s],可选:memory、localStorage、sessionStorage、indexedDB\",\n\n // info\n \"info.request.start\": \"→ %s %s [%s]\",\n \"info.request.success\": \"← %s %s [%s] %s\",\n \"info.request.codeError\": \"← %s %s [%s] 业务码异常 %s\",\n \"info.request.error\": \"← %s %s [%s] %s\",\n \"info.cache.hit\": \"[%s] 缓存命中\",\n \"info.cache.set\": \"[%s] 写入缓存\",\n \"info.cache.invalidate\": \"[%s] 缓存失效(失效源:%s)\",\n \"info.version.default\": \"[%s] 使用默认版本 %s\",\n \"info.version.change\": \"[%s] 版本切换到 %s\",\n \"warn.version.change\": \"[%s] 版本与默认版本不一致:%s → %s\",\n \"info.sse.open\": \"[%s] SSE 连接已建立\",\n \"info.sse.close\": \"[%s] SSE 连接已关闭\",\n \"info.ws.open\": \"[%s] WebSocket 连接已建立\",\n \"info.ws.close\": \"[%s] WebSocket 连接已关闭(code=%s)\"\n};\n\nexport default zh;\n","import en from \"./en\";\nimport zh from \"./zh\";\nimport type { SnailLanguage, SnailLocaleInput, SnailMessages } from \"./types\";\n\nconst BUILT_IN: Record<string, SnailMessages> = { zh, en };\n\n/**\n * Detects the ambient language without assuming a browser.\n *\n * The pre-rewrite implementation read `navigator.language` unconditionally,\n * which threw a `ReferenceError` in Node, in a worker and during SSR. Every\n * probe here is guarded, and the fallback is `en`.\n */\nfunction detectLanguage(): SnailLanguage {\n const globals = globalThis as {\n navigator?: { language?: string; languages?: readonly string[] };\n process?: { env?: Record<string, string | undefined> };\n };\n\n const fromNavigator =\n globals.navigator?.languages?.[0] ?? globals.navigator?.language;\n if (typeof fromNavigator === \"string\" && fromNavigator.length > 0) {\n return fromNavigator;\n }\n\n const fromEnv =\n globals.process?.env?.SNAIL_LOCALE ??\n globals.process?.env?.LC_ALL ??\n globals.process?.env?.LC_MESSAGES ??\n globals.process?.env?.LANG;\n if (typeof fromEnv === \"string\" && fromEnv.length > 0) return fromEnv;\n\n return \"en\";\n}\n\nfunction isCatalogue(value: SnailLocaleInput): value is SnailMessages {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Resolve `zh-CN` / `zh_TW` / `en-US` down to a shipped catalogue. */\nfunction resolveBuiltIn(language: SnailLanguage): SnailMessages | undefined {\n const normalized = language.toLowerCase().replace(\"_\", \"-\");\n if (normalized.startsWith(\"zh\")) return zh;\n if (normalized.startsWith(\"en\")) return en;\n return undefined;\n}\n\n/**\n * Tiny message catalogue with `%s` placeholders.\n *\n * A single shared instance backs the module-level `t()` helper, so plugins can\n * contribute their own messages with `registerMessages()`.\n */\nexport class Localization {\n private language: SnailLanguage;\n private catalogue: SnailMessages;\n\n constructor(language?: SnailLocaleInput) {\n this.language = \"en\";\n this.catalogue = en;\n this.setLocale(language ?? detectLanguage());\n }\n\n /** Currently active language tag or catalogue name. */\n get locale(): SnailLanguage {\n return this.language;\n }\n\n /**\n * Switch language.\n *\n * - `\"zh\"`, `\"zh-CN\"`, `\"en-US\"` → pick a shipped catalogue\n * - a message object → merge over the current catalogue (great for plugins\n * and for applications that want their own wording)\n */\n setLocale(input: SnailLocaleInput): this {\n if (typeof input === \"string\") {\n this.language = input;\n const builtIn = resolveBuiltIn(input);\n if (builtIn) this.catalogue = builtIn;\n return this;\n }\n\n if (isCatalogue(input)) {\n this.catalogue = { ...this.catalogue, ...input };\n return this;\n }\n\n return this;\n }\n\n /** Merge extra messages into the active catalogue. */\n registerMessages(messages: SnailMessages): this {\n this.catalogue = { ...this.catalogue, ...messages };\n return this;\n }\n\n /** Current catalogue contents (a snapshot copy). */\n get messages(): SnailMessages {\n return { ...this.catalogue };\n }\n\n /**\n * Translate `key`, replacing each `%s` with the matching extra argument.\n *\n * A missing key returns the key itself rather than an empty string: the old\n * implementation returned `\"\"`, which silently swallowed typos and produced\n * blank error messages.\n */\n t(key: string, ...args: Array<string | number>): string {\n const template = this.catalogue[key];\n if (template === undefined) return key;\n\n let index = 0;\n return template.replace(/%s/g, () => {\n const value = args[index++];\n return value === undefined ? \"\" : String(value);\n });\n }\n}\n\n/** Shared catalogue instance used by the whole library. */\nexport const localization = new Localization();\n\n/**\n * Translate a message using the shared catalogue.\n *\n * @example\n * ```ts\n * t(\"info.cache.hit\", \"default.UserApi.list\"); // \"[…] cache hit\"\n * ```\n */\nexport function t(key: string, ...args: Array<string | number>): string {\n return localization.t(key, ...args);\n}\n\n/** Switch the language of the shared catalogue. */\nexport function setLocale(input: SnailLocaleInput): void {\n localization.setLocale(input);\n}\n\n/** Read the active language of the shared catalogue. */\nexport function getLocale(): SnailLanguage {\n return localization.locale;\n}\n\n/** Contribute extra messages to the shared catalogue (used by plugins). */\nexport function registerMessages(messages: SnailMessages): void {\n localization.registerMessages(messages);\n}\n\nexport { en, zh };\nexport type { SnailLanguage, SnailLocaleInput, SnailMessages };\nexport const languages: string[] = Object.keys(BUILT_IN);\n","import { AxiosHeaders } from \"axios\";\nimport { SnailDecoratorError } from \"../error/decorator\";\nimport { t } from \"../locale\";\nimport type {\n SnailBuiltinParamSource,\n SnailParamDescriptor,\n SnailParamResolver,\n SnailParamResolverInput\n} from \"../typings/args\";\nimport { capitalize } from \"../utils/url\";\nimport { isPlainObject } from \"../utils/is\";\nimport { replacePathParams } from \"../utils/url\";\nimport type { SnailContext } from \"./context\";\n\n/**\n * Built-in parameter resolvers.\n *\n * Each one mutates the live request on the context, which keeps the argument\n * decorators free of any knowledge about how the request is eventually sent.\n *\n * A key-less decorator (`@Query() query: SomeShape`) spreads the whole object;\n * a keyed one (`@Query(\"page\") page: number`) places a single value. Getting\n * that wrong is a programmer error and throws immediately, with a message that\n * names the method and the offending parameter.\n */\n\nfunction assertPlainObject(\n input: SnailParamResolverInput,\n source: string\n): asserts input is SnailParamResolverInput & { value: Record<string, unknown> } {\n if (!isPlainObject(input.value)) {\n throw new SnailDecoratorError(\n t(\"error.decorator.param.empty\", `${input.methodName}.${source}`)\n );\n }\n}\n\n/** Place a value into the mutable header bag, creating it when absent. */\nfunction headerBag(ctx: SnailContext): AxiosHeaders {\n if (!(ctx.request.headers instanceof AxiosHeaders)) {\n ctx.request.headers = AxiosHeaders.from(ctx.request.headers ?? {});\n }\n return ctx.request.headers;\n}\n\n/** `@Params()` — fills `:placeholder` values used to build the final url. */\nexport const resolvePathParams: SnailParamResolver = (input) => {\n if (input.key !== undefined) {\n input.ctx.pathParams[input.key] = input.value;\n return;\n }\n assertPlainObject(input, \"params\");\n Object.assign(input.ctx.pathParams, input.value);\n};\n\n/** `@Query()` — merges into the query string. */\nexport const resolveQuery: SnailParamResolver = (input) => {\n const current = input.ctx.request.params;\n if (input.key !== undefined) {\n input.ctx.request.params = { ...(isPlainObject(current) ? current : {}), [input.key]: input.value };\n return;\n }\n assertPlainObject(input, \"query\");\n input.ctx.request.params = {\n ...(isPlainObject(current) ? current : {}),\n ...input.value\n };\n};\n\n/**\n * `@Data()` — builds the request body.\n *\n * With a key the value is merged into an object body. Without a key a plain\n * object is merged, while anything else (`FormData`, `Blob`, a raw string,\n * an array) *replaces* the body so non-JSON uploads stay possible.\n */\nexport const resolveBody: SnailParamResolver = (input) => {\n const current = input.ctx.request.data;\n if (input.key !== undefined) {\n const base = isPlainObject(current) ? current : {};\n input.ctx.request.data = { ...base, [input.key]: input.value };\n return;\n }\n if (isPlainObject(input.value)) {\n const base = isPlainObject(current) ? current : {};\n input.ctx.request.data = { ...base, ...input.value };\n return;\n }\n input.ctx.request.data = input.value;\n};\n\n/** `@Header()` — merges into the request headers. */\nexport const resolveHeader: SnailParamResolver = (input) => {\n const headers = headerBag(input.ctx);\n if (input.key !== undefined) {\n headers.set(input.key, input.value as never);\n return;\n }\n assertPlainObject(input, \"header\");\n for (const [key, value] of Object.entries(input.value)) {\n headers.set(key, value as never);\n }\n};\n\n/**\n * The resolver registry.\n *\n * `createParamDecorator` looks sources up here, so a plugin can either reuse a\n * built-in source or register its own.\n */\nexport const paramResolvers: Record<string, SnailParamResolver> = {\n params: resolvePathParams,\n query: resolveQuery,\n data: resolveBody,\n header: resolveHeader\n};\n\n/** Register a resolver under a custom source name. */\nexport function registerParamResolver(source: string, resolver: SnailParamResolver): void {\n paramResolvers[source] = resolver;\n}\n\n/** Human-readable label used in decorator error messages. */\nexport function sourceLabel(source: string): string {\n return capitalize(source);\n}\n\n/** `true` when `source` has a registered resolver. */\nexport function hasParamResolver(source: string): boolean {\n return typeof paramResolvers[source] === \"function\";\n}\n\n/** Names of every registered parameter source. */\nexport function paramSources(): string[] {\n return Object.keys(paramResolvers);\n}\n\n/** Type guard used by the custom-decorator factory. */\nexport function isBuiltinParamSource(value: string): value is SnailBuiltinParamSource {\n return value in paramResolvers;\n}\n\n/**\n * Apply every decorated argument to the request.\n *\n * Descriptors run in ascending parameter-index order so `@Data(\"a\") a` followed\n * by `@Data(\"b\") b` produces `{ a, b }` regardless of decorator evaluation order\n * (TypeScript applies parameter decorators in *reverse* index order).\n */\nexport function applyParamDescriptors(\n ctx: SnailContext,\n args: readonly unknown[]\n): void {\n if (ctx.descriptors.length === 0) return;\n\n const ordered = [...ctx.descriptors].sort((a, b) => a.index - b.index);\n\n for (const descriptor of ordered) {\n descriptor.resolve({\n ctx,\n value: args[descriptor.index],\n key: descriptor.key,\n options: descriptor.options,\n index: descriptor.index,\n methodName: ctx.methodName\n });\n }\n}\n\n/**\n * Substitute `:placeholder` segments and write the final url onto the request.\n *\n * Runs after the argument decorators, because the whole point is to have their\n * values available.\n */\nexport function finalizeRequestURL(ctx: SnailContext): void {\n const route = ctx.route;\n if (!route.includes(\":\")) {\n ctx.request.url = route;\n return;\n }\n\n const missing: string[] = [];\n const resolved = replacePathParams(route, ctx.pathParams, (name) => {\n missing.push(name);\n throw new SnailDecoratorError(t(\"error.path.missing\", route, name, name));\n });\n\n if (missing.length === 0) ctx.request.url = resolved;\n}\n\n/** Build an `AxiosHeaders` instance from a plain record. */\nexport function toAxiosHeaders(\n headers: Record<string, unknown> | AxiosHeaders | undefined\n): AxiosHeaders {\n if (headers instanceof AxiosHeaders) return headers;\n return AxiosHeaders.from((headers ?? {}) as Record<string, string>);\n}\n\n/** Narrow a `SnailParamDescriptor` list to one source. */\nexport function descriptorsOf(\n descriptors: readonly SnailParamDescriptor[],\n source: string\n): SnailParamDescriptor[] {\n return descriptors.filter((descriptor) => descriptor.source === source);\n}\n","import { SnailError } from \"./base\";\n\n/**\n * Thrown when the backend answered with HTTP success but a business status code\n * that the configured {@link ServerOptions.validateCode} rule rejected.\n *\n * The whole parsed body is preserved on `payload`, so an application error\n * handler can still read `payload.message` / `payload.data`.\n */\nexport class SnailResponseError<T = unknown> extends SnailError {\n /** Business status code reported by the backend. */\n readonly businessCode: number | string | undefined;\n\n /** Full parsed response body. */\n readonly payload: T;\n\n constructor(\n message: string,\n options: { businessCode?: number | string; payload: T; cause?: unknown }\n ) {\n super(message, { code: \"SNAIL_RESPONSE_ERROR\", cause: options.cause });\n this.businessCode = options.businessCode;\n this.payload = options.payload;\n }\n}\n\n/**\n * Thrown when the transport failed: non-2xx status, network failure, timeout or\n * abort. `status` is present only when a response actually came back.\n */\nexport class SnailHttpError<T = unknown> extends SnailError {\n readonly status: number | undefined;\n readonly statusText: string | undefined;\n readonly payload: T | undefined;\n\n constructor(\n message: string,\n options: {\n status?: number;\n statusText?: string;\n payload?: T;\n cause?: unknown;\n code?: string;\n } = {}\n ) {\n super(message, { code: options.code ?? \"SNAIL_HTTP_ERROR\", cause: options.cause });\n this.status = options.status;\n this.statusText = options.statusText;\n this.payload = options.payload;\n }\n}\n","import { SnailError } from \"./base\";\n\n/**\n * Thrown when a plugin lifecycle chain is driven illegally — calling `next()`\n * twice from one hook, or calling `next()` after the chain already settled.\n *\n * This is the Koa `compose` invariant, and it catches a very common plugin bug:\n * a hook that both `await next()` *and* falls through into a second `next()`.\n */\nexport class SnailHookError extends SnailError {\n /** Lifecycle hook that misbehaved, e.g. `\"beforeRequest\"`. */\n readonly hook: string;\n\n constructor(hook: string, message: string) {\n super(message, { code: \"SNAIL_HOOK_ERROR\" });\n this.hook = hook;\n }\n}\n","import { registerMessages } from \"../locale\";\nimport type { SnailMessages } from \"../locale/types\";\nimport type { SnailParamResolver } from \"../typings/args\";\nimport type {\n SnailNext,\n SnailPlugin,\n SnailPluginInstallContext,\n SnailPluginObject\n} from \"../typings/plugin\";\nimport type { ResolvedServerOptions } from \"../typings/server\";\nimport { registerParamResolver } from \"./args\";\nimport { SnailHookError } from \"../error/hook\";\nimport { t } from \"../locale\";\n\n/**\n * Plugin machinery.\n *\n * ## Everything is a plugin\n *\n * The core ships *no* optional behaviour: caching, versioning, interceptors,\n * validation, transformation and framework adapters are all plugins. Core owns\n * exactly three things — the metadata written by decorators, the request\n * pipeline, and this plugin lifecycle.\n *\n * ## Two kinds of hook\n *\n * - **Chain hooks** (`beforeRequest`, `afterResponse`) are Koa-style middleware:\n * a hook receives `next`, may `await` it, and may decline to call it to stop\n * the request. This is what makes cross-cutting concerns composable.\n * - **Effect hooks** (`initMeta`, `beforeCreate`, `onError`, `afterRequest`) run\n * sequentially and cannot influence control flow.\n */\n\n/** Identity helper that preserves the option type of a plugin factory. */\nexport function definePlugin<O = unknown>(factory: SnailPlugin<O>): SnailPlugin<O> {\n return factory;\n}\n\n/** Building blocks handed to a plugin's `setup` function. */\nexport interface PluginSetupApi {\n /** Name of the server this plugin was installed on. */\n readonly serverName: string;\n\n /** Fully resolved server options. */\n readonly serverOptions: ResolvedServerOptions;\n\n /** Names of the plugins registered before this one. */\n readonly installedPlugins: readonly string[];\n\n /**\n * Register a custom parameter source usable as `@Source(\"key\")`.\n *\n * @example\n * ```ts\n * api.defineParamSource(\"tenant\", ({ ctx, value }) => {\n * ctx.request.headers.set(\"x-tenant\", String(value));\n * });\n * ```\n */\n defineParamSource(source: string, resolver: SnailParamResolver): void;\n\n /** Contribute translated messages for this plugin's own error output. */\n addMessages(messages: SnailMessages): void;\n\n /** Register cleanup to run when the plugin is uninstalled. */\n onDispose(dispose: () => void | Promise<void>): void;\n}\n\n/** Declarative description accepted by {@link createPlugin}. */\nexport interface PluginDefinition<O, Hooks extends object> {\n /** Unique plugin name. */\n readonly name: string;\n\n /** Execution order inside a chain; higher runs first. Defaults to `0`. */\n readonly priority?: number;\n\n /** Plugin names that must be registered first. */\n readonly dependsOn?: readonly string[];\n\n /**\n * Runs once per server, at install time.\n *\n * Use it to register parameter sources, add messages, seed state or capture\n * options in a closure. Return the lifecycle hooks.\n */\n readonly setup?: (options: O, api: PluginSetupApi) => Hooks | void;\n}\n\n/** The lifecycle hooks a plugin may return, excluding registration plumbing. */\nexport type PluginHooks = Omit<SnailPluginObject, \"name\" | \"priority\" | \"dependsOn\" | \"install\" | \"uninstall\">;\n\n/**\n * Create a third-party plugin.\n *\n * This is the supported entry point for plugin authors. It validates the name,\n * wires `install`/`uninstall`, exposes a scoped {@link PluginSetupApi} and keeps\n * the hook object fully typed.\n *\n * @example\n * ```ts\n * interface TraceOptions { header?: string }\n *\n * export const Trace = createPlugin<TraceOptions, { onSend?: (url: string) => void }>({\n * name: \"trace\",\n * priority: 20,\n * setup(options, api) {\n * const header = options?.header ?? \"x-trace-id\";\n * api.addMessages({ \"trace.missing\": \"trace header %s is missing\" });\n * return {\n * beforeRequest(ctx) {\n * ctx.request.headers.set(header, crypto.randomUUID());\n * }\n * };\n * }\n * });\n *\n * Service.use(Trace({ header: \"x-trace-id\" }));\n * ```\n */\nexport function createPlugin<O = void, Hooks extends object = PluginHooks>(\n definition: PluginDefinition<O, Hooks>\n): SnailPlugin<O> {\n if (!definition || typeof definition.name !== \"string\" || definition.name.length === 0) {\n throw new TypeError(\"[snail] createPlugin() requires a non-empty `name`\");\n }\n\n const { name, priority = 0, dependsOn, setup } = definition;\n\n return (options?: O): SnailPluginObject<O> => {\n const disposers: Array<() => void | Promise<void>> = [];\n\n const plugin: SnailPluginObject<O> = {\n name,\n priority,\n dependsOn,\n options,\n install(installCtx: SnailPluginInstallContext) {\n if (!setup) return;\n\n const hooks = setup(options as O, {\n serverName: installCtx.serverName,\n serverOptions: installCtx.serverOptions,\n installedPlugins: installCtx.pluginNames,\n defineParamSource: registerParamResolver,\n addMessages: registerMessages,\n onDispose: (dispose) => {\n disposers.push(dispose);\n }\n });\n\n // Hooks are merged onto the object rather than revealed through getters.\n // `install` runs synchronously during `use()`, so anything reading the\n // hook list afterwards — the manager's `hooks()` and `hasHook()` — sees\n // exactly the same object either way, and this shape is far harder to\n // get subtly wrong.\n if (hooks) Object.assign(plugin, hooks);\n },\n async uninstall() {\n while (disposers.length > 0) {\n await disposers.pop()!();\n }\n }\n };\n\n return plugin;\n };\n}\n\n/** One hook bound to the plugin that owns it. */\nexport interface BoundHook {\n readonly pluginName: string;\n readonly hook: (...args: any[]) => any;\n}\n\n/**\n * Compose chain hooks into a single Koa-style function.\n *\n * The returned function runs `entries[0]` first (they are pre-sorted by the\n * manager), handing each hook a `next` that advances the chain. When the chain\n * is exhausted, `downstream` runs — that is where the manager splices in the\n * actual HTTP request between `beforeRequest` and `afterResponse`.\n *\n * Calling `next()` twice from one hook throws {@link SnailHookError}; without\n * that guard the second call silently re-runs the rest of the chain.\n */\nexport function composeChain(\n hookName: string,\n entries: readonly BoundHook[]\n): (ctx: unknown, downstream?: () => Promise<void> | void) => Promise<void> {\n return async function runChain(ctx, downstream) {\n let lastIndex = -1;\n\n const dispatch = async (index: number): Promise<void> => {\n if (index <= lastIndex) {\n const offender = entries[Math.max(lastIndex, 0)]?.pluginName ?? \"unknown\";\n throw new SnailHookError(\n hookName,\n t(\"error.hook.next.multiple\", offender, hookName)\n );\n }\n lastIndex = index;\n\n const entry = entries[index];\n if (!entry) {\n await downstream?.();\n return;\n }\n\n let advanced = false;\n const next: SnailNext = async () => {\n if (advanced) {\n throw new SnailHookError(\n hookName,\n t(\"error.hook.next.multiple\", entry.pluginName, hookName)\n );\n }\n advanced = true;\n await dispatch(index + 1);\n };\n\n await entry.hook(ctx, next);\n };\n\n await dispatch(0);\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { useSyncExternalStore } from "react";
2
+ //#region src/adapter/react.ts
3
+ function box(state) {
4
+ return state;
5
+ }
6
+ /**
7
+ * React state adapter.
8
+ *
9
+ * `create` returns a subscribable box; `useBind` is what a component calls during
10
+ * render to subscribe and re-render. Strategies therefore work in both a
11
+ * component (via `useBind`) and an event handler (via `read`).
12
+ *
13
+ * This module is the only place in the library that imports `react`. It is
14
+ * reachable solely from `@snail-js/api/strategies/react` and the `ReactAdapter`
15
+ * plugin.
16
+ */
17
+ var reactStateAdapter = {
18
+ name: "react",
19
+ create(initial) {
20
+ return {
21
+ value: initial,
22
+ version: 0,
23
+ listeners: /* @__PURE__ */ new Set()
24
+ };
25
+ },
26
+ read(state) {
27
+ return state.value;
28
+ },
29
+ write(state, value) {
30
+ const target = box(state);
31
+ target.value = value;
32
+ target.version += 1;
33
+ for (const listener of [...target.listeners]) listener();
34
+ },
35
+ subscribe(state, listener) {
36
+ const target = box(state);
37
+ const wrapped = () => listener(target.value);
38
+ target.listeners.add(wrapped);
39
+ return () => {
40
+ target.listeners.delete(wrapped);
41
+ };
42
+ },
43
+ /**
44
+ * Read a state during render, subscribing the current component.
45
+ *
46
+ * The snapshot is the version counter, not the value: React compares snapshots
47
+ * with `Object.is`, so returning `{ id: 1 }` again would look unchanged and the
48
+ * component would not re-render.
49
+ */
50
+ useBind(state) {
51
+ const target = box(state);
52
+ useSyncExternalStore((listener) => reactStateAdapter.subscribe(state, listener), () => target.version, () => target.version);
53
+ return target.value;
54
+ },
55
+ dispose(state) {
56
+ box(state).listeners.clear();
57
+ }
58
+ };
59
+ //#endregion
60
+ export { reactStateAdapter as t };
61
+
62
+ //# sourceMappingURL=react-C1em-gxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-C1em-gxy.js","names":[],"sources":["../../src/adapter/react.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\nimport type { SnailStateAdapter, SnailStateRef } from \"../typings/adapter\";\n\n/**\n * A React state box.\n *\n * Wider than {@link SnailStateRef} on purpose: React needs a subscription and a\n * snapshot that changes identity to know a re-render is due. A bare `{ value }`\n * box gives it neither, which is the classic `useSyncExternalStore` pitfall — if\n * the snapshot is the value itself, two renders with an equal primitive look\n * unchanged and React bails out.\n */\ninterface ReactStateBox<T> extends SnailStateRef<T> {\n /** Monotonic counter used as the `useSyncExternalStore` snapshot. */\n version: number;\n listeners: Set<() => void>;\n}\n\nfunction box<T>(state: SnailStateRef<T>): ReactStateBox<T> {\n return state as ReactStateBox<T>;\n}\n\n/**\n * React state adapter.\n *\n * `create` returns a subscribable box; `useBind` is what a component calls during\n * render to subscribe and re-render. Strategies therefore work in both a\n * component (via `useBind`) and an event handler (via `read`).\n *\n * This module is the only place in the library that imports `react`. It is\n * reachable solely from `@snail-js/api/strategies/react` and the `ReactAdapter`\n * plugin.\n */\nexport const reactStateAdapter: SnailStateAdapter = {\n name: \"react\",\n\n create<T>(initial: T): SnailStateRef<T> {\n const state: ReactStateBox<T> = {\n value: initial,\n version: 0,\n listeners: new Set()\n };\n return state;\n },\n\n read<T>(state: SnailStateRef<T>): T {\n return state.value;\n },\n\n write<T>(state: SnailStateRef<T>, value: T): void {\n const target = box(state);\n target.value = value;\n target.version += 1;\n for (const listener of [...target.listeners]) listener();\n },\n\n subscribe<T>(state: SnailStateRef<T>, listener: (value: T) => void): () => void {\n const target = box(state);\n // The box stores a 0-argument listener internally; the public signature\n // matches `SnailStateAdapter` and receives the new value.\n const wrapped = (): void => listener(target.value);\n target.listeners.add(wrapped);\n return () => {\n target.listeners.delete(wrapped);\n };\n },\n\n /**\n * Read a state during render, subscribing the current component.\n *\n * The snapshot is the version counter, not the value: React compares snapshots\n * with `Object.is`, so returning `{ id: 1 }` again would look unchanged and the\n * component would not re-render.\n */\n useBind<T>(state: SnailStateRef<T>): T {\n const target = box(state);\n useSyncExternalStore(\n (listener) => reactStateAdapter.subscribe!(state, listener),\n () => target.version,\n () => target.version\n );\n return target.value;\n },\n\n dispose<T>(state: SnailStateRef<T>): void {\n box(state).listeners.clear();\n }\n};\n"],"mappings":";;AAkBA,SAAS,IAAO,OAA2C;CACzD,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,oBAAuC;CAClD,MAAM;CAEN,OAAU,SAA8B;EAMtC,OAAO;GAJL,OAAO;GACP,SAAS;GACT,2BAAW,IAAI,IAAI;EAEd;CACT;CAEA,KAAQ,OAA4B;EAClC,OAAO,MAAM;CACf;CAEA,MAAS,OAAyB,OAAgB;EAChD,MAAM,SAAS,IAAI,KAAK;EACxB,OAAO,QAAQ;EACf,OAAO,WAAW;EAClB,KAAK,MAAM,YAAY,CAAC,GAAG,OAAO,SAAS,GAAG,SAAS;CACzD;CAEA,UAAa,OAAyB,UAA0C;EAC9E,MAAM,SAAS,IAAI,KAAK;EAGxB,MAAM,gBAAsB,SAAS,OAAO,KAAK;EACjD,OAAO,UAAU,IAAI,OAAO;EAC5B,aAAa;GACX,OAAO,UAAU,OAAO,OAAO;EACjC;CACF;;;;;;;;CASA,QAAW,OAA4B;EACrC,MAAM,SAAS,IAAI,KAAK;EACxB,sBACG,aAAa,kBAAkB,UAAW,OAAO,QAAQ,SACpD,OAAO,eACP,OAAO,OACf;EACA,OAAO,OAAO;CAChB;CAEA,QAAW,OAA+B;EACxC,IAAI,KAAK,CAAC,CAAC,UAAU,MAAM;CAC7B;AACF"}
@@ -0,0 +1,40 @@
1
+ import { B as SnailError } from "./plugin-D3RDffDG.js";
2
+ //#region src/error/request.ts
3
+ /** Thrown when a request never reached the server (DNS, offline, CORS, …). */
4
+ var SnailRequestError = class extends SnailError {
5
+ constructor(message, options = {}) {
6
+ super(message, {
7
+ code: "SNAIL_REQUEST_ERROR",
8
+ cause: options.cause
9
+ });
10
+ }
11
+ };
12
+ /** Thrown when a request exceeded the configured timeout. */
13
+ var SnailTimeoutError = class extends SnailError {
14
+ constructor(message, options = {}) {
15
+ super(message, {
16
+ code: "SNAIL_TIMEOUT_ERROR",
17
+ cause: options.cause
18
+ });
19
+ this.timeout = options.timeout;
20
+ }
21
+ };
22
+ /**
23
+ * Thrown when a request is cancelled — by `method.abort()`, by a strategy
24
+ * discarding a stale request, or by an `AbortSignal` the caller supplied.
25
+ *
26
+ * Cancellation is *expected* control flow, not a failure, so strategies
27
+ * deliberately swallow this error kind.
28
+ */
29
+ var SnailCancelledError = class extends SnailError {
30
+ constructor(message = "request cancelled", options = {}) {
31
+ super(message, {
32
+ code: "SNAIL_CANCELLED",
33
+ cause: options.cause
34
+ });
35
+ }
36
+ };
37
+ //#endregion
38
+ export { SnailRequestError as n, SnailTimeoutError as r, SnailCancelledError as t };
39
+
40
+ //# sourceMappingURL=request-qeRhihEF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-qeRhihEF.js","names":[],"sources":["../../src/error/request.ts"],"sourcesContent":["import { SnailError } from \"./base\";\n\n/** Thrown when a request never reached the server (DNS, offline, CORS, …). */\nexport class SnailRequestError extends SnailError {\n constructor(message: string, options: { cause?: unknown } = {}) {\n super(message, { code: \"SNAIL_REQUEST_ERROR\", cause: options.cause });\n }\n}\n\n/** Thrown when a request exceeded the configured timeout. */\nexport class SnailTimeoutError extends SnailError {\n /** Configured timeout, in milliseconds. */\n readonly timeout: number | undefined;\n\n constructor(message: string, options: { timeout?: number; cause?: unknown } = {}) {\n super(message, { code: \"SNAIL_TIMEOUT_ERROR\", cause: options.cause });\n this.timeout = options.timeout;\n }\n}\n\n/**\n * Thrown when a request is cancelled — by `method.abort()`, by a strategy\n * discarding a stale request, or by an `AbortSignal` the caller supplied.\n *\n * Cancellation is *expected* control flow, not a failure, so strategies\n * deliberately swallow this error kind.\n */\nexport class SnailCancelledError extends SnailError {\n constructor(message = \"request cancelled\", options: { cause?: unknown } = {}) {\n super(message, { code: \"SNAIL_CANCELLED\", cause: options.cause });\n }\n}\n"],"mappings":";;;AAGA,IAAa,oBAAb,cAAuC,WAAW;CAChD,YAAY,SAAiB,UAA+B,CAAC,GAAG;EAC9D,MAAM,SAAS;GAAE,MAAM;GAAuB,OAAO,QAAQ;EAAM,CAAC;CACtE;AACF;;AAGA,IAAa,oBAAb,cAAuC,WAAW;CAIhD,YAAY,SAAiB,UAAiD,CAAC,GAAG;EAChF,MAAM,SAAS;GAAE,MAAM;GAAuB,OAAO,QAAQ;EAAM,CAAC;EACpE,KAAK,UAAU,QAAQ;CACzB;AACF;;;;;;;;AASA,IAAa,sBAAb,cAAyC,WAAW;CAClD,YAAY,UAAU,qBAAqB,UAA+B,CAAC,GAAG;EAC5E,MAAM,SAAS;GAAE,MAAM;GAAmB,OAAO,QAAQ;EAAM,CAAC;CAClE;AACF"}
@@ -0,0 +1,127 @@
1
+ import { o as SnailResponseError } from "./plugin-D3RDffDG.js";
2
+ //#region src/default/options.ts
3
+ /** Default key names of the assumed backend envelope. */
4
+ var DEFAULT_RESPONSE_KEYS = {
5
+ code: "code",
6
+ message: "message",
7
+ data: "data"
8
+ };
9
+ /** Business codes accepted when no `validateCode` is configured. */
10
+ var DEFAULT_ACCEPTED_CODES = [0, 200];
11
+ /** Fully resolved defaults for `@Server(...)`. */
12
+ var DEFAULT_SERVER_OPTIONS = {
13
+ name: "SNAIL_SERVER",
14
+ baseURL: "/",
15
+ timeout: 1e4,
16
+ codeKey: DEFAULT_RESPONSE_KEYS.code,
17
+ messageKey: DEFAULT_RESPONSE_KEYS.message,
18
+ dataKey: DEFAULT_RESPONSE_KEYS.data,
19
+ logLevel: "silent",
20
+ coerceJSONString: true
21
+ };
22
+ /** Numeric ordering of log levels, so `logLevel` can be compared. */
23
+ var LOG_LEVEL_WEIGHT = {
24
+ silent: 0,
25
+ error: 1,
26
+ warn: 2,
27
+ info: 3,
28
+ debug: 4
29
+ };
30
+ /** Default options for `@Api(...)`. */
31
+ var DEFAULT_API_OPTIONS = {
32
+ url: "",
33
+ name: ""
34
+ };
35
+ //#endregion
36
+ //#region src/core/response.ts
37
+ /**
38
+ * Response normalisation helpers.
39
+ *
40
+ * Responsibilities, in the order they run per request:
41
+ * 1. optionally repair a JSON body the server sent with the wrong content-type
42
+ * 2. decide whether the response *is* an envelope or a raw payload
43
+ * 3. validate the business status code
44
+ * 4. assemble the {@link SnailResult} handed to the caller
45
+ */
46
+ /**
47
+ * Parse a JSON string body.
48
+ *
49
+ * Some gateways answer `Content-Type: text/plain` (or omit the header) while
50
+ * sending a JSON envelope. Without this repair the caller would receive a
51
+ * string where the types promise an object — a silent, very confusing bug.
52
+ *
53
+ * Returns the original response when parsing is disabled, when the caller asked
54
+ * for `responseType: "text"` explicitly (they want the raw string), or when the
55
+ * body does not look like JSON.
56
+ */
57
+ function coerceJSONStringBody(response, enabled) {
58
+ if (!enabled) return response;
59
+ if (response.config?.responseType === "text") return response;
60
+ const body = response.data;
61
+ if (typeof body !== "string") return response;
62
+ const trimmed = body.trim();
63
+ if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) return response;
64
+ try {
65
+ return {
66
+ ...response,
67
+ data: JSON.parse(trimmed)
68
+ };
69
+ } catch {
70
+ return response;
71
+ }
72
+ }
73
+ /** `true` when the response body looks like an envelope carrying `dataKey`. */
74
+ function looksLikeEnvelope(body, dataKey) {
75
+ return body !== null && typeof body === "object" && !Array.isArray(body) && dataKey in body;
76
+ }
77
+ /** Read one key off an unknown body, or `undefined`. */
78
+ function readKey(body, key) {
79
+ if (body === null || typeof body !== "object") return void 0;
80
+ return body[key];
81
+ }
82
+ /** Extract the payload out of an envelope, tolerating raw pass-through bodies. */
83
+ function unwrapEnvelope(body, dataKey) {
84
+ if (looksLikeEnvelope(body, dataKey)) return body[dataKey];
85
+ return body;
86
+ }
87
+ /**
88
+ * The default business-code rule: accept `0` and `200`.
89
+ *
90
+ * Chosen because `0` is the overwhelmingly common "no error" code in Chinese
91
+ * backends and `200` mirrors HTTP for teams that reuse it. Applications with a
92
+ * different convention pass `validateCode` to `@Server(...)`.
93
+ */
94
+ function createDefaultCodeValidator(accepted = DEFAULT_ACCEPTED_CODES) {
95
+ const acceptedSet = new Set(accepted.map(String));
96
+ return (code) => code === void 0 || code === null ? true : acceptedSet.has(String(code));
97
+ }
98
+ /**
99
+ * Assert the business status code, throwing a {@link SnailResponseError} when
100
+ * the application's rule rejects it.
101
+ */
102
+ function assertBusinessCode(options) {
103
+ const { body, code, dataKey, validate, fullName, message } = options;
104
+ if (code === void 0 || code === null) return;
105
+ if ((validate ?? createDefaultCodeValidator())(code, body)) return;
106
+ throw new SnailResponseError(message, {
107
+ businessCode: code,
108
+ payload: body
109
+ });
110
+ }
111
+ /** Assemble the value `send()` resolves to. */
112
+ function buildResult(options) {
113
+ const { response, envelope, codeKey, messageKey, dataKey, fromCache, config } = options;
114
+ return {
115
+ response,
116
+ envelope,
117
+ data: unwrapEnvelope(envelope, dataKey),
118
+ code: readKey(envelope, codeKey),
119
+ message: readKey(envelope, messageKey),
120
+ fromCache,
121
+ config
122
+ };
123
+ }
124
+ //#endregion
125
+ export { readKey as a, DEFAULT_API_OPTIONS as c, LOG_LEVEL_WEIGHT as d, looksLikeEnvelope as i, DEFAULT_RESPONSE_KEYS as l, buildResult as n, unwrapEnvelope as o, coerceJSONStringBody as r, DEFAULT_ACCEPTED_CODES as s, assertBusinessCode as t, DEFAULT_SERVER_OPTIONS as u };
126
+
127
+ //# sourceMappingURL=response-D-ubkAFl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-D-ubkAFl.js","names":[],"sources":["../../src/default/options.ts","../../src/core/response.ts"],"sourcesContent":["import type { SnailLogLevel, SnailServerOptions } from \"../typings/server\";\n\n/** Default key names of the assumed backend envelope. */\nexport const DEFAULT_RESPONSE_KEYS = {\n code: \"code\",\n message: \"message\",\n data: \"data\"\n} as const;\n\n/** Business codes accepted when no `validateCode` is configured. */\nexport const DEFAULT_ACCEPTED_CODES: readonly number[] = [0, 200];\n\n/** Fully resolved defaults for `@Server(...)`. */\nexport const DEFAULT_SERVER_OPTIONS: Required<\n Pick<\n SnailServerOptions,\n | \"name\"\n | \"baseURL\"\n | \"timeout\"\n | \"codeKey\"\n | \"messageKey\"\n | \"dataKey\"\n | \"logLevel\"\n | \"coerceJSONString\"\n >\n> = {\n name: \"SNAIL_SERVER\",\n baseURL: \"/\",\n timeout: 10000,\n codeKey: DEFAULT_RESPONSE_KEYS.code,\n messageKey: DEFAULT_RESPONSE_KEYS.message,\n dataKey: DEFAULT_RESPONSE_KEYS.data,\n logLevel: \"silent\",\n coerceJSONString: true\n} as const;\n\n/** Numeric ordering of log levels, so `logLevel` can be compared. */\nexport const LOG_LEVEL_WEIGHT: Record<SnailLogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n debug: 4\n};\n\n/** Default options for `@Api(...)`. */\nexport const DEFAULT_API_OPTIONS = {\n url: \"\",\n name: \"\"\n} as const;\n","import type { AxiosResponse, InternalAxiosRequestConfig } from \"axios\";\nimport { DEFAULT_ACCEPTED_CODES } from \"../default/options\";\nimport { SnailResponseError } from \"../error/response\";\nimport type {\n SnailCodeValidator,\n SnailResult\n} from \"../typings/response\";\n/**\n * Response normalisation helpers.\n *\n * Responsibilities, in the order they run per request:\n * 1. optionally repair a JSON body the server sent with the wrong content-type\n * 2. decide whether the response *is* an envelope or a raw payload\n * 3. validate the business status code\n * 4. assemble the {@link SnailResult} handed to the caller\n */\n\n/**\n * Parse a JSON string body.\n *\n * Some gateways answer `Content-Type: text/plain` (or omit the header) while\n * sending a JSON envelope. Without this repair the caller would receive a\n * string where the types promise an object — a silent, very confusing bug.\n *\n * Returns the original response when parsing is disabled, when the caller asked\n * for `responseType: \"text\"` explicitly (they want the raw string), or when the\n * body does not look like JSON.\n */\nexport function coerceJSONStringBody<T>(\n response: AxiosResponse<T>,\n enabled: boolean\n): AxiosResponse<T> {\n if (!enabled) return response;\n if (response.config?.responseType === \"text\") return response;\n\n const body = response.data as unknown;\n if (typeof body !== \"string\") return response;\n\n const trimmed = body.trim();\n if (!(trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\"))) return response;\n\n try {\n return { ...response, data: JSON.parse(trimmed) as T };\n } catch {\n return response;\n }\n}\n\n/** `true` when the response body looks like an envelope carrying `dataKey`. */\nexport function looksLikeEnvelope(body: unknown, dataKey: string): boolean {\n return (\n body !== null &&\n typeof body === \"object\" &&\n !Array.isArray(body) &&\n dataKey in (body as Record<string, unknown>)\n );\n}\n\n/** Read one key off an unknown body, or `undefined`. */\nexport function readKey<T = unknown>(body: unknown, key: string): T | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n return (body as Record<string, unknown>)[key] as T | undefined;\n}\n\n/** Extract the payload out of an envelope, tolerating raw pass-through bodies. */\nexport function unwrapEnvelope<T>(body: unknown, dataKey: string): T {\n if (looksLikeEnvelope(body, dataKey)) {\n return (body as Record<string, unknown>)[dataKey] as T;\n }\n return body as T;\n}\n\n/**\n * The default business-code rule: accept `0` and `200`.\n *\n * Chosen because `0` is the overwhelmingly common \"no error\" code in Chinese\n * backends and `200` mirrors HTTP for teams that reuse it. Applications with a\n * different convention pass `validateCode` to `@Server(...)`.\n */\nexport function createDefaultCodeValidator(\n accepted: readonly (number | string)[] = DEFAULT_ACCEPTED_CODES\n): SnailCodeValidator {\n const acceptedSet = new Set<string>(accepted.map(String));\n return (code) => (code === undefined || code === null ? true : acceptedSet.has(String(code)));\n}\n\n/**\n * Assert the business status code, throwing a {@link SnailResponseError} when\n * the application's rule rejects it.\n */\nexport function assertBusinessCode(options: {\n body: unknown;\n code: number | string | undefined;\n dataKey: string;\n validate: SnailCodeValidator | undefined;\n fullName: string;\n message: string;\n}): void {\n const { body, code, dataKey, validate, fullName, message } = options;\n if (code === undefined || code === null) return;\n\n const rule = validate ?? createDefaultCodeValidator();\n if (rule(code, body)) return;\n\n throw new SnailResponseError(message, { businessCode: code, payload: body });\n}\n\n/** Assemble the value `send()` resolves to. */\nexport function buildResult<\n S,\n T,\n D extends string,\n C extends string,\n M extends string\n>(options: {\n response: AxiosResponse;\n envelope: unknown;\n codeKey: string;\n messageKey: string;\n dataKey: string;\n fromCache: boolean;\n config: InternalAxiosRequestConfig;\n}): SnailResult<S, T, D, C, M> {\n const { response, envelope, codeKey, messageKey, dataKey, fromCache, config } = options;\n\n return {\n response: response as SnailResult<S, T, D, C, M>[\"response\"],\n envelope: envelope as SnailResult<S, T, D, C, M>[\"envelope\"],\n data: unwrapEnvelope<T>(envelope, dataKey),\n code: readKey<number>(envelope, codeKey) as SnailResult<S, T, D, C, M>[\"code\"],\n message: readKey<string>(envelope, messageKey) as SnailResult<S, T, D, C, M>[\"message\"],\n fromCache,\n config\n };\n}\n"],"mappings":";;;AAGA,IAAa,wBAAwB;CACnC,MAAM;CACN,SAAS;CACT,MAAM;AACR;;AAGA,IAAa,yBAA4C,CAAC,GAAG,GAAG;;AAGhE,IAAa,yBAYT;CACF,MAAM;CACN,SAAS;CACT,SAAS;CACT,SAAS,sBAAsB;CAC/B,YAAY,sBAAsB;CAClC,SAAS,sBAAsB;CAC/B,UAAU;CACV,kBAAkB;AACpB;;AAGA,IAAa,mBAAkD;CAC7D,QAAQ;CACR,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACT;;AAGA,IAAa,sBAAsB;CACjC,KAAK;CACL,MAAM;AACR;;;;;;;;;;;;;;;;;;;;;;;ACrBA,SAAgB,qBACd,UACA,SACkB;CAClB,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,SAAS,QAAQ,iBAAiB,QAAQ,OAAO;CAErD,MAAM,OAAO,SAAS;CACtB,IAAI,OAAO,SAAS,UAAU,OAAO;CAErC,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,EAAE,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG,IAAI,OAAO;CAElE,IAAI;EACF,OAAO;GAAE,GAAG;GAAU,MAAM,KAAK,MAAM,OAAO;EAAO;CACvD,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,kBAAkB,MAAe,SAA0B;CACzE,OACE,SAAS,QACT,OAAO,SAAS,YAChB,CAAC,MAAM,QAAQ,IAAI,KACnB,WAAY;AAEhB;;AAGA,SAAgB,QAAqB,MAAe,KAA4B;CAC9E,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO,KAAA;CACtD,OAAQ,KAAiC;AAC3C;;AAGA,SAAgB,eAAkB,MAAe,SAAoB;CACnE,IAAI,kBAAkB,MAAM,OAAO,GACjC,OAAQ,KAAiC;CAE3C,OAAO;AACT;;;;;;;;AASA,SAAgB,2BACd,WAAyC,wBACrB;CACpB,MAAM,cAAc,IAAI,IAAY,SAAS,IAAI,MAAM,CAAC;CACxD,QAAQ,SAAU,SAAS,KAAA,KAAa,SAAS,OAAO,OAAO,YAAY,IAAI,OAAO,IAAI,CAAC;AAC7F;;;;;AAMA,SAAgB,mBAAmB,SAO1B;CACP,MAAM,EAAE,MAAM,MAAM,SAAS,UAAU,UAAU,YAAY;CAC7D,IAAI,SAAS,KAAA,KAAa,SAAS,MAAM;CAGzC,KADa,YAAY,2BAA2B,EAAA,CAC3C,MAAM,IAAI,GAAG;CAEtB,MAAM,IAAI,mBAAmB,SAAS;EAAE,cAAc;EAAM,SAAS;CAAK,CAAC;AAC7E;;AAGA,SAAgB,YAMd,SAQ6B;CAC7B,MAAM,EAAE,UAAU,UAAU,SAAS,YAAY,SAAS,WAAW,WAAW;CAEhF,OAAO;EACK;EACA;EACV,MAAM,eAAkB,UAAU,OAAO;EACzC,MAAM,QAAgB,UAAU,OAAO;EACvC,SAAS,QAAgB,UAAU,UAAU;EAC7C;EACA;CACF;AACF"}