@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.
- package/LICENSE +21 -0
- package/README.md +125 -605
- package/dist/adapter/plain.d.ts +12 -0
- package/dist/adapter/plain.d.ts.map +1 -0
- package/dist/adapter/react.d.ts +14 -0
- package/dist/adapter/react.d.ts.map +1 -0
- package/dist/adapter/registry.d.ts +16 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/vue.d.ts +15 -0
- package/dist/adapter/vue.d.ts.map +1 -0
- package/dist/chunks/custom-UchOSg_s.js +466 -0
- package/dist/chunks/custom-UchOSg_s.js.map +1 -0
- package/dist/chunks/download-lBC7oOTT.js +160 -0
- package/dist/chunks/download-lBC7oOTT.js.map +1 -0
- package/dist/chunks/object-Ct0w-h4i.js +74 -0
- package/dist/chunks/object-Ct0w-h4i.js.map +1 -0
- package/dist/chunks/plugin-D3RDffDG.js +742 -0
- package/dist/chunks/plugin-D3RDffDG.js.map +1 -0
- package/dist/chunks/react-C1em-gxy.js +62 -0
- package/dist/chunks/react-C1em-gxy.js.map +1 -0
- package/dist/chunks/request-qeRhihEF.js +40 -0
- package/dist/chunks/request-qeRhihEF.js.map +1 -0
- package/dist/chunks/response-D-ubkAFl.js +127 -0
- package/dist/chunks/response-D-ubkAFl.js.map +1 -0
- package/dist/chunks/use-download-FAjtdJ58.js +1816 -0
- package/dist/chunks/use-download-FAjtdJ58.js.map +1 -0
- package/dist/chunks/vue-Dd9EhkQ3.js +34 -0
- package/dist/chunks/vue-Dd9EhkQ3.js.map +1 -0
- package/dist/core/args.d.ts +54 -0
- package/dist/core/args.d.ts.map +1 -0
- package/dist/core/connection.d.ts +15 -0
- package/dist/core/connection.d.ts.map +1 -0
- package/dist/core/context.d.ts +124 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/http-stream.d.ts +28 -0
- package/dist/core/http-stream.d.ts.map +1 -0
- package/dist/core/logger.d.ts +22 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/metadata.d.ts +92 -0
- package/dist/core/metadata.d.ts.map +1 -0
- package/dist/core/metadata.keys.d.ts +41 -0
- package/dist/core/metadata.keys.d.ts.map +1 -0
- package/dist/core/method.d.ts +177 -0
- package/dist/core/method.d.ts.map +1 -0
- package/dist/core/plugin-manager.d.ts +106 -0
- package/dist/core/plugin-manager.d.ts.map +1 -0
- package/dist/core/plugin.d.ts +113 -0
- package/dist/core/plugin.d.ts.map +1 -0
- package/dist/core/reconnect.d.ts +22 -0
- package/dist/core/reconnect.d.ts.map +1 -0
- package/dist/core/resolve-stream.d.ts +45 -0
- package/dist/core/resolve-stream.d.ts.map +1 -0
- package/dist/core/resolve.d.ts +50 -0
- package/dist/core/resolve.d.ts.map +1 -0
- package/dist/core/response.d.ts +60 -0
- package/dist/core/response.d.ts.map +1 -0
- package/dist/core/server.d.ts +132 -0
- package/dist/core/server.d.ts.map +1 -0
- package/dist/core/sse.d.ts +27 -0
- package/dist/core/sse.d.ts.map +1 -0
- package/dist/core/state-bag.d.ts +36 -0
- package/dist/core/state-bag.d.ts.map +1 -0
- package/dist/core/websocket.d.ts +29 -0
- package/dist/core/websocket.d.ts.map +1 -0
- package/dist/decorators/api.d.ts +24 -4
- package/dist/decorators/api.d.ts.map +1 -0
- package/dist/decorators/args.d.ts +77 -14
- package/dist/decorators/args.d.ts.map +1 -0
- package/dist/decorators/custom.d.ts +81 -0
- package/dist/decorators/custom.d.ts.map +1 -0
- package/dist/decorators/header.d.ts +22 -0
- package/dist/decorators/header.d.ts.map +1 -0
- package/dist/decorators/index.d.ts +17 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/methods.d.ts +33 -0
- package/dist/decorators/methods.d.ts.map +1 -0
- package/dist/decorators/progress.d.ts +24 -6
- package/dist/decorators/progress.d.ts.map +1 -0
- package/dist/decorators/server.d.ts +28 -7
- package/dist/decorators/server.d.ts.map +1 -0
- package/dist/decorators/stream.d.ts +113 -0
- package/dist/decorators/stream.d.ts.map +1 -0
- package/dist/default/options.d.ts +19 -0
- package/dist/default/options.d.ts.map +1 -0
- package/dist/error/base.d.ts +20 -0
- package/dist/error/base.d.ts.map +1 -0
- package/dist/error/decorator.d.ts +14 -0
- package/dist/error/decorator.d.ts.map +1 -0
- package/dist/error/hook.d.ts +14 -0
- package/dist/error/hook.d.ts.map +1 -0
- package/dist/error/index.d.ts +8 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/options.d.ts +12 -0
- package/dist/error/options.d.ts.map +1 -0
- package/dist/error/plugin.d.ts +11 -0
- package/dist/error/plugin.d.ts.map +1 -0
- package/dist/error/request.d.ts +29 -0
- package/dist/error/request.d.ts.map +1 -0
- package/dist/error/response.d.ts +36 -0
- package/dist/error/response.d.ts.map +1 -0
- package/dist/index.d.ts +56 -11
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2034 -0
- package/dist/index.js.map +1 -0
- package/dist/locale/en.d.ts +5 -0
- package/dist/locale/en.d.ts.map +1 -0
- package/dist/locale/index.d.ts +57 -0
- package/dist/locale/index.d.ts.map +1 -0
- package/dist/locale/types.d.ts +11 -0
- package/dist/locale/types.d.ts.map +1 -0
- package/dist/locale/zh.d.ts +5 -0
- package/dist/locale/zh.d.ts.map +1 -0
- package/dist/plugins/cache/adapters/indexeddb.d.ts +52 -0
- package/dist/plugins/cache/adapters/indexeddb.d.ts.map +1 -0
- package/dist/plugins/cache/adapters/memory.d.ts +60 -0
- package/dist/plugins/cache/adapters/memory.d.ts.map +1 -0
- package/dist/plugins/cache/adapters/web-storage.d.ts +43 -0
- package/dist/plugins/cache/adapters/web-storage.d.ts.map +1 -0
- package/dist/plugins/cache/decorators.d.ts +62 -0
- package/dist/plugins/cache/decorators.d.ts.map +1 -0
- package/dist/plugins/cache/index.d.ts +41 -0
- package/dist/plugins/cache/index.d.ts.map +1 -0
- package/dist/plugins/cache/key.d.ts +48 -0
- package/dist/plugins/cache/key.d.ts.map +1 -0
- package/dist/plugins/cache/manager.d.ts +124 -0
- package/dist/plugins/cache/manager.d.ts.map +1 -0
- package/dist/plugins/cache/plugin.d.ts +64 -0
- package/dist/plugins/cache/plugin.d.ts.map +1 -0
- package/dist/plugins/cache/type.d.ts +116 -0
- package/dist/plugins/cache/type.d.ts.map +1 -0
- package/dist/plugins/index.d.ts +75 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +2265 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/interceptor/decorators.d.ts +44 -0
- package/dist/plugins/interceptor/decorators.d.ts.map +1 -0
- package/dist/plugins/interceptor/index.d.ts +31 -0
- package/dist/plugins/interceptor/index.d.ts.map +1 -0
- package/dist/plugins/interceptor/manager.d.ts +27 -0
- package/dist/plugins/interceptor/manager.d.ts.map +1 -0
- package/dist/plugins/interceptor/plugin.d.ts +65 -0
- package/dist/plugins/interceptor/plugin.d.ts.map +1 -0
- package/dist/plugins/interceptor/type.d.ts +45 -0
- package/dist/plugins/interceptor/type.d.ts.map +1 -0
- package/dist/plugins/pool/index.d.ts +26 -0
- package/dist/plugins/pool/index.d.ts.map +1 -0
- package/dist/plugins/pool/plugin.d.ts +83 -0
- package/dist/plugins/pool/plugin.d.ts.map +1 -0
- package/dist/plugins/pool/scheduler.d.ts +153 -0
- package/dist/plugins/pool/scheduler.d.ts.map +1 -0
- package/dist/plugins/pool/type.d.ts +38 -0
- package/dist/plugins/pool/type.d.ts.map +1 -0
- package/dist/plugins/react/index.d.ts +20 -0
- package/dist/plugins/react/index.d.ts.map +1 -0
- package/dist/plugins/react/index.js +145 -0
- package/dist/plugins/react/index.js.map +1 -0
- package/dist/plugins/react/plugin.d.ts +33 -0
- package/dist/plugins/react/plugin.d.ts.map +1 -0
- package/dist/plugins/react/type.d.ts +32 -0
- package/dist/plugins/react/type.d.ts.map +1 -0
- package/dist/plugins/transform/decorators.d.ts +54 -0
- package/dist/plugins/transform/decorators.d.ts.map +1 -0
- package/dist/plugins/transform/hydrate.d.ts +47 -0
- package/dist/plugins/transform/hydrate.d.ts.map +1 -0
- package/dist/plugins/transform/index.d.ts +41 -0
- package/dist/plugins/transform/index.d.ts.map +1 -0
- package/dist/plugins/transform/plugin.d.ts +20 -0
- package/dist/plugins/transform/plugin.d.ts.map +1 -0
- package/dist/plugins/transform/type.d.ts +67 -0
- package/dist/plugins/transform/type.d.ts.map +1 -0
- package/dist/plugins/validate/decorators.d.ts +24 -0
- package/dist/plugins/validate/decorators.d.ts.map +1 -0
- package/dist/plugins/validate/index.d.ts +40 -0
- package/dist/plugins/validate/index.d.ts.map +1 -0
- package/dist/plugins/validate/plugin.d.ts +13 -0
- package/dist/plugins/validate/plugin.d.ts.map +1 -0
- package/dist/plugins/validate/type.d.ts +54 -0
- package/dist/plugins/validate/type.d.ts.map +1 -0
- package/dist/plugins/version/decorators.d.ts +22 -0
- package/dist/plugins/version/decorators.d.ts.map +1 -0
- package/dist/plugins/version/index.d.ts +23 -0
- package/dist/plugins/version/index.d.ts.map +1 -0
- package/dist/plugins/version/plugin.d.ts +13 -0
- package/dist/plugins/version/plugin.d.ts.map +1 -0
- package/dist/plugins/version/type.d.ts +47 -0
- package/dist/plugins/version/type.d.ts.map +1 -0
- package/dist/plugins/vue/index.d.ts +21 -0
- package/dist/plugins/vue/index.d.ts.map +1 -0
- package/dist/plugins/vue/index.js +114 -0
- package/dist/plugins/vue/index.js.map +1 -0
- package/dist/plugins/vue/plugin.d.ts +15 -0
- package/dist/plugins/vue/plugin.d.ts.map +1 -0
- package/dist/plugins/vue/type.d.ts +34 -0
- package/dist/plugins/vue/type.d.ts.map +1 -0
- package/dist/strategies/index.d.ts +2 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +29 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/plain.d.ts +2 -0
- package/dist/strategies/plain.d.ts.map +1 -0
- package/dist/strategies/plain.js +23 -0
- package/dist/strategies/plain.js.map +1 -0
- package/dist/strategies/react.d.ts +2 -0
- package/dist/strategies/react.d.ts.map +1 -0
- package/dist/strategies/react.js +30 -0
- package/dist/strategies/react.js.map +1 -0
- package/dist/strategies/shared/adapter.d.ts +23 -0
- package/dist/strategies/shared/adapter.d.ts.map +1 -0
- package/dist/strategies/shared/dom.d.ts +53 -0
- package/dist/strategies/shared/dom.d.ts.map +1 -0
- package/dist/strategies/shared/error.d.ts +30 -0
- package/dist/strategies/shared/error.d.ts.map +1 -0
- package/dist/strategies/shared/method.d.ts +51 -0
- package/dist/strategies/shared/method.d.ts.map +1 -0
- package/dist/strategies/shared/public.d.ts +33 -0
- package/dist/strategies/shared/public.d.ts.map +1 -0
- package/dist/strategies/shared/queue.d.ts +30 -0
- package/dist/strategies/shared/queue.d.ts.map +1 -0
- package/dist/strategies/shared/retry.d.ts +51 -0
- package/dist/strategies/shared/retry.d.ts.map +1 -0
- package/dist/strategies/shared/state.d.ts +117 -0
- package/dist/strategies/shared/state.d.ts.map +1 -0
- package/dist/strategies/shared/timing.d.ts +53 -0
- package/dist/strategies/shared/timing.d.ts.map +1 -0
- package/dist/strategies/shared/watcher.d.ts +37 -0
- package/dist/strategies/shared/watcher.d.ts.map +1 -0
- package/dist/strategies/use-auto-request.d.ts +77 -0
- package/dist/strategies/use-auto-request.d.ts.map +1 -0
- package/dist/strategies/use-download.d.ts +103 -0
- package/dist/strategies/use-download.d.ts.map +1 -0
- package/dist/strategies/use-fetcher.d.ts +52 -0
- package/dist/strategies/use-fetcher.d.ts.map +1 -0
- package/dist/strategies/use-pagination.d.ts +101 -0
- package/dist/strategies/use-pagination.d.ts.map +1 -0
- package/dist/strategies/use-request.d.ts +50 -0
- package/dist/strategies/use-request.d.ts.map +1 -0
- package/dist/strategies/use-retriable-request.d.ts +46 -0
- package/dist/strategies/use-retriable-request.d.ts.map +1 -0
- package/dist/strategies/use-sse.d.ts +103 -0
- package/dist/strategies/use-sse.d.ts.map +1 -0
- package/dist/strategies/use-token-auth.d.ts +88 -0
- package/dist/strategies/use-token-auth.d.ts.map +1 -0
- package/dist/strategies/use-uploader.d.ts +88 -0
- package/dist/strategies/use-uploader.d.ts.map +1 -0
- package/dist/strategies/use-watcher.d.ts +69 -0
- package/dist/strategies/use-watcher.d.ts.map +1 -0
- package/dist/typings/adapter.d.ts +61 -0
- package/dist/typings/adapter.d.ts.map +1 -0
- package/dist/typings/api.d.ts +122 -0
- package/dist/typings/api.d.ts.map +1 -0
- package/dist/typings/args.d.ts +51 -0
- package/dist/typings/args.d.ts.map +1 -0
- package/dist/typings/index.d.ts +8 -12
- package/dist/typings/index.d.ts.map +1 -0
- package/dist/typings/meta.d.ts +44 -0
- package/dist/typings/meta.d.ts.map +1 -0
- package/dist/typings/plugin.d.ts +114 -0
- package/dist/typings/plugin.d.ts.map +1 -0
- package/dist/typings/response.d.ts +118 -0
- package/dist/typings/response.d.ts.map +1 -0
- package/dist/typings/server.d.ts +83 -0
- package/dist/typings/server.d.ts.map +1 -0
- package/dist/typings/stream.d.ts +150 -0
- package/dist/typings/stream.d.ts.map +1 -0
- package/dist/utils/download.d.ts +97 -0
- package/dist/utils/download.d.ts.map +1 -0
- package/dist/utils/emitter.d.ts +36 -0
- package/dist/utils/emitter.d.ts.map +1 -0
- package/dist/utils/index.d.ts +6 -1
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/is.d.ts +22 -0
- package/dist/utils/is.d.ts.map +1 -0
- package/dist/utils/object.d.ts +44 -0
- package/dist/utils/object.d.ts.map +1 -0
- package/dist/utils/url.d.ts +44 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/package.json +90 -27
- package/README_EN.md +0 -607
- package/dist/cache/index.d.ts +0 -9
- package/dist/cache/indexDBCache.d.ts +0 -13
- package/dist/cache/localstorageCache.d.ts +0 -10
- package/dist/cache/memoryCache.d.ts +0 -11
- package/dist/core/index.d.ts +0 -4
- package/dist/core/snailApi.d.ts +0 -22
- package/dist/core/snailMethod.d.ts +0 -49
- package/dist/core/snailServer.d.ts +0 -35
- package/dist/core/snailSse.d.ts +0 -20
- package/dist/decorators/cache.d.ts +0 -15
- package/dist/decorators/method.d.ts +0 -38
- package/dist/decorators/sse.d.ts +0 -30
- package/dist/decorators/strategy.d.ts +0 -10
- package/dist/decorators/versioning.d.ts +0 -16
- package/dist/eventEmmit/index.d.ts +0 -1
- package/dist/eventEmmit/snail.event.d.ts +0 -10
- package/dist/snail-api.js +0 -4868
- package/dist/snail-api.umd.cjs +0 -4872
- package/dist/strategies/jwtStrategy.d.ts +0 -4
- package/dist/typings/api.option.d.ts +0 -10
- package/dist/typings/apiProxy.d.ts +0 -8
- package/dist/typings/cache.management.option.d.ts +0 -25
- package/dist/typings/cache.type.d.ts +0 -23
- package/dist/typings/content.type.d.ts +0 -5
- package/dist/typings/request.body.d.ts +0 -3
- package/dist/typings/response.data.d.ts +0 -13
- package/dist/typings/snail.event.d.ts +0 -6
- package/dist/typings/snail.method.d.ts +0 -14
- package/dist/typings/snail.option.d.ts +0 -16
- package/dist/typings/sse.d.ts +0 -15
- package/dist/typings/strategy.d.ts +0 -5
- package/dist/typings/versioning.option.d.ts +0 -30
- package/dist/utils/function.d.ts +0 -31
- package/dist/versioning/index.d.ts +0 -1
- package/dist/versioning/versioning.d.ts +0 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/core/state-bag.ts","../src/core/context.ts","../src/core/connection.ts","../src/core/http-stream.ts","../src/core/method.ts","../src/core/plugin-manager.ts","../src/error/options.ts","../src/core/resolve.ts","../src/core/resolve-stream.ts","../src/core/reconnect.ts","../src/core/sse.ts","../src/core/websocket.ts","../src/core/server.ts","../src/decorators/api.ts","../src/decorators/server.ts","../src/decorators/methods.ts","../src/decorators/header.ts","../src/decorators/progress.ts","../src/decorators/stream.ts"],"sourcesContent":["/**\n * Typed key/value bag.\n *\n * Two independent bags ride along every request:\n *\n * - `ctx.state` — **plugin scratch space**. Timers, cache keys, in-flight\n * promises, tracing ids. Never exposed to the caller.\n * - `ctx.meta` — **caller-visible reactive values** (`data`, `loading`, `error`)\n * created by the framework adapter's `initMeta` hook.\n *\n * Keeping them separate matters: a plugin writing bookkeeping into `meta` would\n * leak internal state into the object the UI renders.\n */\nexport class StateBag {\n private readonly values = new Map<string, unknown>();\n\n /** Read a value, optionally falling back when the key is absent. */\n get<T = unknown>(key: string): T | undefined;\n get<T = unknown>(key: string, fallback: T): T;\n get<T = unknown>(key: string, fallback?: T): T | undefined {\n return (this.values.has(key) ? this.values.get(key) : fallback) as T | undefined;\n }\n\n /** Read a value, throwing when the key is absent. */\n require<T = unknown>(key: string): T {\n if (!this.values.has(key)) {\n throw new ReferenceError(`[snail] context state \"${key}\" has not been set`);\n }\n return this.values.get(key) as T;\n }\n\n /** Write a value. */\n set<T = unknown>(key: string, value: T): this {\n this.values.set(key, value);\n return this;\n }\n\n /** Write a value only when the key is currently absent. */\n setDefault<T = unknown>(key: string, value: T): T {\n if (!this.values.has(key)) this.values.set(key, value);\n return this.values.get(key) as T;\n }\n\n /** `true` when the key was ever set (even to `undefined`). */\n has(key: string): boolean {\n return this.values.has(key);\n }\n\n /** Remove a key. Returns whether it existed. */\n delete(key: string): boolean {\n return this.values.delete(key);\n }\n\n /** Drop everything. */\n clear(): void {\n this.values.clear();\n }\n\n /** Current keys. */\n keys(): string[] {\n return [...this.values.keys()];\n }\n\n /** Shallow snapshot, handy for logging and assertions. */\n snapshot(): Record<string, unknown> {\n return Object.fromEntries(this.values);\n }\n}\n","import type { AxiosResponse, InternalAxiosRequestConfig } from \"axios\";\nimport type { SnailLogger } from \"./logger\";\nimport type { SnailServer } from \"./server\";\nimport { StateBag } from \"./state-bag\";\nimport type { SnailApiOptions, SnailMethodType } from \"../typings/api\";\nimport type { SnailParamDescriptor } from \"../typings/args\";\nimport type { SnailResult } from \"../typings/response\";\nimport type { ResolvedServerOptions } from \"../typings/server\";\n\n/** Everything needed to construct a request context. */\nexport interface SnailContextInit {\n server: SnailServer<any, any, any, any>;\n serverOptions: ResolvedServerOptions;\n apiClass: new () => unknown;\n api: unknown;\n apiName: string;\n apiOptions: Required<SnailApiOptions>;\n methodName: string;\n methodType: SnailMethodType;\n route: string;\n request: InternalAxiosRequestConfig;\n descriptors: readonly SnailParamDescriptor[];\n logger: SnailLogger;\n}\n\n/**\n * Per-request context — the single object every plugin hook receives.\n *\n * One context exists per `send()` call, so plugins may store freely in\n * `ctx.state` without worrying about concurrent requests colliding. That is a\n * deliberate fix over the pre-rewrite design, where the \"event map\" and the\n * request config lived on the long-lived `SnailMethod` instance and two\n * overlapping `send()` calls shared mutable state.\n */\nexport class SnailContext {\n /** The server instance that owns this request. */\n readonly server: SnailServer<any, any, any, any>;\n\n /** Fully resolved server options. */\n readonly serverOptions: ResolvedServerOptions;\n\n /** The decorated api class (constructor). */\n readonly apiClass: new () => unknown;\n\n /** The instantiated api class. */\n readonly api: unknown;\n\n /** Resolved api name — `@Api({ name })` or the class name. */\n readonly apiName: string;\n\n /** Fully resolved api options. */\n readonly apiOptions: Required<SnailApiOptions>;\n\n /** Decorated method name, e.g. `\"getUser\"`. */\n readonly methodName: string;\n\n /** Request verb. */\n readonly methodType: SnailMethodType;\n\n /**\n * Url template for this method — api prefix joined with the method path,\n * **before** `:placeholder` substitution.\n */\n readonly route: string;\n\n /** `server.api.method`, used in logs and error messages. */\n readonly fullName: string;\n\n /** Level-gated logger configured from `@Server({ logLevel })`. */\n readonly logger: SnailLogger;\n\n /** Parameter descriptors captured by the argument decorators. */\n readonly descriptors: readonly SnailParamDescriptor[];\n\n /** Plugin scratch space. Not visible to the caller. */\n readonly state = new StateBag();\n\n /** Caller-visible reactive values, populated by `initMeta` hooks. */\n meta: Record<string, unknown> = {};\n\n /**\n * The live axios request config.\n *\n * Plugins and argument decorators mutate this object in place; replacing it\n * wholesale is also supported and is what `ctx.request = ...` means.\n */\n request: InternalAxiosRequestConfig;\n\n /** Values gathered from `@Params()` for `:placeholder` substitution. */\n pathParams: Record<string, unknown> = {};\n\n /** Set once a response exists — from the network **or** from a cache. */\n response: AxiosResponse | undefined;\n\n /** Set when the request failed. */\n error: unknown;\n\n /** Set once the envelope passed validation. */\n result: SnailResult<any, any, any, any, any> | undefined;\n\n /** Timestamp when `send()` started. */\n startedAt: number = Date.now();\n\n /** Timestamp when the request settled. */\n finishedAt: number | undefined;\n\n private interrupted = false;\n private cacheHit = false;\n\n constructor(init: SnailContextInit) {\n this.server = init.server;\n this.serverOptions = init.serverOptions;\n this.apiClass = init.apiClass;\n this.api = init.api;\n this.apiName = init.apiName;\n this.apiOptions = init.apiOptions;\n this.methodName = init.methodName;\n this.methodType = init.methodType;\n this.route = init.route;\n this.fullName = `${init.serverOptions.name}.${init.apiName}.${init.methodName}`;\n this.request = init.request;\n this.descriptors = init.descriptors;\n this.logger = init.logger;\n }\n\n /** Milliseconds elapsed since `send()` started. */\n get elapsed(): number {\n return (this.finishedAt ?? Date.now()) - this.startedAt;\n }\n\n /**\n * Stop the request.\n *\n * With a `response` argument the network call is skipped entirely and that\n * response is used instead — this is exactly how a cache hit works. Without\n * one, the request is abandoned and `send()` rejects with a cancellation error.\n */\n interrupt(response?: AxiosResponse): void {\n this.interrupted = true;\n if (response) this.response = response;\n }\n\n /** `true` when a plugin short-circuited the request. */\n get isInterrupted(): boolean {\n return this.interrupted;\n }\n\n /** Record that the current response came from a cache. */\n markCacheHit(): void {\n this.cacheHit = true;\n }\n\n /** `true` when the response was served from a cache. */\n get isCacheHit(): boolean {\n return this.cacheHit;\n }\n\n /** Replace the current response. */\n setResponse(response: AxiosResponse | undefined): void {\n this.response = response;\n }\n\n /** Read the current response. */\n getResponse(): AxiosResponse | undefined {\n return this.response;\n }\n\n /** Read the current response, throwing when there is none. */\n requireResponse(): AxiosResponse {\n if (!this.response) {\n throw new ReferenceError(`[snail] ${this.fullName} has no response at this point`);\n }\n return this.response;\n }\n\n /** Replace the request config. */\n setRequest(request: InternalAxiosRequestConfig): void {\n this.request = request;\n this.request.url = this.request.url ?? this.route;\n }\n\n /** Read the request config. */\n getRequest(): InternalAxiosRequestConfig {\n return this.request;\n }\n\n /** Replace the parsed result. */\n setResult(result: SnailResult<any, any, any, any, any>): void {\n this.result = result;\n }\n\n /**\n * Clear everything that belongs to one `send()` while keeping the context\n * identity, so `meta` — and therefore the caller's reactive handles — survive\n * a re-send.\n */\n reset(request: InternalAxiosRequestConfig): void {\n this.state.clear();\n this.pathParams = {};\n this.response = undefined;\n this.error = undefined;\n this.result = undefined;\n this.finishedAt = undefined;\n this.interrupted = false;\n this.cacheHit = false;\n this.startedAt = Date.now();\n this.request = request;\n }\n\n /** Shallow copy of the fields worth logging. */\n describe(): Record<string, unknown> {\n return {\n name: this.fullName,\n method: this.methodType,\n url: this.request.url,\n route: this.route,\n baseURL: this.request.baseURL,\n params: this.request.params,\n fromCache: this.cacheHit,\n elapsed: this.elapsed\n };\n }\n}\n","import type { SnailConnection } from \"../typings/stream\";\n\n/**\n * Attach `Symbol.dispose` / `Symbol.asyncDispose` to a connection.\n *\n * Defined as a helper rather than as two methods on each transport because the\n * symbols are *optional*: `esnext.disposable` is a recent addition, and an engine\n * that lacks them would otherwise end up with a property literally named\n * `\"undefined\"` if the keys were written inline.\n *\n * The result is that a connection can be released three equivalent ways —\n * `close()`, `using`, or `await using` — with `close()` staying the only one that\n * works everywhere.\n */\nexport function withDispose<T extends SnailConnection>(connection: T): T {\n const symbols = Symbol as unknown as { dispose?: symbol; asyncDispose?: symbol };\n const target = connection as unknown as Record<symbol, unknown>;\n\n if (typeof symbols.dispose === \"symbol\") {\n target[symbols.dispose] = (): void => {\n connection.close();\n };\n }\n\n if (typeof symbols.asyncDispose === \"symbol\") {\n target[symbols.asyncDispose] = async (): Promise<void> => {\n connection.close();\n };\n }\n\n return connection;\n}\n","import { SnailRequestError } from \"../error/request\";\nimport { t } from \"../locale\";\nimport type {\n SnailHttpStreamConnection,\n SnailHttpStreamOptions\n} from \"../typings/stream\";\nimport { deferred } from \"../utils/object\";\nimport { withDispose } from \"./connection\";\nimport type { SnailLogger } from \"./logger\";\n\n/** Everything the HTTP streaming transport needs. */\nexport interface HttpStreamInit {\n /** Fully qualified url. */\n url: string;\n /** Options from `@HttpStream(path, options)` with its request method. */\n options: SnailHttpStreamOptions & { method?: string };\n /** Request payload. */\n body: unknown;\n /** Name used in log lines. */\n name: string;\n /** Extra headers (server-level, auth, …). */\n headers?: Record<string, string>;\n /** Logger. */\n logger: SnailLogger;\n}\n\n/**\n * Streaming HTTP response.\n *\n * Uses `fetch` so the response body arrives as a `ReadableStream`, which works\n * in browsers, Node 18+ and workers alike. The connection object is itself\n * async-iterable, so callers write `for await (const chunk of stream)`.\n */\nexport function createHttpStream(init: HttpStreamInit): SnailHttpStreamConnection {\n const { url, options, body, name, logger } = init;\n const controller = new AbortController();\n const opened = deferred<void>();\n const closed = deferred<void>();\n\n // Attaching a handler marks `opened` as handled, so a failing stream does not\n // surface as an unhandled rejection when the caller only ever iterates it.\n void opened.promise.catch(() => undefined);\n\n const decodeText = options.decodeText ?? true;\n const lineDelimited = options.lineDelimited ?? false;\n\n let connected = false;\n let stopped = false;\n let iterator: AsyncIterator<string> | undefined;\n\n const start = async (): Promise<ReadableStreamDefaultReader<Uint8Array>> => {\n const method = (options.method ?? \"POST\").toUpperCase();\n const hasBody = body !== undefined && method !== \"GET\";\n\n const response = await fetch(url, {\n method,\n headers: {\n accept: \"text/event-stream, application/x-ndjson, text/plain\",\n ...(hasBody ? { \"content-type\": \"application/json\" } : {}),\n ...(options.headers ?? {}),\n ...(init.headers ?? {})\n },\n body: hasBody ? JSON.stringify(body) : undefined,\n signal: controller.signal\n });\n\n if (!response.ok) {\n throw new SnailRequestError(\n t(\n \"error.request.failed\",\n name,\n `stream responded ${response.status} ${response.statusText}`\n )\n );\n }\n if (!response.body) {\n throw new SnailRequestError(\n t(\"error.request.failed\", name, \"stream response has no readable body\")\n );\n }\n\n connected = true;\n opened.resolve();\n return response.body.getReader();\n };\n\n /**\n * Start the request immediately rather than on first iteration.\n *\n * `opened` and `closed` are only useful if the request is already in flight — a\n * lazily started stream leaves `await connection.opened` pending forever, which\n * is a trap. Creating the connection *is* the \"send\" action for a stream.\n */\n const readerPromise = start();\n\n // An unconsumed failure must not become an unhandled rejection either.\n void readerPromise.catch(() => undefined);\n\n const createIterator = (): AsyncIterator<string> => {\n const decoder = new TextDecoder(\"utf-8\");\n let pending = \"\";\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n\n const next = async (): Promise<IteratorResult<string>> => {\n try {\n reader ??= await readerPromise;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (lineDelimited) {\n const newlineIndex = pending.indexOf(\"\\n\");\n if (newlineIndex !== -1) {\n const line = pending.slice(0, newlineIndex);\n pending = pending.slice(newlineIndex + 1);\n if (line.trim().length === 0) continue;\n return { value: line, done: false };\n }\n } else if (pending.length > 0) {\n const chunk = pending;\n pending = \"\";\n return { value: chunk, done: false };\n }\n\n const { done, value } = await reader.read();\n if (done) {\n connected = false;\n stopped = true;\n closed.resolve();\n if (pending.length > 0) {\n const tail = pending;\n pending = \"\";\n return { value: tail, done: false };\n }\n return { value: undefined, done: true };\n }\n\n pending += decodeText ? decoder.decode(value, { stream: true }) : String(value);\n }\n } catch (error) {\n connected = false;\n stopped = true;\n opened.reject(error);\n closed.resolve();\n throw error;\n }\n };\n\n return {\n next,\n return: async () => {\n stopped = true;\n connected = false;\n controller.abort();\n closed.resolve();\n return { value: undefined, done: true };\n }\n };\n };\n\n const connection: SnailHttpStreamConnection = withDispose({\n get connected() {\n return connected;\n },\n opened: opened.promise,\n closed: closed.promise,\n close() {\n if (stopped) return;\n stopped = true;\n connected = false;\n controller.abort();\n logger.debug(t(\"info.sse.close\", name));\n closed.resolve();\n },\n [Symbol.asyncIterator]() {\n iterator ??= createIterator();\n return iterator;\n },\n async text() {\n let out = \"\";\n for await (const chunk of connection) out += chunk;\n return out;\n }\n });\n\n return connection;\n}\n","import { AxiosHeaders, isCancel } from \"axios\";\nimport type { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from \"axios\";\nimport { SnailCancelledError, SnailTimeoutError } from \"../error/request\";\nimport { SnailHttpError, SnailResponseError } from \"../error/response\";\nimport { t } from \"../locale\";\nimport type { SnailApiOptions, SnailMethodOptions, SnailMethodType } from \"../typings/api\";\nimport type { SnailParamDescriptor } from \"../typings/args\";\nimport type { SnailMeta } from \"../typings/meta\";\nimport type { SnailResult } from \"../typings/response\";\nimport type { ResolvedServerOptions } from \"../typings/server\";\nimport { Emitter } from \"../utils/emitter\";\nimport { applyParamDescriptors, finalizeRequestURL } from \"./args\";\nimport { SnailContext } from \"./context\";\nimport type { SnailLogger } from \"./logger\";\nimport type { PluginManager } from \"./plugin-manager\";\nimport { assertBusinessCode, buildResult, coerceJSONStringBody, readKey } from \"./response\";\nimport type { SnailServer } from \"./server\";\n\n/** Payload of the `codeError` event. */\nexport interface SnailCodeErrorEvent {\n /** Business status code the backend returned. */\n code: number | string | undefined;\n /** Full parsed envelope. */\n payload: unknown;\n /** The `SnailResponseError` that will be thrown. */\n error: unknown;\n}\n\n/** Events emitted by a {@link SnailMethod}. */\nexport interface SnailMethodEventMap<\n S,\n T,\n D extends string,\n C extends string,\n M extends string\n> {\n success: SnailResult<S, T, D, C, M>;\n error: unknown;\n codeError: SnailCodeErrorEvent;\n finish: undefined;\n cache: undefined;\n}\n\n/** Everything `SnailMethod` needs, assembled by the proxy in `SnailServer`. */\nexport interface SnailMethodInit {\n server: SnailServer<any, any, any, any>;\n pluginManager: PluginManager;\n axios: AxiosInstance;\n apiClass: new () => unknown;\n api: unknown;\n apiName: string;\n apiOptions: Required<SnailApiOptions>;\n serverOptions: ResolvedServerOptions;\n methodName: string;\n methodType: SnailMethodType;\n route: string;\n methodOptions: SnailMethodOptions & { url: string };\n descriptors: readonly SnailParamDescriptor[];\n headers: AxiosHeaders;\n logger: SnailLogger;\n /**\n * Builds a fresh axios config for this method.\n *\n * A factory rather than a value: every `send()` must start from a clean config\n * so mutations a plugin made during the previous send do not leak forward.\n */\n requestConfig: () => InternalAxiosRequestConfig;\n}\n\n/**\n * One pending request.\n *\n * Created by calling a proxied api method — `userApi.getUser(\"1\")` — and sent by\n * calling {@link SnailMethod.send}. Nothing hits the network until `send()`.\n *\n * ## Why the context is built once and reset\n *\n * Framework adapters create the caller's reactive handles in their `initMeta`\n * hook. Those handles must survive every re-send, so the context is constructed\n * once and {@link SnailContext.reset} clears only the per-request fields. Calling\n * `userApi.getUser()` twice would produce two independent sets of refs, which is\n * exactly the bug the old `request()`-returns-fresh-state design had.\n */\nexport class SnailMethod<\n S = unknown,\n T = unknown,\n D extends string = \"data\",\n C extends string = \"code\",\n M extends string = \"message\"\n> {\n /** `server.api.method`. */\n readonly name: string;\n\n /** Decorated method name. */\n readonly methodName: string;\n\n /** Request verb. */\n readonly methodType: SnailMethodType;\n\n /** Url template before `:placeholder` substitution. */\n readonly route: string;\n\n /** Arguments this instance was created with. `send(...args)` may override them. */\n readonly args: readonly unknown[];\n\n /**\n * Caller-visible reactive values created by framework adapters.\n *\n * A live view of `context.meta`, so the handles an adapter created in\n * `initMeta` stay stable across every re-send. Empty when no adapter plugin is\n * installed.\n *\n * `loading` and `error` are typed by the {@link SnailMeta} interface; the\n * envelope handles are named after the server's configured keys, so augment\n * `SnailMeta` to type them.\n */\n get meta(): SnailMeta & Record<string, unknown> {\n return this.context.meta as SnailMeta & Record<string, unknown>;\n }\n\n /** The live request context. */\n readonly context: SnailContext;\n\n private readonly init: SnailMethodInit;\n private readonly emitter: Emitter<\n SnailMethodEventMap<S, T, D, C, M> & Record<string, unknown>\n > = new Emitter();\n private controller: AbortController | undefined;\n private inFlight = false;\n\n constructor(init: SnailMethodInit, args: readonly unknown[] = []) {\n this.init = init;\n this.args = args;\n this.name = `${init.serverOptions.name}.${init.apiName}.${init.methodName}`;\n this.methodName = init.methodName;\n this.methodType = init.methodType;\n this.route = init.route;\n\n this.context = new SnailContext({\n server: init.server,\n serverOptions: init.serverOptions,\n apiClass: init.apiClass,\n api: init.api,\n apiName: init.apiName,\n apiOptions: init.apiOptions,\n methodName: init.methodName,\n methodType: init.methodType,\n route: init.route,\n request: init.requestConfig(),\n descriptors: init.descriptors,\n logger: init.logger\n });\n\n // Adapter plugins create `data` / `loading` / `error` here. Runs eagerly so\n // the caller can render the handles before the first request.\n init.pluginManager.runEffectsSync(\"initMeta\", this.context);\n }\n\n /** `true` while a request is in flight. */\n get pending(): boolean {\n return this.inFlight;\n }\n\n /** Result of the most recent successful request. */\n get result(): SnailResult<S, T, D, C, M> | undefined {\n return this.context.result as SnailResult<S, T, D, C, M> | undefined;\n }\n\n /** Error from the most recent failed request. */\n get error(): unknown {\n return this.context.error;\n }\n\n /** The final axios config of the most recent request. */\n get request(): InternalAxiosRequestConfig {\n return this.context.request;\n }\n\n /**\n * Send the request.\n *\n * Any arguments given here replace the ones captured when the method was\n * proxied, which is what lets a strategy own one instance and vary the\n * arguments per call.\n *\n * ## One request in flight at a time\n *\n * A `SnailMethod` owns exactly one context, and that context is what holds the\n * caller's reactive handles. Two overlapping `send()` calls would therefore\n * race over the same `ctx.response`, and the slower response could land in the\n * state the faster one already reported.\n *\n * Starting a second send therefore **aborts the first**, so the previous call\n * rejects with a `SnailCancelledError` and \"latest wins\" holds. Callers that\n * genuinely want two parallel requests should create two instances —\n * `userApi.getUser(\"1\")` and `userApi.getUser(\"2\")` — which is also what makes\n * the argument types line up.\n */\n async send(...args: unknown[]): Promise<SnailResult<S, T, D, C, M>> {\n const callArgs = args.length > 0 ? args : this.args;\n const ctx = this.begin();\n\n this.inFlight = true;\n ctx.logger.info(\n t(\n \"info.request.start\",\n this.methodType,\n `${ctx.request.baseURL ?? \"\"}${this.route}`,\n this.name\n )\n );\n\n try {\n // A plugin with an async `install` may not have wired its hooks yet.\n await this.init.pluginManager.ready;\n\n // Safety net for exactly that case: no adapter had run `initMeta` when the\n // context was built, so give it a chance now.\n if (Object.keys(ctx.meta).length === 0) {\n this.init.pluginManager.runEffectsSync(\"initMeta\", ctx);\n }\n\n this.init.pluginManager.runEffectsSync(\"beforeCreate\", ctx);\n\n applyParamDescriptors(ctx, callArgs);\n finalizeRequestURL(ctx);\n\n await this.init.pluginManager.runChain(\"beforeRequest\", ctx, () =>\n this.dispatch(ctx)\n );\n\n if (!ctx.response) {\n // Reaching this point without a response means a `beforeRequest` hook\n // declined to call `next()`: the request was refused, not attempted and\n // failed. Reporting it as a cancellation is what lets a strategy treat it\n // as expected control flow (the cache plugin on a hit does exactly this,\n // except it also supplies the response).\n throw new SnailCancelledError(t(\"error.request.cancelled\", this.name));\n }\n\n // The response chain runs for a network response and a cache hit alike.\n //\n // A hit short-circuits `beforeRequest` and supplies `ctx.response`, which\n // skips `dispatch` entirely. Leaving `afterResponse` inside `dispatch` meant\n // every plugin that works on the *payload* — zod response validation, the\n // JSON→class transform — silently did nothing on a hit, so the same call\n // returned a DTO instance the first time and a plain object the second. The\n // chain belongs to the response, not to the transport.\n //\n // `requestInterceptor` and `responseInterceptor` deliberately stay on the\n // network path: they exist to rewrite a real request and a real response.\n await this.init.pluginManager.runChain(\"afterResponse\", ctx);\n\n const result = this.finalize(ctx) as SnailResult<S, T, D, C, M>;\n this.emitter.emit(\"success\", result);\n return result;\n } catch (error) {\n throw await this.fail(ctx, error);\n } finally {\n this.inFlight = false;\n ctx.finishedAt = Date.now();\n\n // `afterRequest` runs *before* the `finish` event on purpose. Adapters clear\n // `loading` there, so emitting `finish` first would hand a caller's\n // `onFinish` handler a stale `loading === true` — precisely the flag a UI\n // reads to dismiss its spinner. Cleanup first, then tell everyone.\n try {\n await this.init.pluginManager.runEffects(\"afterRequest\", ctx);\n } catch (cleanupError) {\n ctx.logger.error(\n t(\"error.request.failed\", this.name, `afterRequest hook: ${String(cleanupError)}`)\n );\n }\n\n this.emitter.emit(\"finish\", undefined);\n }\n }\n\n /**\n * Cancel the in-flight request.\n *\n * `send()` rejects with a {@link SnailCancelledError}, which strategies treat\n * as expected control flow rather than a failure.\n */\n abort(reason?: unknown): void {\n this.controller?.abort(reason);\n }\n\n // ── events ────────────────────────────────────────────────────────────────\n\n /** Subscribe to a successful request. Returns an unsubscribe function. */\n onSuccess(listener: (result: SnailResult<S, T, D, C, M>) => void): () => void {\n return this.emitter.on(\"success\", listener);\n }\n\n /** Subscribe to a failed request. */\n onError(listener: (error: unknown) => void): () => void {\n return this.emitter.on(\"error\", listener);\n }\n\n /**\n * Subscribe to a rejected business code.\n *\n * Observation only: the request still rejects with a `SnailResponseError`, so\n * this is the right place to raise a toast, not to recover.\n */\n onCodeError(listener: (event: SnailCodeErrorEvent) => void): () => void {\n return this.emitter.on(\"codeError\", listener);\n }\n\n /** Subscribe to settlement, successful or not. */\n onFinish(listener: () => void): () => void {\n return this.emitter.on(\"finish\", listener);\n }\n\n /** Subscribe to a response served from a cache. */\n onHitCache(listener: () => void): () => void {\n return this.emitter.on(\"cache\", listener);\n }\n\n // ── pipeline ──────────────────────────────────────────────────────────────\n\n /** Reset the context for a fresh send, superseding any request already in flight. */\n private begin(): SnailContext {\n const ctx = this.context;\n\n // \"Latest wins\": a second send() invalidates the first rather than letting\n // both write into one context. The superseded call settles as cancelled,\n // which every strategy already treats as expected control flow.\n this.controller?.abort();\n this.controller = new AbortController();\n\n const config = this.init.requestConfig();\n config.signal = this.controller.signal;\n ctx.reset(config);\n return ctx;\n }\n\n /**\n * The transport step.\n *\n * Runs only when every `beforeRequest` hook called `next()`, i.e. only when the\n * response has to come from the network. The `afterResponse` chain is *not* here\n * — see {@link SnailMethod.send} for why it has to run on a cache hit too.\n */\n private async dispatch(ctx: SnailContext): Promise<void> {\n let config = this.init.pluginManager.reduce(\n \"requestInterceptor\",\n ctx.request,\n ctx\n );\n ctx.request = config;\n\n let response: AxiosResponse;\n try {\n response = await this.init.axios.request(config);\n } catch (error) {\n throw this.normalizeTransportError(error);\n }\n\n response = coerceJSONStringBody(response, ctx.serverOptions.coerceJSONString);\n ctx.setResponse(response);\n\n const intercepted = this.init.pluginManager.reduce(\n \"responseInterceptor\",\n response,\n ctx\n );\n ctx.setResponse(intercepted);\n }\n\n /** Validate the envelope and assemble the caller-facing result. */\n private finalize(ctx: SnailContext): SnailResult<S, T, D, C, M> {\n const response = ctx.requireResponse();\n const envelope = response.data;\n const { codeKey, messageKey, dataKey, validateCode } = ctx.serverOptions;\n const code = readKey<number | string>(envelope, codeKey);\n\n assertBusinessCode({\n body: envelope,\n code,\n dataKey,\n validate: validateCode,\n fullName: this.name,\n message: t(\"error.response.code\", this.name, String(code))\n });\n\n const result = buildResult<S, T, D, C, M>({\n response,\n envelope,\n codeKey,\n messageKey,\n dataKey,\n fromCache: ctx.isCacheHit,\n config: ctx.request\n });\n\n ctx.setResult(result);\n if (ctx.isCacheHit) this.emitter.emit(\"cache\", undefined);\n\n ctx.logger.info(\n t(\n \"info.request.success\",\n this.methodType,\n `${response.config.baseURL ?? \"\"}${response.config.url ?? \"\"}`,\n this.name,\n `${Math.round(ctx.elapsed)}ms`\n )\n );\n\n return result;\n }\n\n /** Report a failure through the plugin hooks and the events, then rethrow it. */\n private async fail(ctx: SnailContext, error: unknown): Promise<unknown> {\n ctx.error = error;\n\n try {\n await this.init.pluginManager.runEffects(\"onError\", ctx, error);\n } catch (hookError) {\n ctx.logger.error(\n t(\"error.request.failed\", this.name, `onError hook: ${String(hookError)}`)\n );\n }\n\n if (error instanceof SnailResponseError) {\n this.emitter.emit(\"codeError\", {\n code: error.businessCode,\n payload: error.payload,\n error\n });\n ctx.logger.warn(\n t(\n \"info.request.codeError\",\n this.methodType,\n this.route,\n this.name,\n String(error.businessCode)\n )\n );\n } else {\n this.emitter.emit(\"error\", error);\n if (!(error instanceof SnailCancelledError)) {\n ctx.logger.error(\n t(\"info.request.error\", this.methodType, this.route, this.name, String(error))\n );\n }\n }\n\n return error;\n }\n\n /**\n * Turn axios' error vocabulary into ours.\n *\n * The rule is: **if the server answered, hand back axios' error unchanged** —\n * it carries `response.status` and `response.data`, which applications and the\n * auth strategies both branch on. Only when no response exists at all (DNS\n * failure, offline, CORS rejection) is there nothing useful to preserve, so\n * that case becomes a typed {@link SnailHttpError} with the axios error as its\n * `cause`.\n */\n private normalizeTransportError(error: unknown): unknown {\n if (error instanceof SnailCancelledError) return error;\n\n const axiosError = error as {\n code?: string;\n message?: string;\n response?: AxiosResponse;\n isAxiosError?: boolean;\n config?: { timeout?: number };\n };\n\n if (isCancel(error) || axiosError?.code === \"ERR_CANCELED\") {\n return new SnailCancelledError(t(\"error.request.cancelled\", this.name), {\n cause: error\n });\n }\n\n if (axiosError?.code === \"ECONNABORTED\" || axiosError?.code === \"ETIMEDOUT\") {\n return new SnailTimeoutError(\n t(\"error.request.timeout\", this.name, String(axiosError.config?.timeout ?? \"\")),\n { timeout: axiosError.config?.timeout, cause: error }\n );\n }\n\n if (axiosError?.isAxiosError === true && !axiosError.response) {\n return new SnailHttpError(\n t(\"error.request.failed\", this.name, axiosError.message ?? \"network error\"),\n { cause: error, code: \"SNAIL_NETWORK_ERROR\" }\n );\n }\n\n return error;\n }\n}\n","import { SnailPluginError } from \"../error/plugin\";\nimport { t } from \"../locale\";\nimport type { SnailPluginInstallContext, SnailPluginObject } from \"../typings/plugin\";\nimport type { ResolvedServerOptions } from \"../typings/server\";\nimport { composeChain, type BoundHook } from \"./plugin\";\n\n/** Hook names that traverse the plugin list in the forward direction. */\nconst FORWARD_HOOKS = new Set<string>([\n \"configureServer\",\n \"configureApi\",\n \"configureMethod\",\n \"initMeta\",\n \"beforeCreate\",\n \"beforeRequest\",\n \"requestInterceptor\"\n]);\n\n/** Hook names that traverse the plugin list in the unwind direction. */\nconst UNWIND_HOOKS = new Set<string>([\n \"afterResponse\",\n \"responseInterceptor\",\n \"onError\",\n \"afterRequest\"\n]);\n\n/** A plugin registered on one server. */\nexport interface RegisteredPlugin {\n /** Plugin name. */\n readonly name: string;\n /** Chain ordering weight; higher runs first in the forward phase. */\n readonly priority: number;\n /** Registration sequence, used as a stable tie-breaker. */\n readonly index: number;\n /** The plugin object itself. */\n readonly instance: SnailPluginObject<any>;\n}\n\n/**\n * Per-server plugin registry.\n *\n * ## Why this is not a singleton\n *\n * The pre-rewrite implementation kept one global manager plus a mutable\n * `_server` pointer that every caller had to set with `switchServer()` before\n * `getHooks()`. Any two servers in flight — or simply two interleaved `await`s —\n * silently read the *wrong* server's plugins. Each `SnailServer` now owns its\n * own manager and passes its name in the constructor, so that class of bug is\n * unrepresentable.\n *\n * ## Ordering\n *\n * Plugins sort by `priority` descending, ties broken by registration order.\n *\n * - **Forward hooks** run highest-priority first, so an interceptor (`100`)\n * installs before the cache (`-100`).\n * - **Unwind hooks** run in the opposite direction, closing the onion: the\n * plugin closest to the network reacts to the response first.\n *\n * ## Sync registration, async install\n *\n * `register()` is synchronous and returns immediately, so `Service.use(A).use(B)`\n * chains naturally while a `dependsOn` mistake still throws on the spot. The\n * `install` hooks themselves may be async; {@link PluginManager.ready} is awaited\n * once per request, before any plugin hook runs.\n */\nexport class PluginManager {\n private readonly registry = new Map<string, RegisteredPlugin>();\n private readonly order: string[] = [];\n private readonly installTasks: Array<Promise<void>> = [];\n private sequence = 0;\n\n constructor(\n private readonly serverName: string,\n private readonly serverOptions: ResolvedServerOptions\n ) {}\n\n /** Number of registered plugins. */\n get size(): number {\n return this.registry.size;\n }\n\n /** Resolves once every `install` hook has settled. */\n get ready(): Promise<void> {\n return Promise.all(this.installTasks).then(() => undefined);\n }\n\n /** `true` when a plugin with this name is registered. */\n has(name: string): boolean {\n return this.registry.has(name);\n }\n\n /** Read one registered plugin. */\n get(name: string): RegisteredPlugin | undefined {\n return this.registry.get(name);\n }\n\n /** Registered plugin names in chain order (forward phase). */\n names(): string[] {\n return this.sorted(\"forward\").map((entry) => entry.name);\n }\n\n /** All registered plugins in chain order (forward phase). */\n list(): RegisteredPlugin[] {\n return this.sorted(\"forward\");\n }\n\n /**\n * Validate and register a plugin. Synchronous by design.\n *\n * Throws {@link SnailPluginError} for a nameless plugin, a duplicate name or\n * an unsatisfied `dependsOn` — all before anything is mutated.\n */\n register(plugin: SnailPluginObject<any>): void {\n if (!plugin || typeof plugin !== \"object\") {\n throw new SnailPluginError(\"[snail] use() expects a plugin object\");\n }\n\n const { name } = plugin;\n if (typeof name !== \"string\" || name.length === 0) {\n throw new SnailPluginError(\"[snail] a plugin must declare a non-empty `name`\");\n }\n\n if (this.registry.has(name)) {\n throw new SnailPluginError(\n t(\"error.options.plugin.exists\", name, this.serverName),\n { pluginName: name }\n );\n }\n\n for (const dependency of plugin.dependsOn ?? []) {\n if (!this.registry.has(dependency)) {\n throw new SnailPluginError(\n t(\"error.options.plugin.missing\", name, dependency),\n { pluginName: name }\n );\n }\n }\n\n this.registry.set(name, {\n name,\n priority: Number.isFinite(plugin.priority) ? (plugin.priority as number) : 0,\n index: this.sequence++,\n instance: plugin\n });\n this.order.push(name);\n\n if (typeof plugin.install === \"function\") {\n const context: SnailPluginInstallContext = {\n serverName: this.serverName,\n serverOptions: this.serverOptions,\n pluginNames: this.names()\n };\n\n // `install` is invoked eagerly and synchronously when it can be, so a\n // plugin's hooks are wired the moment `use()` returns. Framework adapters\n // depend on that: their `initMeta` hook must exist before the first\n // `createApi()` call. A promise-returning `install` is queued on `ready`.\n let outcome: unknown;\n try {\n outcome = plugin.install(context, plugin.options);\n } catch (error) {\n this.registry.delete(name);\n this.order.pop();\n throw new SnailPluginError(\n `[snail] plugin \"${name}\" failed to install: ${String(error)}`,\n { pluginName: name, cause: error }\n );\n }\n\n if (outcome && typeof (outcome as PromiseLike<unknown>).then === \"function\") {\n this.installTasks.push(\n Promise.resolve(outcome).then(\n () => undefined,\n (error: unknown) => {\n throw new SnailPluginError(\n `[snail] plugin \"${name}\" failed to install: ${String(error)}`,\n { pluginName: name, cause: error }\n );\n }\n )\n );\n }\n }\n }\n\n /** Unregister a plugin, running its `uninstall` hook first. */\n async remove(name: string): Promise<void> {\n const entry = this.registry.get(name);\n if (!entry) {\n throw new SnailPluginError(\n t(\"error.options.plugin.notFound\", name, this.serverName),\n { pluginName: name }\n );\n }\n\n await entry.instance.uninstall?.(\n {\n serverName: this.serverName,\n serverOptions: this.serverOptions,\n pluginNames: this.names()\n },\n entry.instance.options\n );\n\n this.registry.delete(name);\n const at = this.order.indexOf(name);\n if (at !== -1) this.order.splice(at, 1);\n }\n\n /** Remove every plugin, unwinding in reverse registration order. */\n async clear(): Promise<void> {\n for (const name of [...this.order].reverse()) {\n await this.remove(name);\n }\n }\n\n /**\n * Sorted plugin list.\n *\n * `\"forward\"` → priority descending (highest first).\n * `\"unwind\"` → priority ascending (highest last).\n */\n sorted(direction: \"forward\" | \"unwind\"): RegisteredPlugin[] {\n const entries = this.order\n .map((name) => this.registry.get(name))\n .filter((entry): entry is RegisteredPlugin => entry !== undefined);\n\n entries.sort((a, b) =>\n a.priority === b.priority ? a.index - b.index : b.priority - a.priority\n );\n\n return direction === \"forward\" ? entries : entries.reverse();\n }\n\n /** Hooks of one name, in the direction that hook traverses. */\n hooks(hookName: string): BoundHook[] {\n if (!FORWARD_HOOKS.has(hookName) && !UNWIND_HOOKS.has(hookName)) {\n throw new SnailPluginError(t(\"error.hook.unknown\", hookName));\n }\n\n const direction = FORWARD_HOOKS.has(hookName) ? \"forward\" : \"unwind\";\n const bound: BoundHook[] = [];\n\n for (const entry of this.sorted(direction)) {\n const hook = (entry.instance as unknown as Record<string, unknown>)[hookName];\n if (typeof hook === \"function\") {\n bound.push({ pluginName: entry.name, hook: hook as BoundHook[\"hook\"] });\n }\n }\n\n return bound;\n }\n\n /** `true` when at least one plugin implements `hookName`. */\n hasHook(hookName: string): boolean {\n for (const name of this.order) {\n const hook = (this.registry.get(name)!.instance as unknown as Record<string, unknown>)[\n hookName\n ];\n if (typeof hook === \"function\") return true;\n }\n return false;\n }\n\n /**\n * Run a chain hook over every plugin, then `downstream`.\n *\n * A plugin that never calls `next()` stops the chain: `downstream` (for\n * `beforeRequest`, the HTTP request itself) never runs. That is the mechanism\n * behind a cache hit.\n */\n async runChain(\n hookName: string,\n ctx: unknown,\n downstream?: () => Promise<void> | void\n ): Promise<void> {\n const hooks = this.hooks(hookName);\n if (hooks.length === 0) {\n await downstream?.();\n return;\n }\n await composeChain(hookName, hooks)(ctx, downstream);\n }\n\n /** Run a non-chain hook over every plugin, sequentially and awaited. */\n async runEffects(hookName: string, ...args: unknown[]): Promise<void> {\n for (const { hook } of this.hooks(hookName)) {\n await hook(...args);\n }\n }\n\n /** Run a non-chain hook synchronously — for constructor-time hooks. */\n runEffectsSync(hookName: string, ...args: unknown[]): void {\n for (const { hook } of this.hooks(hookName)) {\n hook(...args);\n }\n }\n\n /**\n * Fold a value through every plugin's hook.\n *\n * Used by `requestInterceptor` / `responseInterceptor`, where each plugin may\n * return a replacement for the value it received. Returning `undefined` keeps\n * the previous value.\n */\n reduce<T>(hookName: string, initial: T, ...args: unknown[]): T {\n let current = initial;\n for (const { hook } of this.hooks(hookName)) {\n const next = hook(current, ...args);\n if (next !== undefined && next !== null) current = next as T;\n }\n return current;\n }\n}\n","import { SnailError } from \"./base\";\n\n/**\n * Thrown when a required configuration block is missing — a server class\n * without `@Server()`, an api class without `@Api()`, or a value that failed\n * one of the option guards.\n */\nexport class SnailOptionsError extends SnailError {\n constructor(message: string, options: { cause?: unknown } = {}) {\n super(message, { code: \"SNAIL_OPTIONS_ERROR\", cause: options.cause });\n }\n}\n","import { AxiosHeaders } from \"axios\";\nimport type { InternalAxiosRequestConfig } from \"axios\";\nimport { DEFAULT_SERVER_OPTIONS } from \"../default/options\";\nimport {\n SNAIL_API_OPTIONS,\n SNAIL_DOWNLOAD_PROGRESS,\n SNAIL_HEADERS,\n SNAIL_REQUEST_METHOD,\n SNAIL_SERVER_OPTIONS,\n SNAIL_UPLOAD_PROGRESS\n} from \"./metadata.keys\";\nimport { getMetadata } from \"./metadata\";\nimport { SnailOptionsError } from \"../error/options\";\nimport { t } from \"../locale\";\nimport type {\n SnailApiOptions,\n SnailMethodOptions,\n SnailMethodType\n} from \"../typings/api\";\nimport type { ResolvedServerOptions, SnailServerOptions } from \"../typings/server\";\nimport { buildRequestURL } from \"../utils/url\";\n\n/**\n * Turns raw decorator metadata into concrete, fully-defaulted options.\n *\n * Everything a request needs is resolved *here* rather than spread across the\n * server, the proxy and the method. One place to read, one place to test.\n */\n\n/** Read and default the options written by `@Server(...)`. */\nexport function resolveServerOptions(\n serverClass: unknown,\n fallbackName: string\n): ResolvedServerOptions {\n const declared = getMetadata<SnailServerOptions>(SNAIL_SERVER_OPTIONS, serverClass);\n if (!declared) {\n throw new SnailOptionsError(t(\"error.options.server.missing\", fallbackName));\n }\n\n const baseURL = declared.baseURL ?? DEFAULT_SERVER_OPTIONS.baseURL;\n if (typeof baseURL !== \"string\" || baseURL.length === 0) {\n throw new SnailOptionsError(t(\"error.options.server.baseURL\"));\n }\n\n return {\n ...declared,\n name: declared.name ?? fallbackName,\n baseURL,\n timeout: declared.timeout ?? DEFAULT_SERVER_OPTIONS.timeout,\n codeKey: declared.codeKey ?? DEFAULT_SERVER_OPTIONS.codeKey,\n messageKey: declared.messageKey ?? DEFAULT_SERVER_OPTIONS.messageKey,\n dataKey: declared.dataKey ?? DEFAULT_SERVER_OPTIONS.dataKey,\n logLevel: declared.logLevel ?? DEFAULT_SERVER_OPTIONS.logLevel,\n coerceJSONString:\n declared.coerceJSONString ?? DEFAULT_SERVER_OPTIONS.coerceJSONString\n };\n}\n\n/** Read and default the options written by `@Api(...)`. */\nexport function resolveApiOptions(\n apiClass: new () => unknown,\n fallbackName: string\n): Required<SnailApiOptions> {\n const declared = getMetadata<SnailApiOptions>(SNAIL_API_OPTIONS, apiClass) ?? {};\n\n if (declared.url !== undefined && typeof declared.url !== \"string\") {\n throw new SnailOptionsError(t(\"error.options.api.url\"));\n }\n\n return {\n url: declared.url ?? \"\",\n name: declared.name ?? fallbackName,\n timeout: declared.timeout as number,\n adapter: declared.adapter as never,\n responseType: declared.responseType as never,\n withCredentials: declared.withCredentials as boolean\n };\n}\n\n/** Read the request verb written by `@Get()` / `@Post()` / … */\nexport function resolveRequestMethod(\n apiClass: new () => unknown,\n methodName: string\n): SnailMethodType | undefined {\n const options = getMetadata<{ method: SnailMethodType }>(\n SNAIL_REQUEST_METHOD,\n apiClass,\n methodName\n );\n return options?.method;\n}\n\n/** Read the full request-method options written by `@Get(path, options)`. */\nexport function resolveMethodDecoratorOptions(\n apiClass: new () => unknown,\n methodName: string\n): (SnailMethodOptions & { method: SnailMethodType; url: string }) | undefined {\n return getMetadata<SnailMethodOptions & { method: SnailMethodType; url: string }>(\n SNAIL_REQUEST_METHOD,\n apiClass,\n methodName\n );\n}\n\n/**\n * Merge the three levels of `@Header(...)` — api class, then method.\n *\n * Method-level headers win, which is the only ordering that lets a single\n * endpoint override a class-wide default.\n */\nexport function resolveHeaders(\n apiClass: new () => unknown,\n methodName: string\n): AxiosHeaders {\n const apiHeaders = getMetadata<Record<string, unknown>>(SNAIL_HEADERS, apiClass) ?? {};\n const methodHeaders =\n getMetadata<Record<string, unknown>>(SNAIL_HEADERS, apiClass, methodName) ?? {};\n return AxiosHeaders.from({ ...apiHeaders, ...methodHeaders } as Record<string, string>);\n}\n\n/** Progress callbacks written by `@UploadProgress()` / `@DownloadProgress()`. */\nexport function resolveProgress(\n apiClass: new () => unknown,\n methodName: string\n): {\n onUploadProgress: SnailMethodOptions[\"onUploadProgress\"];\n onDownloadProgress: SnailMethodOptions[\"onDownloadProgress\"];\n} {\n return {\n onUploadProgress: getMetadata(SNAIL_UPLOAD_PROGRESS, apiClass, methodName),\n onDownloadProgress: getMetadata(SNAIL_DOWNLOAD_PROGRESS, apiClass, methodName)\n };\n}\n\n/** Join the api prefix with a method path. */\nexport function resolveRoute(apiURL: string, methodPath: string): string {\n return buildRequestURL(apiURL, methodPath || \"\");\n}\n\n/**\n * Build the axios config a request starts from.\n *\n * Values cascade method → api → server, and only the winner survives. The\n * argument decorators and the plugins refine this further during the pipeline.\n */export function buildBaseRequestConfig(input: {\n serverOptions: ResolvedServerOptions;\n apiOptions: Required<SnailApiOptions>;\n methodOptions: SnailMethodOptions & { url: string };\n methodType: SnailMethodType;\n headers: AxiosHeaders;\n}): InternalAxiosRequestConfig {\n const { serverOptions, apiOptions, methodOptions, methodType, headers } = input;\n\n // `AxiosHeaders.from(existing)` returns the *same* instance rather than a copy.\n // Since the per-method `headers` object is cached on the method descriptor and\n // reused by every request of that method, building on it directly would let a\n // `@HeaderValue()` argument or a plugin's in-place mutation leak into the next\n // request. `concat` always allocates a fresh instance.\n const merged = AxiosHeaders.concat(headers);\n for (const [key, value] of Object.entries(serverOptions.headers ?? {})) {\n if (!merged.has(key)) merged.set(key, value as never);\n }\n\n return {\n url: methodOptions.url,\n method: methodType.toLowerCase(),\n baseURL: serverOptions.baseURL,\n timeout: methodOptions.timeout ?? apiOptions.timeout ?? serverOptions.timeout,\n responseType:\n methodOptions.responseType ??\n apiOptions.responseType ??\n serverOptions.responseType ??\n \"json\",\n withCredentials:\n methodOptions.withCredentials ??\n apiOptions.withCredentials ??\n serverOptions.withCredentials,\n adapter: methodOptions.adapter ?? apiOptions.adapter ?? serverOptions.adapter,\n headers: merged,\n params: { ...(serverOptions.params ?? {}), ...(methodOptions.params ?? {}) },\n data: methodOptions.data,\n onUploadProgress: methodOptions.onUploadProgress,\n onDownloadProgress: methodOptions.onDownloadProgress\n };\n}\n","import type { SnailSseHandlers, SnailWsHandlers } from \"../decorators/stream\";\nimport type {\n SnailHttpStreamOptions,\n SnailSseOptions,\n SnailWsOptions\n} from \"../typings/stream\";import { getMetadata } from \"./metadata\";\nimport {\n SNAIL_HTTP_STREAM,\n SNAIL_SSE_HANDLERS,\n SNAIL_SSE_OPTIONS,\n SNAIL_WS_HANDLERS,\n SNAIL_WS_OPTIONS\n} from \"./metadata.keys\";\n\n/** Resolved description of an `@Sse` endpoint. */\nexport interface ResolvedSseEndpoint {\n url: string;\n options: SnailSseOptions;\n handlers: SnailSseHandlers;\n}\n\n/** Resolved description of an `@WebSocket` endpoint. */\nexport interface ResolvedWsEndpoint {\n url: string;\n options: SnailWsOptions;\n handlers: SnailWsHandlers;\n}\n\n/** Resolved description of an `@HttpStream` method. */\nexport interface ResolvedHttpStreamEndpoint {\n url: string;\n options: SnailHttpStreamOptions;\n}\n\nconst EMPTY_SSE_HANDLERS: SnailSseHandlers = { open: [], error: [], events: [] };\nconst EMPTY_WS_HANDLERS: SnailWsHandlers = {\n open: [],\n message: [],\n close: [],\n error: []\n};\n\n/** Read the `@Sse(...)` options and its registered handlers. */\nexport function resolveSseEndpoint(\n streamClass: unknown\n): ResolvedSseEndpoint | undefined {\n const declared = getMetadata<SnailSseOptions & { url: string }>(\n SNAIL_SSE_OPTIONS,\n streamClass\n );\n if (!declared) return undefined;\n\n return {\n url: declared.url ?? \"\",\n options: declared,\n handlers:\n getMetadata<SnailSseHandlers>(SNAIL_SSE_HANDLERS, streamClass) ??\n EMPTY_SSE_HANDLERS\n };\n}\n\n/** Read the `@WebSocket(...)` options and its registered handlers. */\nexport function resolveWsEndpoint(\n streamClass: unknown\n): ResolvedWsEndpoint | undefined {\n const declared = getMetadata<SnailWsOptions & { url: string }>(\n SNAIL_WS_OPTIONS,\n streamClass\n );\n if (!declared) return undefined;\n\n return {\n url: declared.url ?? \"\",\n options: declared,\n handlers:\n getMetadata<SnailWsHandlers>(SNAIL_WS_HANDLERS, streamClass) ??\n EMPTY_WS_HANDLERS\n };\n}\n\n/** Read the `@HttpStream(...)` options of one method. */\nexport function resolveHttpStreamEndpoint(\n apiClass: unknown,\n methodName: string\n): ResolvedHttpStreamEndpoint | undefined {\n const declared = getMetadata<SnailHttpStreamOptions & { url: string }>(\n SNAIL_HTTP_STREAM,\n apiClass,\n methodName\n );\n if (!declared) return undefined;\n return { url: declared.url ?? \"\", options: declared };\n}\n\n/**\n * Turn an http(s) `baseURL` into the matching WebSocket origin.\n *\n * `https://api.example.com` → `wss://api.example.com`. A relative `baseURL`\n * (the common browser case) keeps working because `new WebSocket` resolves a\n * relative url against the document base — but the scheme still has to be\n * upgraded explicitly, which is what this does.\n */\nexport function toWebSocketURL(url: string): string {\n if (url.startsWith(\"https://\")) return `wss://${url.slice(\"https://\".length)}`;\n if (url.startsWith(\"http://\")) return `ws://${url.slice(\"http://\".length)}`;\n return url;\n}\n\n/**\n * Bind every SSE handler to the instance that will receive the events.\n *\n * Decorators only ever see the prototype, so the raw functions are stored and\n * bound here — one binding per `open()`, against the instance actually created by\n * `createSse`.\n */\nexport function rebindSseHandlers(\n handlers: SnailSseHandlers,\n instance: object\n): SnailSseHandlers {\n return {\n open: handlers.open.map((fn) => fn.bind(instance)),\n error: handlers.error.map((fn) => fn.bind(instance)),\n events: handlers.events.map((entry) => ({\n event: entry.event,\n handler: entry.handler.bind(instance)\n }))\n };\n}\n\n/** Bind every WebSocket handler to the instance. @see rebindSseHandlers */\nexport function rebindWsHandlers(\n handlers: SnailWsHandlers,\n instance: object\n): SnailWsHandlers {\n return {\n open: handlers.open.map((fn) => fn.bind(instance)),\n message: handlers.message.map((fn) => fn.bind(instance)),\n close: handlers.close.map((fn) => fn.bind(instance)),\n error: handlers.error.map((fn) => fn.bind(instance))\n };\n}\n","import type { SnailReconnectPolicy } from \"../typings/stream\";\n\n/** A reconnect policy with every default applied. */\nexport type ResolvedReconnectPolicy = Required<SnailReconnectPolicy>;\n\n/** Defaults used when a transport enables reconnecting without a policy. */\nexport const DEFAULT_RECONNECT_POLICY: ResolvedReconnectPolicy = {\n retries: 3,\n delayMs: 1000,\n maxDelayMs: 30000,\n factor: 2,\n jitter: true\n};\n\n/**\n * Normalise the `reconnect` option.\n *\n * Returns `undefined` when reconnecting is switched off, so callers can branch on\n * a single falsy check instead of handling `false | undefined | policy`.\n */\nexport function resolveReconnectPolicy(\n policy: false | SnailReconnectPolicy | undefined\n): ResolvedReconnectPolicy | undefined {\n if (policy === false) return undefined;\n if (policy === undefined) return { ...DEFAULT_RECONNECT_POLICY };\n return { ...DEFAULT_RECONNECT_POLICY, ...policy };\n}\n\n/**\n * Exponential backoff with optional full jitter.\n *\n * `attempt` is 1-based: `attempt: 1` returns the first delay. The result is\n * always capped by `maxDelayMs`, so a long outage cannot push a retry hours out.\n */\nexport function backoffDelay(\n attempt: number,\n policy: ResolvedReconnectPolicy\n): number {\n const base = policy.delayMs * Math.pow(policy.factor, Math.max(0, attempt - 1));\n const capped = Math.min(base, policy.maxDelayMs);\n if (!policy.jitter) return capped;\n // Full jitter: uniform between 0 and the capped delay, with a small floor so a\n // reconnect is never scheduled for \"immediately\".\n return Math.max(50, Math.round(Math.random() * capped));\n}\n\n/** `true` when another attempt is allowed. */\nexport function canRetry(attempt: number, policy: ResolvedReconnectPolicy): boolean {\n return attempt <= policy.retries;\n}\n","import { SnailRequestError } from \"../error/request\";\nimport { t } from \"../locale\";\nimport type {\n SnailSseConnection,\n SnailSseMessage,\n SnailSseOptions\n} from \"../typings/stream\";\nimport type { SnailSseHandlers } from \"../decorators/stream\";\nimport { deferred } from \"../utils/object\";\nimport { withDispose } from \"./connection\";\nimport { backoffDelay, canRetry, resolveReconnectPolicy } from \"./reconnect\";\nimport type { SnailLogger } from \"./logger\";\n\n/** Everything the SSE transport needs to open a connection. */\nexport interface SseConnectionInit {\n /** Fully qualified url. */\n url: string;\n /** Options from `@Sse(path, options)`. */\n options: SnailSseOptions;\n /** Handlers registered by the decorators. */\n handlers: SnailSseHandlers;\n /** Name used in log lines. */\n name: string;\n /** Extra headers (server-level, auth, …). */\n headers?: Record<string, string>;\n /** Logger. */\n logger: SnailLogger;\n}\n\n/**\n * Server-Sent Events over `fetch`.\n *\n * `EventSource` is deliberately not used: it cannot send request headers, cannot\n * `POST`, cannot be aborted cleanly and reconnects on its own terms. A `fetch`\n * stream reader supports all four, and the parser below is ~50 lines.\n */\nexport function createSseConnection(init: SseConnectionInit): SnailSseConnection {\n const { url, options, handlers, name, logger } = init;\n const policy = resolveReconnectPolicy(options.reconnect ?? { retries: 3 });\n\n const opened = deferred<void>();\n const closed = deferred<void>();\n const controller = new AbortController();\n\n // A caller that only iterates or only calls `close()` never attaches a handler\n // to `opened`. Marking it handled here keeps a legitimate failure from being\n // reported as an unhandled rejection, while `await connection.opened` still\n // rejects with the real error.\n void opened.promise.catch(() => undefined);\n\n let connected = false;\n let stopped = false;\n let attempt = 0;\n let retryHint: number | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let lastError: unknown;\n\n /**\n * Whether the current attempt delivered at least one event.\n *\n * This is what separates a healthy connection from a doomed one. A server that\n * accepts the request, sends events and then closes the stream is behaving\n * correctly and should reconnect with a fresh retry budget; a server that\n * accepts and immediately closes without ever sending anything is looping, and\n * its retry budget must actually run out.\n *\n * Resetting the counter as soon as headers arrive — the obvious implementation\n * — makes the budget meaningless and produces an unbounded hot reconnect loop.\n */\n let sawEvent = false;\n\n const messageListeners = new Set<(message: SnailSseMessage) => void>();\n const eventListeners = new Map<string, Set<(message: SnailSseMessage) => void>>();\n\n /** Invoke one listener set, keeping a broken listener from killing the stream. */\n const notify = (\n listeners: Iterable<(message: SnailSseMessage) => void>,\n message: SnailSseMessage\n ): void => {\n for (const listener of [...listeners]) {\n try {\n listener(message);\n } catch (error) {\n logger.error(`[snail] ${name} SSE message listener threw`, error);\n }\n }\n };\n\n const dispatch = (message: SnailSseMessage): void => {\n sawEvent = true;\n for (const entry of handlers.events) {\n if (entry.event !== message.event) continue;\n try {\n (entry.handler as (message: SnailSseMessage) => void)(message);\n } catch (error) {\n logger.error(`[snail] ${name} SSE handler for \"${message.event}\" threw`, error);\n }\n }\n notify(messageListeners, message);\n notify(eventListeners.get(message.event) ?? [], message);\n };\n\n const scheduleReconnect = (): void => {\n if (stopped) return;\n\n if (!policy || !canRetry(attempt + 1, policy)) {\n if (!connected) {\n // Surface the *actual* failure — \"responded 500\" or \"connection refused\"\n // — rather than a generic \"gave up after N attempts\", which would hide\n // the one detail the caller needs to diagnose it.\n opened.reject(\n lastError ??\n new SnailRequestError(\n t(\"error.request.failed\", name, `SSE connection failed after ${attempt} attempt(s)`)\n )\n );\n }\n stopped = true;\n closed.resolve();\n return;\n }\n\n attempt += 1;\n const delay = retryHint ?? backoffDelay(attempt, policy);\n retryHint = undefined;\n\n timer = setTimeout(() => {\n void run();\n }, delay);\n };\n\n const run = async (): Promise<void> => {\n if (stopped) return;\n\n sawEvent = false;\n\n try {\n const response = await fetch(url, {\n method: options.method ?? \"GET\",\n headers: {\n accept: \"text/event-stream\",\n ...(options.headers ?? {}),\n ...(init.headers ?? {})\n },\n body:\n options.method === \"POST\" && options.data !== undefined\n ? JSON.stringify(options.data)\n : undefined,\n credentials: options.withCredentials ? \"include\" : \"same-origin\",\n signal: controller.signal\n });\n\n if (!response.ok) {\n throw new SnailRequestError(\n t(\"error.request.failed\", name, `SSE responded ${response.status} ${response.statusText}`)\n );\n }\n if (!response.body) {\n throw new SnailRequestError(\n t(\"error.request.failed\", name, \"SSE response has no readable body\")\n );\n }\n\n if (!connected) {\n connected = true;\n opened.resolve();\n for (const handler of handlers.open) {\n try {\n handler(new Event(\"open\"));\n } catch (error) {\n logger.error(`[snail] ${name} SSE open handler threw`, error);\n }\n }\n logger.info(t(\"info.sse.open\", name));\n }\n\n await readStream(response.body, dispatch, (hint) => {\n retryHint = hint;\n });\n\n // A clean end-of-stream is still a disconnect: fall through to reconnect.\n connected = false;\n lastError = undefined;\n // A connection that delivered events was healthy — start the budget over.\n if (sawEvent) attempt = 0;\n scheduleReconnect();\n } catch (error) {\n if (controller.signal.aborted || stopped) {\n stopped = true;\n closed.resolve();\n return;\n }\n\n lastError = error;\n\n for (const handler of handlers.error) {\n try {\n handler(new Event(\"error\"));\n } catch (handlerError) {\n logger.error(`[snail] ${name} SSE error handler threw`, handlerError);\n }\n }\n logger.warn(t(\"error.request.failed\", name, `SSE ${String(error)}`));\n\n connected = false;\n scheduleReconnect();\n }\n };\n\n void run();\n\n return withDispose({\n get connected() {\n return connected;\n },\n opened: opened.promise,\n closed: closed.promise,\n onMessage(listener) {\n messageListeners.add(listener);\n return () => {\n messageListeners.delete(listener);\n };\n },\n on(event, listener) {\n let listeners = eventListeners.get(event);\n if (!listeners) {\n listeners = new Set();\n eventListeners.set(event, listeners);\n }\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n close() {\n if (stopped) return;\n stopped = true;\n if (timer) clearTimeout(timer);\n controller.abort();\n connected = false;\n messageListeners.clear();\n eventListeners.clear();\n logger.info(t(\"info.sse.close\", name));\n closed.resolve();\n }\n });\n}\n\n/**\n * Parse an event-stream body.\n *\n * Follows the WHATWG rules that matter in practice: lines are terminated by\n * `\\n`, `\\r\\n` or `\\r`; a blank line dispatches the buffered event; `:` starts a\n * comment; a field value keeps everything after the first colon, minus one\n * leading space.\n */\nasync function readStream(\n body: ReadableStream<Uint8Array>,\n dispatch: (message: SnailSseMessage) => void,\n onRetry: (hint: number) => void\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n\n let buffer = \"\";\n let eventName = \"message\";\n let dataLines: string[] = [];\n let lastId = \"\";\n let sawData = false;\n\n const flush = (): void => {\n if (!sawData) {\n eventName = \"message\";\n dataLines = [];\n return;\n }\n dispatch({\n event: eventName || \"message\",\n data: dataLines.join(\"\\n\"),\n id: lastId,\n retry: undefined\n });\n eventName = \"message\";\n dataLines = [];\n sawData = false;\n };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n let newlineIndex = buffer.search(/\\r\\n|\\r|\\n/);\n while (newlineIndex !== -1) {\n const line = buffer.slice(0, newlineIndex);\n const terminatorLength = buffer.startsWith(\"\\r\\n\", newlineIndex) ? 2 : 1;\n buffer = buffer.slice(newlineIndex + terminatorLength);\n newlineIndex = buffer.search(/\\r\\n|\\r|\\n/);\n\n if (line.length === 0) {\n flush();\n continue;\n }\n if (line.startsWith(\":\")) continue;\n\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n eventName = value;\n break;\n case \"data\":\n dataLines.push(value);\n sawData = true;\n break;\n case \"id\":\n lastId = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (Number.isFinite(parsed)) onRetry(parsed);\n break;\n }\n default:\n break;\n }\n }\n }\n\n flush();\n}\n","import { SnailRequestError } from \"../error/request\";\nimport { t } from \"../locale\";\nimport type { SnailSocketConnection, SnailWsOptions } from \"../typings/stream\";\nimport type { SnailWsHandlers } from \"../decorators/stream\";\nimport { deferred } from \"../utils/object\";\nimport { withDispose } from \"./connection\";\nimport { backoffDelay, canRetry, resolveReconnectPolicy } from \"./reconnect\";\nimport type { SnailLogger } from \"./logger\";\n\n/** Everything the WebSocket transport needs. */\nexport interface WsConnectionInit {\n /** Fully qualified `ws://` / `wss://` url. */\n url: string;\n /** Options from `@WebSocket(path, options)`. */\n options: SnailWsOptions;\n /** Handlers registered by the decorators. */\n handlers: SnailWsHandlers;\n /** Name used in log lines. */\n name: string;\n /** Logger. */\n logger: SnailLogger;\n}\n\n/**\n * WebSocket with reconnecting and an outbound queue.\n *\n * Two behaviours the platform socket does not give you, and which every real\n * application ends up writing by hand:\n *\n * - **Reconnect with backoff** — `close` is normal; without a policy the socket\n * simply stays dead after a blip.\n * - **Send-before-open queueing** — `open()` returns immediately, so a caller\n * that sends on the next line would otherwise throw `InvalidStateError`.\n */\nexport function createWsConnection(init: WsConnectionInit): SnailSocketConnection {\n const { url, options, handlers, name, logger } = init;\n const policy = resolveReconnectPolicy(options.reconnect ?? { retries: 3 });\n const serializer = normalizeSerializer(options.serializer);\n const queueWhileConnecting = options.queueWhileConnecting ?? true;\n\n const opened = deferred<void>();\n const closed = deferred<void>();\n\n // Marked handled so a connection that never opens does not become an unhandled\n // rejection when the caller only ever calls `send()`/`close()`.\n void opened.promise.catch(() => undefined);\n\n let socket: WebSocket | undefined;\n let connected = false;\n let stopped = false;\n let attempt = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let queue: unknown[] = [];\n\n const emit = <K extends keyof SnailWsHandlers>(\n kind: K,\n event: SnailWsHandlers[K] extends Array<(event: infer E) => void> ? E : never\n ): void => {\n for (const handler of handlers[kind] as Array<(event: unknown) => void>) {\n try {\n handler(event);\n } catch (error) {\n logger.error(`[snail] ${name} WS ${kind} handler threw`, error);\n }\n }\n };\n\n const flush = (): void => {\n if (!socket || !connected || queue.length === 0) return;\n const pending = queue;\n queue = [];\n for (const message of pending) {\n socket.send(serializer.serialize(message));\n }\n };\n\n const scheduleReconnect = (): void => {\n if (stopped) return;\n\n if (!policy || !canRetry(attempt + 1, policy)) {\n if (!connected) {\n opened.reject(\n new SnailRequestError(\n t(\"error.request.failed\", name, `WebSocket failed after ${attempt} attempt(s)`)\n )\n );\n }\n stopped = true;\n closed.resolve();\n return;\n }\n\n attempt += 1;\n timer = setTimeout(connect, backoffDelay(attempt, policy));\n };\n\n const connect = (): void => {\n if (stopped) return;\n\n let ws: WebSocket;\n try {\n ws = new WebSocket(url, options.protocols);\n } catch (error) {\n logger.warn(t(\"error.request.failed\", name, `WebSocket ${String(error)}`));\n scheduleReconnect();\n return;\n }\n socket = ws;\n\n ws.onopen = (event) => {\n connected = true;\n attempt = 0;\n opened.resolve();\n emit(\"open\", event);\n logger.info(t(\"info.ws.open\", name));\n flush();\n };\n\n ws.onmessage = (event) => {\n const raw = options.serializer === \"text\" ? event : deserializeEvent(event, serializer);\n emit(\"message\", raw as never);\n };\n\n ws.onerror = (event) => {\n emit(\"error\", event);\n };\n\n ws.onclose = (event) => {\n connected = false;\n emit(\"close\", event);\n logger.info(t(\"info.ws.close\", name, String(event.code)));\n if (!stopped) scheduleReconnect();\n };\n };\n\n connect();\n\n return withDispose({\n get connected() {\n return connected;\n },\n opened: opened.promise,\n closed: closed.promise,\n send(data: unknown) {\n if (stopped) {\n throw new SnailRequestError(\n t(\"error.request.failed\", name, \"cannot send on a closed WebSocket\")\n );\n }\n if (!connected) {\n if (!queueWhileConnecting) {\n throw new SnailRequestError(\n t(\"error.request.failed\", name, \"WebSocket is not open yet\")\n );\n }\n queue.push(data);\n return;\n }\n socket!.send(serializer.serialize(data));\n },\n close() {\n if (stopped) return;\n stopped = true;\n if (timer) clearTimeout(timer);\n queue = [];\n connected = false;\n try {\n socket?.close(1000, \"client closed\");\n } catch {\n /* a socket that never opened throws on close; nothing to do */\n }\n closed.resolve();\n }\n });\n}\n\ninterface WsSerializer {\n serialize: (value: unknown) => string;\n deserialize: (raw: string) => unknown;\n}\n\nfunction normalizeSerializer(\n input: SnailWsOptions[\"serializer\"]\n): WsSerializer {\n if (!input || input === \"json\") {\n return {\n serialize: (value) => (typeof value === \"string\" ? value : JSON.stringify(value)),\n deserialize: (raw) => {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n }\n };\n }\n\n if (input === \"text\") {\n return { serialize: (value) => String(value), deserialize: (raw) => raw };\n }\n\n return {\n serialize: (value) => String(input.serialize?.(value) ?? value),\n deserialize: (raw) => input.deserialize?.(raw) ?? raw\n };\n}\n\n/** Decode a `MessageEvent` into a plain value for the handlers. */\nfunction deserializeEvent(event: MessageEvent, serializer: WsSerializer): MessageEvent {\n if (typeof event.data !== \"string\") return event;\n\n const decoded = serializer.deserialize(event.data);\n // Handlers receive a `MessageEvent`-shaped object so `event.data` keeps\n // working, while JSON payloads arrive already parsed.\n return { ...event, data: decoded } as MessageEvent;\n}\n","import axios from \"axios\";\nimport { AxiosHeaders } from \"axios\";\nimport type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from \"axios\";\nimport { SnailDecoratorError } from \"../error/decorator\";\nimport type { SnailApiProxy, SnailMethodOptions, SnailMethodType } from \"../typings/api\";\nimport type { SnailParamDescriptor } from \"../typings/args\";\nimport type { SnailPlugin, SnailPluginObject } from \"../typings/plugin\";\nimport type { SnailEnvelopeSchema } from \"../typings/response\";\nimport type {\n SnailHttpStreamOptions,\n SnailSseEndpoint,\n SnailWsEndpoint\n} from \"../typings/stream\";\nimport type { ResolvedServerOptions } from \"../typings/server\";\nimport { buildRequestURL } from \"../utils/url\";\nimport { applyParamDescriptors, finalizeRequestURL } from \"./args\";\nimport { SnailContext } from \"./context\";\nimport { createHttpStream } from \"./http-stream\";\nimport { createLogger } from \"./logger\";\nimport { SNAIL_PARAMS } from \"./metadata.keys\";\nimport { getMetadata } from \"./metadata\";\nimport { SnailMethod } from \"./method\";\nimport { PluginManager } from \"./plugin-manager\";\nimport {\n buildBaseRequestConfig,\n resolveApiOptions,\n resolveHeaders,\n resolveMethodDecoratorOptions,\n resolveProgress,\n resolveRoute,\n resolveServerOptions\n} from \"./resolve\";\nimport {\n rebindSseHandlers,\n rebindWsHandlers,\n resolveHttpStreamEndpoint,\n resolveSseEndpoint,\n resolveWsEndpoint,\n toWebSocketURL\n} from \"./resolve-stream\";\nimport { createSseConnection } from \"./sse\";\nimport { createWsConnection } from \"./websocket\";\n\n/**\n * The server base class.\n *\n * Extend it, decorate the subclass with `@Server(...)` and instantiate once:\n *\n * ```ts\n * @Server({ baseURL: \"/api\", timeout: 5000 })\n * class BackEnd extends SnailServer {}\n *\n * export const Service = new BackEnd();\n * ```\n *\n * The instance owns an axios instance, a plugin registry and the resolved\n * options. It is created eagerly at module load, so `use()` is synchronous and\n * chainable: `Service.use(A()).use(B())`.\n */\nexport class SnailServer<\n ServerResponse = SnailEnvelopeSchema,\n DataKey extends string = \"data\",\n CodeKey extends string = \"code\",\n MessageKey extends string = \"message\"\n> {\n /** Server name — `@Server({ name })` or the subclass name. */\n readonly name: string;\n\n /** Fully resolved options, defaults applied. */\n readonly options: ResolvedServerOptions;\n\n /** The axios instance every request of this server goes through. */\n readonly axios: AxiosInstance;\n\n /** This server's plugin registry. */\n readonly pluginManager: PluginManager;\n\n private readonly logger;\n private readonly apiCache = new WeakMap<object, unknown>();\n\n constructor() {\n const serverClass = this.constructor as new () => unknown;\n this.options = resolveServerOptions(serverClass, serverClass.name);\n this.name = this.options.name;\n this.logger = createLogger(this.options.logLevel);\n\n // A bare instance on purpose: every option travels on the per-request config\n // built by `buildBaseRequestConfig`, so there is exactly one place where\n // defaults are applied instead of two competing layers.\n this.axios = axios.create();\n this.pluginManager = new PluginManager(this.name, this.options);\n\n // `configureServer` runs once, after the manager exists, so a plugin may\n // still register another plugin from inside it.\n for (const { hook } of this.pluginManager.hooks(\"configureServer\")) {\n (hook as (options: ResolvedServerOptions) => void)(this.options);\n }\n }\n\n /**\n * Register a plugin.\n *\n * Synchronous and chainable. Validation (name, duplicates, `dependsOn`) throws\n * immediately; an async `install` hook is awaited once, before the first\n * request, via the manager's `ready` promise.\n */\n use(plugin: SnailPluginObject<any> | SnailPlugin<any>): this {\n const instance = typeof plugin === \"function\" ? plugin() : plugin;\n this.pluginManager.register(instance);\n return this;\n }\n\n /** Unregister a plugin by instance or by name. */\n async remove(plugin: SnailPluginObject<any> | string): Promise<boolean> {\n const name = typeof plugin === \"string\" ? plugin : plugin?.name;\n if (!name || !this.pluginManager.has(name)) return false;\n await this.pluginManager.remove(name);\n return true;\n }\n\n /** `true` when a plugin with this name is registered. */\n hasPlugin(name: string): boolean {\n return this.pluginManager.has(name);\n }\n\n /** Registered plugin names, in chain order. */\n get plugins(): readonly string[] {\n return this.pluginManager.names();\n }\n\n /**\n * Turn a decorated api class into a proxy whose methods build requests.\n *\n * ```ts\n * @Api(\"/user\")\n * class UserApi {\n * @Get(\"/:id\")\n * getUser(@Params(\"id\") id: string): Promise<User> { return null!; }\n * }\n *\n * export const userApi = Service.createApi(UserApi);\n * const method = userApi.getUser(\"1\"); // nothing sent yet\n * const { data } = await method.send();\n * ```\n *\n * A declared return type — `Promise<User>` above — becomes the payload type,\n * so `data` is `User` with no explicit generic.\n */\n createApi<TClass extends new (...args: any[]) => object>(\n apiClass: TClass\n ): SnailApiProxy<InstanceType<TClass>, ServerResponse, DataKey, CodeKey, MessageKey> {\n const cached = this.apiCache.get(apiClass);\n if (cached) {\n return cached as SnailApiProxy<\n InstanceType<TClass>,\n ServerResponse,\n DataKey,\n CodeKey,\n MessageKey\n >;\n }\n\n const apiOptions = resolveApiOptions(apiClass, apiClass.name);\n for (const { hook } of this.pluginManager.hooks(\"configureApi\")) {\n (hook as (options: typeof apiOptions, target: unknown) => void)(apiOptions, apiClass);\n }\n\n const instance = new apiClass();\n // Method descriptions are resolved once per api class and cached: resolving\n // them on every property access would re-run `configureMethod` per *call*\n // rather than per method, and a plugin that mutates method options would\n // then apply its change once per request.\n const descriptors = new Map<string, MethodDescriptor | null>();\n const descriptorFor = (methodName: string): MethodDescriptor | null => {\n if (descriptors.has(methodName)) return descriptors.get(methodName)!;\n const resolved = this.resolveMethodDescriptor(apiClass, apiOptions, methodName);\n descriptors.set(methodName, resolved);\n return resolved;\n };\n\n const proxied = new Proxy(instance, {\n get: (target, propertyKey, receiver) => {\n const value = Reflect.get(target, propertyKey, receiver);\n\n if (typeof propertyKey === \"symbol\" || typeof value !== \"function\") {\n return value;\n }\n\n const descriptor = descriptorFor(String(propertyKey));\n if (!descriptor) {\n // Not a decorated request method — hand back the original so an api\n // class may still hold helpers next to its endpoints.\n return value;\n }\n\n if (descriptor.kind === \"stream\") {\n // `@HttpStream` methods return a stream controller rather than a request\n // object: a stream has no envelope to validate or cache, so it never\n // enters the plugin pipeline.\n return (...args: unknown[]) =>\n this.createHttpStream(apiClass, apiOptions, descriptor, args);\n }\n\n return (...args: unknown[]) =>\n this.createMethod(apiClass, target, apiOptions, descriptor, args);\n }\n });\n\n this.apiCache.set(apiClass, proxied);\n return proxied as unknown as SnailApiProxy<\n InstanceType<TClass>,\n ServerResponse,\n DataKey,\n CodeKey,\n MessageKey\n >;\n }\n\n /**\n * Turn a class decorated with `@Sse(...)` into an endpoint factory.\n *\n * ```ts\n * @Sse(\"/events\")\n * class Events { @SseEvent() onMessage(m: SnailSseMessage) {} }\n *\n * const events = Service.createSse(Events);\n * const connection = events.open();\n * ```\n */\n createSse<TClass extends new (...args: any[]) => object>(\n sseClass: TClass\n ): SnailSseEndpoint {\n const endpoint = resolveSseEndpoint(sseClass);\n if (!endpoint) {\n throw new SnailDecoratorError(\n `[snail] ${sseClass.name} is missing the @Sse() decorator`\n );\n }\n\n const instance = new sseClass();\n\n return {\n open: () =>\n createSseConnection({\n url: buildRequestURL(this.options.baseURL, endpoint.url),\n options: endpoint.options,\n handlers: rebindSseHandlers(endpoint.handlers, instance),\n name: `${this.options.name}.${sseClass.name}`,\n logger: this.logger\n })\n };\n }\n\n /**\n * Turn a class decorated with `@WebSocket(...)` into an endpoint factory.\n *\n * ```ts\n * @WebSocket(\"/ws\")\n * class Chat { @OnWsMessage() incoming(event: MessageEvent) {} }\n *\n * const chat = Service.createWebSocket(Chat);\n * const socket = chat.open();\n * socket.send({ hello: \"world\" });\n * ```\n */\n createWebSocket<TClass extends new (...args: any[]) => object>(\n wsClass: TClass\n ): SnailWsEndpoint {\n const endpoint = resolveWsEndpoint(wsClass);\n if (!endpoint) {\n throw new SnailDecoratorError(\n `[snail] ${wsClass.name} is missing the @WebSocket() decorator`\n );\n }\n\n const instance = new wsClass();\n\n return {\n open: () =>\n createWsConnection({\n url: toWebSocketURL(buildRequestURL(this.options.baseURL, endpoint.url)),\n options: endpoint.options,\n handlers: rebindWsHandlers(endpoint.handlers, instance),\n name: `${this.options.name}.${wsClass.name}`,\n logger: this.logger\n })\n };\n }\n\n /**\n * Send a one-off request that is **not** backed by a decorated api class.\n *\n * This is an escape hatch, and it is deliberately thin: it awaits plugin\n * installation and then calls the axios instance directly. **No lifecycle hook\n * runs** — no `beforeRequest`, no `afterResponse`, no caching, no validation, no\n * response transformation — and the envelope is *not* unwrapped, so you get the\n * raw `AxiosResponse` back.\n *\n * Prefer a decorated api method for anything that should participate in the\n * plugin pipeline. Reach for this only for a call that genuinely has no place in\n * a service definition, such as a health check against a third party.\n */\n async request<T = unknown, R = AxiosResponse<T>>(\n config: AxiosRequestConfig\n ): Promise<R> {\n await this.pluginManager.ready;\n const merged: AxiosRequestConfig = {\n baseURL: this.options.baseURL,\n timeout: this.options.timeout,\n ...config\n };\n return (await this.axios.request(merged)) as unknown as R;\n }\n\n /** Uninstall every plugin, running their cleanup hooks. */\n async dispose(): Promise<void> {\n await this.pluginManager.clear();\n }\n\n /** Metadata helper for tooling and tests. */\n describe(): Record<string, unknown> {\n return {\n name: this.name,\n baseURL: this.options.baseURL,\n timeout: this.options.timeout,\n codeKey: this.options.codeKey,\n messageKey: this.options.messageKey,\n dataKey: this.options.dataKey,\n logLevel: this.options.logLevel,\n plugins: this.pluginManager.list().map((entry) => ({\n name: entry.name,\n priority: entry.priority\n }))\n };\n }\n\n // ── internals ─────────────────────────────────────────────────────────────\n\n /**\n * Resolve everything static about one decorated method.\n *\n * Called once per method per api class. Returns `null` for a member that is not\n * a decorated endpoint, which is how an api class may keep plain helpers next to\n * its requests.\n */\n private resolveMethodDescriptor(\n apiClass: new () => object,\n apiOptions: ReturnType<typeof resolveApiOptions>,\n methodName: string\n ): MethodDescriptor | null {\n const streamEndpoint = resolveHttpStreamEndpoint(apiClass, methodName);\n if (streamEndpoint) {\n const methodType = (streamEndpoint.options.method ?? \"POST\").toUpperCase() as SnailMethodType;\n return {\n kind: \"stream\",\n methodName,\n methodType,\n route: resolveRoute(apiOptions.url, streamEndpoint.url),\n streamOptions: streamEndpoint.options,\n headers: resolveHeaders(apiClass, methodName),\n descriptors:\n getMetadata<SnailParamDescriptor[]>(SNAIL_PARAMS, apiClass, methodName) ?? []\n };\n }\n\n const methodOptions = resolveMethodDecoratorOptions(apiClass, methodName);\n if (!methodOptions) return null;\n\n const methodType = methodOptions.method;\n const progress = resolveProgress(apiClass, methodName);\n const headers = resolveHeaders(apiClass, methodName);\n const route = resolveRoute(apiOptions.url, methodOptions.url ?? \"\");\n\n const resolvedMethodOptions = {\n ...methodOptions,\n url: route,\n onUploadProgress:\n (methodOptions.onUploadProgress as never) ?? (progress.onUploadProgress as never),\n onDownloadProgress:\n (methodOptions.onDownloadProgress as never) ??\n (progress.onDownloadProgress as never)\n };\n\n for (const { hook } of this.pluginManager.hooks(\"configureMethod\")) {\n (\n hook as (\n options: typeof resolvedMethodOptions,\n name: string,\n apiName: string\n ) => void\n )(resolvedMethodOptions, methodName, apiOptions.name);\n }\n\n return {\n kind: \"request\",\n methodName,\n methodType,\n route,\n methodOptions: resolvedMethodOptions,\n headers,\n descriptors:\n getMetadata<SnailParamDescriptor[]>(SNAIL_PARAMS, apiClass, methodName) ?? []\n };\n }\n\n /**\n * Build the stream controller behind an `@HttpStream` method.\n *\n * A real context is constructed rather than a look-alike, so the `@Query()` /\n * `@Data()` / `@HeaderValue()` resolvers behave exactly as they do for a normal\n * request. The plugin pipeline is deliberately skipped: a byte stream has no\n * envelope to cache or validate.\n */\n private createHttpStream(\n apiClass: new () => object,\n apiOptions: ReturnType<typeof resolveApiOptions>,\n descriptor: Extract<MethodDescriptor, { kind: \"stream\" }>,\n args: unknown[]\n ): ReturnType<typeof createHttpStream> {\n const streamOptions = descriptor.streamOptions;\n const methodOptions = {\n ...streamOptions,\n url: descriptor.route,\n method: descriptor.methodType\n };\n\n const config = buildBaseRequestConfig({\n serverOptions: this.options,\n apiOptions,\n methodOptions: methodOptions as never,\n methodType: descriptor.methodType,\n headers: descriptor.headers\n });\n config.url = descriptor.route;\n\n const ctx = new SnailContext({\n server: this as unknown as SnailServer<any, any, any, any>,\n serverOptions: this.options,\n apiClass,\n api: undefined,\n apiName: apiOptions.name || apiClass.name,\n apiOptions,\n methodName: descriptor.methodName,\n methodType: descriptor.methodType,\n route: descriptor.route,\n request: config,\n descriptors: descriptor.descriptors,\n logger: this.logger\n });\n\n applyParamDescriptors(ctx, args);\n finalizeRequestURL(ctx);\n\n const headers = Object.fromEntries(\n Object.entries(ctx.request.headers.toJSON()).map(([key, value]) => [key, String(value)])\n );\n\n return createHttpStream({\n url: buildRequestURL(this.options.baseURL, ctx.request.url ?? descriptor.route),\n options: { ...streamOptions, method: descriptor.methodType },\n body: ctx.request.data,\n name: ctx.fullName,\n headers,\n logger: this.logger\n });\n }\n\n /** Construct the request object a proxied method call returns. */\n private createMethod(\n apiClass: new () => object,\n api: object,\n apiOptions: ReturnType<typeof resolveApiOptions>,\n descriptor: Extract<MethodDescriptor, { kind: \"request\" }>,\n args: unknown[]\n ): SnailMethod<any, any, any, any, any> {\n const { methodName, methodType, route, methodOptions, headers, descriptors } = descriptor;\n\n return new SnailMethod(\n {\n server: this as unknown as SnailServer<any, any, any, any>,\n pluginManager: this.pluginManager,\n axios: this.axios,\n apiClass,\n api,\n apiName: apiOptions.name || apiClass.name,\n apiOptions,\n serverOptions: this.options,\n methodName,\n methodType,\n route,\n methodOptions,\n descriptors,\n headers,\n logger: this.logger,\n requestConfig: () =>\n buildBaseRequestConfig({\n serverOptions: this.options,\n apiOptions,\n methodOptions,\n methodType,\n headers\n })\n },\n args\n );\n }\n}\n\n/** Everything static about one decorated member, resolved once per api class. */\ntype MethodDescriptor =\n | {\n kind: \"request\";\n methodName: string;\n methodType: SnailMethodType;\n route: string;\n methodOptions: SnailMethodOptions & { url: string };\n headers: AxiosHeaders;\n descriptors: readonly SnailParamDescriptor[];\n }\n | {\n kind: \"stream\";\n methodName: string;\n methodType: SnailMethodType;\n route: string;\n streamOptions: SnailHttpStreamOptions;\n headers: AxiosHeaders;\n descriptors: readonly SnailParamDescriptor[];\n };\n","import { SnailDecoratorError } from \"../error/decorator\";\nimport { t } from \"../locale\";\nimport type { SnailApiOptions } from \"../typings/api\";\nimport { defineMetadata, getOwnMetadata } from \"../core/metadata\";\nimport { SNAIL_API_OPTIONS } from \"../core/metadata.keys\";\n\n/**\n * Declare an api class.\n *\n * ```ts\n * @Api(\"/user\")\n * class UserApi {\n * @Get(\"/:id\")\n * getUser(@Params(\"id\") id: string): Promise<User> { return null!; }\n * }\n * ```\n *\n * The url is a *prefix*: it is joined with the server `baseURL` and the method\n * path, in that order.\n *\n * Always declare `@Api()` even when there is no prefix. It is what names the api\n * for logging, cache namespacing and `@HitSource` targets; without it those fall\n * back to the class name. The library does **not** enforce it at runtime —\n * inherited api classes are legitimate and a base class may carry the decorator —\n * so a missing `@Api()` silently degrades rather than throwing.\n */\nexport function Api(url?: string): ClassDecorator;\nexport function Api(options: SnailApiOptions): ClassDecorator;\nexport function Api(urlOrOptions?: string | SnailApiOptions): ClassDecorator {\n const options: SnailApiOptions =\n typeof urlOrOptions === \"string\" || urlOrOptions === undefined\n ? { url: urlOrOptions ?? \"\" }\n : urlOrOptions;\n\n if (options.url !== undefined && typeof options.url !== \"string\") {\n throw new SnailDecoratorError(t(\"error.options.api.url\"));\n }\n\n return (target) => {\n if (typeof target !== \"function\") {\n throw new SnailDecoratorError(t(\"error.decorator.class.target\", \"Api\"));\n }\n\n const previous = getOwnMetadata<SnailApiOptions>(SNAIL_API_OPTIONS, target);\n\n defineMetadata(\n SNAIL_API_OPTIONS,\n previous ? { ...previous, ...options } : { ...options },\n target\n );\n };\n}\n","import { SnailDecoratorError } from \"../error/decorator\";\nimport { t } from \"../locale\";\nimport type { SnailServerOptions } from \"../typings/server\";\nimport { defineMetadata, getOwnMetadata } from \"../core/metadata\";\nimport { SNAIL_SERVER_OPTIONS } from \"../core/metadata.keys\";\n\n/**\n * Declare the options of a server class.\n *\n * ```ts\n * @Server({ baseURL: \"/api\", timeout: 5000 })\n * class BackEnd extends SnailServer {}\n * ```\n *\n * The shorthand form is equivalent to `{ baseURL }`:\n *\n * ```ts\n * @Server(\"/api\")\n * class BackEnd extends SnailServer {}\n * ```\n *\n * Applying `@Server` twice merges the options, and the **outermost decorator\n * wins** — decorators are evaluated bottom-up, so the one written furthest from\n * the class is applied last:\n *\n * ```ts\n * @Server({ timeout: 30000 }) // applied last → this timeout wins\n * @Server({ baseURL: \"/api\" })\n * class BackEnd extends SnailServer {}\n * // → { baseURL: \"/api\", timeout: 30000 }\n * ```\n */\nexport function Server(baseURL: string): ClassDecorator;\nexport function Server(options: SnailServerOptions): ClassDecorator;\nexport function Server(baseURLOrOptions: string | SnailServerOptions): ClassDecorator {\n const options: SnailServerOptions =\n typeof baseURLOrOptions === \"string\"\n ? { baseURL: baseURLOrOptions }\n : baseURLOrOptions;\n\n return (target) => {\n if (typeof target !== \"function\") {\n throw new SnailDecoratorError(t(\"error.decorator.class.target\", \"Server\"));\n }\n\n const previous = getOwnMetadata<SnailServerOptions>(\n SNAIL_SERVER_OPTIONS,\n target\n );\n\n defineMetadata(\n SNAIL_SERVER_OPTIONS,\n previous ? { ...previous, ...options } : { ...options },\n target\n );\n };\n}\n","import { SnailDecoratorError } from \"../error/decorator\";\nimport { t } from \"../locale\";\nimport type {\n SnailMethodDecoratorOptions,\n SnailMethodType\n} from \"../typings/api\";\nimport { defineMetadata, getOwnMetadata } from \"../core/metadata\";\nimport { SNAIL_REQUEST_METHOD } from \"../core/metadata.keys\";\n\n/** Options accepted by every request-method decorator. */\nexport interface RequestMethodOptions extends SnailMethodDecoratorOptions {}\n\n/**\n * Build one request-method decorator.\n *\n * Decorated methods are never executed: `createApi` replaces them with a factory\n * that returns a request object. Their body exists purely to declare the argument\n * and return types, so `return null!` (or nothing at all) is the convention.\n *\n * Applying two request-method decorators to one method is an error and throws\n * while the class is being defined, which is the earliest possible moment.\n */\nfunction createRequestMethod(method: SnailMethodType) {\n return (\n path = \"\",\n options: RequestMethodOptions = {}\n ): MethodDecorator => {\n if (typeof path !== \"string\") {\n throw new SnailDecoratorError(\n `@${method} expects its first argument to be a string path`\n );\n }\n\n return (target, propertyKey) => {\n if (propertyKey === undefined) {\n throw new SnailDecoratorError(\n `@${method} must decorate a method, not a class or a property`\n );\n }\n\n const existing = getOwnMetadata<{ method: SnailMethodType }>(\n SNAIL_REQUEST_METHOD,\n target,\n propertyKey\n );\n\n if (existing) {\n throw new SnailDecoratorError(\n t(\"error.decorator.method.duplicate\", String(propertyKey))\n );\n }\n\n defineMetadata(\n SNAIL_REQUEST_METHOD,\n { ...options, method, url: path },\n target,\n propertyKey\n );\n };\n };\n}\n\n/** `GET` request. */\nexport const Get = createRequestMethod(\"GET\");\n/** `POST` request. */\nexport const Post = createRequestMethod(\"POST\");\n/** `PUT` request. */\nexport const Put = createRequestMethod(\"PUT\");\n/** `DELETE` request. */\nexport const Delete = createRequestMethod(\"DELETE\");\n/** `PATCH` request. */\nexport const Patch = createRequestMethod(\"PATCH\");\n/** `HEAD` request. */\nexport const Head = createRequestMethod(\"HEAD\");\n/** `OPTIONS` request. */\nexport const Options = createRequestMethod(\"OPTIONS\");\n\n/** Alias kept for symmetry with `axios.request` style naming. */\nexport const Request = createRequestMethod;\n","import { mergeMetadata } from \"../core/metadata\";\nimport { SNAIL_HEADERS } from \"../core/metadata.keys\";\n\n/**\n * Static request headers.\n *\n * Works on an api class and on a single method; the two levels merge, with the\n * method winning:\n *\n * ```ts\n * @Api(\"/user\")\n * @Header({ \"x-client\": \"web\" })\n * class UserApi {\n * @Get(\"/secret\")\n * @Header({ \"x-scope\": \"admin\" })\n * secret() {}\n * }\n * // → x-client: web, x-scope: admin\n * ```\n *\n * For a header whose value comes from an argument, use the `@HeaderValue()`\n * parameter decorator.\n */\nexport function Header(record: Record<string, unknown>): ClassDecorator & MethodDecorator {\n return ((target: any, propertyKey?: string | symbol) => {\n mergeMetadata(SNAIL_HEADERS, record, target, propertyKey);\n }) as ClassDecorator & MethodDecorator;\n}\n","import type { AxiosProgressEvent } from \"axios\";\nimport { defineMetadata } from \"../core/metadata\";\nimport {\n SNAIL_DOWNLOAD_PROGRESS,\n SNAIL_UPLOAD_PROGRESS\n} from \"../core/metadata.keys\";\n\n/** Signature of an upload/download progress callback. */\nexport type SnailProgressCallback = (event: AxiosProgressEvent) => void;\n\n/**\n * Report upload progress for the decorated method.\n *\n * ```ts\n * @Post(\"/upload\")\n * @UploadProgress((e) => { if (e.total) bar.value = e.loaded / e.total; })\n * upload(@Data() file: FormData) {}\n * ```\n *\n * Progress events require the `xhr` adapter; axios' `fetch` adapter cannot\n * report them. Set `adapter: \"xhr\"` on the method or the server when using this\n * decorator in a browser.\n */\nexport function UploadProgress(callback: SnailProgressCallback): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(SNAIL_UPLOAD_PROGRESS, callback, target, propertyKey);\n };\n}\n\n/**\n * Report download progress for the decorated method.\n *\n * @see {@link UploadProgress} for the adapter caveat.\n */\nexport function DownloadProgress(callback: SnailProgressCallback): MethodDecorator {\n return (target, propertyKey) => {\n defineMetadata(SNAIL_DOWNLOAD_PROGRESS, callback, target, propertyKey);\n };\n}\n","import { SnailDecoratorError } from \"../error/decorator\";\nimport { t } from \"../locale\";\nimport type {\n SnailHttpStreamOptions,\n SnailSseOptions,\n SnailWsOptions\n} from \"../typings/stream\";\nimport { defineMetadata, getOwnMetadata, mergeMetadata } from \"../core/metadata\";\nimport {\n SNAIL_HTTP_STREAM,\n SNAIL_SSE_HANDLERS,\n SNAIL_SSE_OPTIONS,\n SNAIL_WS_HANDLERS,\n SNAIL_WS_OPTIONS\n} from \"../core/metadata.keys\";\nimport type { SnailMethodType } from \"../typings/api\";\n\n/**\n * Streaming / realtime decorators.\n *\n * Three transports, one idea: the decorated members only *describe* handlers,\n * and `Service.createSse(...)` / `createWebSocket(...)` / a `@HttpStream` method\n * turn them into a live connection.\n */\n\n// ── SSE ─────────────────────────────────────────────────────────────────────\n\n/** Handler names recorded for an SSE class. */\nexport interface SnailSseHandlers {\n open: Array<(event: Event) => void>;\n error: Array<(event: Event) => void>;\n events: Array<{ event: string; handler: (message: unknown) => void }>;\n}\n\n/**\n * Declare a Server-Sent Events endpoint.\n *\n * ```ts\n * @Sse(\"/events\")\n * class Ticker {\n * @OnSseOpen()\n * open() { console.log(\"connected\"); }\n *\n * @OnSseError()\n * failed(event: Event) { console.warn(\"lost\", event); }\n *\n * @SseEvent() // the default `message` event\n * message(message: SnailSseMessage) {}\n *\n * @SseEvent(\"tick\")\n * tick(message: SnailSseMessage) {}\n * }\n *\n * const ticker = Service.createSse(Ticker);\n * const connection = ticker.open();\n *\n * // Decorate with @SseEvent, or subscribe on the connection itself:\n * const off = connection.on(\"tick\", (message) => console.log(message.data));\n * connection.close();\n * ```\n *\n * The transport is `fetch` + a stream reader rather than `EventSource`: that is\n * what makes request headers, `POST` and `withCredentials` possible, none of which\n * `EventSource` supports.\n */\nexport function Sse(path: string, options: SnailSseOptions = {}): ClassDecorator {\n return (target) => {\n if (typeof target !== \"function\") {\n throw new SnailDecoratorError(t(\"error.decorator.class.target\", \"Sse\"));\n }\n if (getOwnMetadata(SNAIL_WS_OPTIONS, target)) {\n throw new SnailDecoratorError(\n t(\"error.decorator.stream.duplicate\", target.name)\n );\n }\n mergeMetadata(SNAIL_SSE_OPTIONS, { ...options, url: path }, target);\n };\n}\n\n/** Register a handler for the SSE `open` event. */\nexport function OnSseOpen(): MethodDecorator {\n return (target, propertyKey) => {\n appendSseHandler(target, propertyKey, (handlers, fn) => handlers.open.push(fn));\n };\n}\n\n/** Register a handler for the SSE `error` event. */\nexport function OnSseError(): MethodDecorator {\n return (target, propertyKey) => {\n appendSseHandler(target, propertyKey, (handlers, fn) => handlers.error.push(fn));\n };\n}\n\n/**\n * Register a handler for a named SSE event.\n *\n * @param event event name; omit for the default `message` event\n */\nexport function SseEvent(event = \"message\"): MethodDecorator {\n return (target, propertyKey) => {\n appendSseHandler(target, propertyKey, (handlers, fn) => {\n handlers.events.push({ event, handler: fn });\n });\n };\n}\n\nfunction appendSseHandler(\n target: any,\n propertyKey: string | symbol | undefined,\n push: (handlers: SnailSseHandlers, fn: any) => void\n): void {\n if (propertyKey === undefined) {\n throw new SnailDecoratorError(\"[snail] SSE handler decorators must be used on methods\");\n }\n const fn = target[propertyKey];\n if (typeof fn !== \"function\") {\n throw new SnailDecoratorError(\n `[snail] @SseEvent/@OnSseOpen/@OnSseError must decorate a method, got \"${String(propertyKey)}\"`\n );\n }\n\n const handlers: SnailSseHandlers =\n getOwnMetadata<SnailSseHandlers>(SNAIL_SSE_HANDLERS, target) ?? {\n open: [],\n error: [],\n events: []\n };\n\n // Stored unbound: the decorator only sees the prototype, and binding there\n // would make `this` the prototype instead of the instance. `createSse` binds\n // each handler to the instance it creates.\n push(handlers, fn);\n defineMetadata(SNAIL_SSE_HANDLERS, handlers, target);\n}\n\n// ── WebSocket ───────────────────────────────────────────────────────────────\n\n/** Handler names recorded for a WebSocket class. */\nexport interface SnailWsHandlers {\n open: Array<(event: Event) => void>;\n message: Array<(event: MessageEvent) => void>;\n close: Array<(event: CloseEvent) => void>;\n error: Array<(event: Event) => void>;\n}\n\n/**\n * Declare a WebSocket endpoint.\n *\n * ```ts\n * @WebSocket(\"/ws\")\n * class ChatSocket {\n * @OnWsOpen() connected() {}\n * @OnWsMessage() incoming(event: MessageEvent) {}\n * @OnWsClose() gone(event: CloseEvent) {}\n * @OnWsError() failed(event: Event) {}\n * }\n *\n * const chat = Service.createWebSocket(ChatSocket);\n * const socket = chat.open();\n * socket.send({ hello: \"world\" });\n * ```\n */\nexport function WebSocket(path: string, options: SnailWsOptions = {}): ClassDecorator {\n return (target) => {\n if (typeof target !== \"function\") {\n throw new SnailDecoratorError(t(\"error.decorator.class.target\", \"WebSocket\"));\n }\n if (getOwnMetadata(SNAIL_SSE_OPTIONS, target)) {\n throw new SnailDecoratorError(\n t(\"error.decorator.stream.duplicate\", target.name)\n );\n }\n mergeMetadata(SNAIL_WS_OPTIONS, { ...options, url: path }, target);\n };\n}\n\n/** Register a handler for the socket `open` event. */\nexport function OnWsOpen(): MethodDecorator {\n return wsHandlerDecorator(\"open\");\n}\n\n/** Register a handler for incoming messages. */\nexport function OnWsMessage(): MethodDecorator {\n return wsHandlerDecorator(\"message\");\n}\n\n/** Register a handler for the socket `close` event. */\nexport function OnWsClose(): MethodDecorator {\n return wsHandlerDecorator(\"close\");\n}\n\n/** Register a handler for the socket `error` event. */\nexport function OnWsError(): MethodDecorator {\n return wsHandlerDecorator(\"error\");\n}\n\nfunction wsHandlerDecorator(\n kind: keyof SnailWsHandlers\n): MethodDecorator {\n return (target, propertyKey) => {\n if (propertyKey === undefined) {\n throw new SnailDecoratorError(\"[snail] WebSocket handler decorators must be used on methods\");\n }\n const fn = (target as Record<string | symbol, unknown>)[propertyKey];\n if (typeof fn !== \"function\") {\n throw new SnailDecoratorError(\n `[snail] @OnWs* must decorate a method, got \"${String(propertyKey)}\"`\n );\n }\n\n const handlers: SnailWsHandlers =\n getOwnMetadata<SnailWsHandlers>(SNAIL_WS_HANDLERS, target) ?? {\n open: [],\n message: [],\n close: [],\n error: []\n };\n\n // Stored unbound, for the reason given in `appendSseHandler`.\n (handlers[kind] as Array<unknown>).push(fn);\n defineMetadata(SNAIL_WS_HANDLERS, handlers, target);\n };\n}\n\n/** Short alias for {@link WebSocket}. */\nexport const Ws = WebSocket;\n\n// ── HTTP stream ─────────────────────────────────────────────────────────────\n\n/**\n * Declare a streaming HTTP endpoint.\n *\n * ```ts\n * @Api(\"/ai\")\n * class AiApi {\n * @HttpStream(\"/chat\", { method: \"POST\" })\n * chat(@Data() prompt: { text: string }) {}\n * }\n *\n * const stream = aiApi.chat({ text: \"hi\" });\n * for await (const chunk of stream.stream()) { render(chunk); }\n * ```\n *\n * Unlike `@Get`/`@Post`, the proxied method returns a stream controller instead\n * of a `SnailMethod`, so no envelope validation happens.\n */\nexport function HttpStream(\n path = \"\",\n options: SnailHttpStreamOptions = {}\n): MethodDecorator {\n return (target, propertyKey) => {\n if (propertyKey === undefined) {\n throw new SnailDecoratorError(\"[snail] @HttpStream must decorate a method\");\n }\n defineMetadata(\n SNAIL_HTTP_STREAM,\n { ...options, url: path },\n target,\n propertyKey\n );\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locale/en.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,iCAAiC;AACjC,QAAA,MAAM,EAAE,EAAE,aAuDT,CAAC;eAEa,EAAE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import en from "./en.js";
|
|
2
|
+
import zh from "./zh.js";
|
|
3
|
+
import type { SnailLanguage, SnailLocaleInput, SnailMessages } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tiny message catalogue with `%s` placeholders.
|
|
6
|
+
*
|
|
7
|
+
* A single shared instance backs the module-level `t()` helper, so plugins can
|
|
8
|
+
* contribute their own messages with `registerMessages()`.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Localization {
|
|
11
|
+
private language;
|
|
12
|
+
private catalogue;
|
|
13
|
+
constructor(language?: SnailLocaleInput);
|
|
14
|
+
/** Currently active language tag or catalogue name. */
|
|
15
|
+
get locale(): SnailLanguage;
|
|
16
|
+
/**
|
|
17
|
+
* Switch language.
|
|
18
|
+
*
|
|
19
|
+
* - `"zh"`, `"zh-CN"`, `"en-US"` → pick a shipped catalogue
|
|
20
|
+
* - a message object → merge over the current catalogue (great for plugins
|
|
21
|
+
* and for applications that want their own wording)
|
|
22
|
+
*/
|
|
23
|
+
setLocale(input: SnailLocaleInput): this;
|
|
24
|
+
/** Merge extra messages into the active catalogue. */
|
|
25
|
+
registerMessages(messages: SnailMessages): this;
|
|
26
|
+
/** Current catalogue contents (a snapshot copy). */
|
|
27
|
+
get messages(): SnailMessages;
|
|
28
|
+
/**
|
|
29
|
+
* Translate `key`, replacing each `%s` with the matching extra argument.
|
|
30
|
+
*
|
|
31
|
+
* A missing key returns the key itself rather than an empty string: the old
|
|
32
|
+
* implementation returned `""`, which silently swallowed typos and produced
|
|
33
|
+
* blank error messages.
|
|
34
|
+
*/
|
|
35
|
+
t(key: string, ...args: Array<string | number>): string;
|
|
36
|
+
}
|
|
37
|
+
/** Shared catalogue instance used by the whole library. */
|
|
38
|
+
export declare const localization: Localization;
|
|
39
|
+
/**
|
|
40
|
+
* Translate a message using the shared catalogue.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* t("info.cache.hit", "default.UserApi.list"); // "[…] cache hit"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function t(key: string, ...args: Array<string | number>): string;
|
|
48
|
+
/** Switch the language of the shared catalogue. */
|
|
49
|
+
export declare function setLocale(input: SnailLocaleInput): void;
|
|
50
|
+
/** Read the active language of the shared catalogue. */
|
|
51
|
+
export declare function getLocale(): SnailLanguage;
|
|
52
|
+
/** Contribute extra messages to the shared catalogue (used by plugins). */
|
|
53
|
+
export declare function registerMessages(messages: SnailMessages): void;
|
|
54
|
+
export { en, zh };
|
|
55
|
+
export type { SnailLanguage, SnailLocaleInput, SnailMessages };
|
|
56
|
+
export declare const languages: string[];
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/locale/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA6C9E;;;;;GAKG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAgB;IAEjC,YAAY,QAAQ,CAAC,EAAE,gBAAgB,EAItC;IAED,uDAAuD;IACvD,IAAI,MAAM,IAAI,aAAa,CAE1B;IAED;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAcvC;IAED,sDAAsD;IACtD,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAG9C;IAED,oDAAoD;IACpD,IAAI,QAAQ,IAAI,aAAa,CAE5B;IAED;;;;;;OAMG;IACH,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAStD;CACF;AAED,2DAA2D;AAC3D,eAAO,MAAM,YAAY,cAAqB,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAEtE;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAEvD;AAED,wDAAwD;AACxD,wBAAgB,SAAS,IAAI,aAAa,CAEzC;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAE9D;AAED,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;AAClB,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAC;AAC/D,eAAO,MAAM,SAAS,EAAE,MAAM,EAA0B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Languages shipped with the library. */
|
|
2
|
+
export type SnailLanguage = "zh" | "en" | (string & {});
|
|
3
|
+
/** A message catalogue: dotted key → template containing `%s` placeholders. */
|
|
4
|
+
export type SnailMessages = Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Anything accepted by `setLocale`:
|
|
7
|
+
* - a language tag (`"zh-CN"`, `"en"`) resolved against the built-in catalogues
|
|
8
|
+
* - a full message catalogue, merged over the current one
|
|
9
|
+
*/
|
|
10
|
+
export type SnailLocaleInput = SnailLanguage | SnailMessages;
|
|
11
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/locale/types.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAExD,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zh.d.ts","sourceRoot":"","sources":["../../src/locale/zh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,yCAAyC;AACzC,QAAA,MAAM,EAAE,EAAE,aAuDT,CAAC;eAEa,EAAE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CacheAdapter } from "../type.js";
|
|
2
|
+
/** Options accepted by {@link IndexedDBCacheAdapter}. */
|
|
3
|
+
export interface IndexedDBCacheAdapterOptions {
|
|
4
|
+
/** Database name. Defaults to `"snail-js-api"`. */
|
|
5
|
+
databaseName?: string;
|
|
6
|
+
/** Object store name, and therefore the namespace. Defaults to `"cache"`. */
|
|
7
|
+
storeName?: string;
|
|
8
|
+
/** Schema version, bumped when the store layout changes. */
|
|
9
|
+
version?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* L2 adapter over IndexedDB.
|
|
13
|
+
*
|
|
14
|
+
* ## Why every method degrades instead of rejecting
|
|
15
|
+
*
|
|
16
|
+
* IndexedDB is absent in Node and can be unavailable in private browsing modes.
|
|
17
|
+
* An L2 store is an optimisation: a request must succeed (and stay correct) when
|
|
18
|
+
* it is missing. Every public method therefore resolves to "nothing stored" or a
|
|
19
|
+
* no-op rather than throwing, and the connection is opened lazily so importing
|
|
20
|
+
* this module — or constructing the adapter — never touches a global.
|
|
21
|
+
*
|
|
22
|
+
* ## Why the timeout is stored per record, not as an index
|
|
23
|
+
*
|
|
24
|
+
* A TTL index would make reads of an expired key need a second query anyway. The
|
|
25
|
+
* record carries its own `expiresAt`, which makes the freshness check a pure
|
|
26
|
+
* in-memory comparison on the value already fetched.
|
|
27
|
+
*/
|
|
28
|
+
export declare class IndexedDBCacheAdapter implements CacheAdapter {
|
|
29
|
+
private readonly databaseName;
|
|
30
|
+
private readonly storeName;
|
|
31
|
+
private readonly version;
|
|
32
|
+
private database;
|
|
33
|
+
private opening;
|
|
34
|
+
constructor(options?: IndexedDBCacheAdapterOptions);
|
|
35
|
+
/** `false` when the environment has no IndexedDB at all. */
|
|
36
|
+
get available(): boolean;
|
|
37
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
38
|
+
set(key: string, value: unknown, ttlSeconds: number): Promise<void>;
|
|
39
|
+
delete(key: string): Promise<void>;
|
|
40
|
+
clear(): Promise<void>;
|
|
41
|
+
keys(): Promise<string[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Open (once) and memoise the database connection.
|
|
44
|
+
*
|
|
45
|
+
* The in-flight promise is stored before it settles, so two concurrent
|
|
46
|
+
* requests cannot open the same database twice and leak a connection.
|
|
47
|
+
*/
|
|
48
|
+
private open;
|
|
49
|
+
private read;
|
|
50
|
+
private transaction;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=indexeddb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../../../src/plugins/cache/adapters/indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAS5C,yDAAyD;AACzD,MAAM,WAAW,4BAA4B;IAC3C,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAsB,YAAW,YAAY;IACxD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,OAAO,CAAmC;IAElD,YAAY,OAAO,GAAE,4BAAiC,EAIrD;IAED,4DAA4D;IAC5D,IAAI,SAAS,IAAI,OAAO,CAMvB;IAEK,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAe1D;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxE;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASvC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3B;IAEK,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAU9B;IAID;;;;;OAKG;IACH,OAAO,CAAC,IAAI;YAiCE,IAAI;IASlB,OAAO,CAAC,WAAW;CAapB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { CacheAdapter } from "../type.js";
|
|
2
|
+
/** Options accepted by {@link MemoryCacheAdapter}. */
|
|
3
|
+
export interface MemoryCacheAdapterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Maximum number of live entries. Least-recently-used entries are evicted
|
|
6
|
+
* first.
|
|
7
|
+
*
|
|
8
|
+
* A non-positive or non-finite value means {@link DEFAULT_L1_MAX_SIZE}: an
|
|
9
|
+
* unbounded in-memory store is a leak, so it must not be reachable by accident.
|
|
10
|
+
*/
|
|
11
|
+
maxSize?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Called whenever an entry leaves the store without the owner asking —
|
|
14
|
+
* expiry or capacity eviction.
|
|
15
|
+
*
|
|
16
|
+
* The `CacheManager` uses it to drop the key from its tag index and TTL map;
|
|
17
|
+
* without it, evicted keys would keep their tags forever and
|
|
18
|
+
* `invalidateTags` would keep firing `delete` calls for keys that are gone.
|
|
19
|
+
*/
|
|
20
|
+
onEvict?: (key: string) => void;
|
|
21
|
+
}
|
|
22
|
+
/** L1 capacity used whenever the configured one is missing or not positive. */
|
|
23
|
+
export declare const DEFAULT_L1_MAX_SIZE = 100;
|
|
24
|
+
/**
|
|
25
|
+
* The L1 store: a `Map` with TTL and LRU capacity.
|
|
26
|
+
*
|
|
27
|
+
* ## Why there is no sweep timer
|
|
28
|
+
*
|
|
29
|
+
* The pre-rewrite memory adapter ran a `setInterval` that (a) kept a Node
|
|
30
|
+
* process alive forever and (b) made tests hang until vitest force-killed them.
|
|
31
|
+
* Expiry is therefore swept *lazily*, at the start of every operation: at most
|
|
32
|
+
* `maxSize` records are inspected, which is cheaper than the timer it replaces
|
|
33
|
+
* for any realistic capacity, and it cannot outlive the store.
|
|
34
|
+
*
|
|
35
|
+
* ## Why a `Map` and not a `WeakMap`
|
|
36
|
+
*
|
|
37
|
+
* LRU needs enumeration and ordering. `Map` preserves insertion order, so
|
|
38
|
+
* "least recently used" is simply "first key" and touching an entry is
|
|
39
|
+
* delete + set — no linked list to keep in sync with the data.
|
|
40
|
+
*/
|
|
41
|
+
export declare class MemoryCacheAdapter implements CacheAdapter {
|
|
42
|
+
private readonly records;
|
|
43
|
+
private readonly maxSize;
|
|
44
|
+
private readonly onEvict;
|
|
45
|
+
constructor(options?: MemoryCacheAdapterOptions);
|
|
46
|
+
/** Number of live entries. */
|
|
47
|
+
get size(): number;
|
|
48
|
+
/** `true` when the key is present and still fresh. */
|
|
49
|
+
has(key: string): Promise<boolean>;
|
|
50
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
51
|
+
set(key: string, value: unknown, ttlSeconds: number): Promise<void>;
|
|
52
|
+
delete(key: string): Promise<void>;
|
|
53
|
+
clear(): Promise<void>;
|
|
54
|
+
keys(): Promise<string[]>;
|
|
55
|
+
/** Drop expired records. Called before every operation, never on a timer. */
|
|
56
|
+
private sweep;
|
|
57
|
+
/** Make room for `incoming` new entries by dropping the oldest ones. */
|
|
58
|
+
private evictForCapacity;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../../src/plugins/cache/adapters/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,sDAAsD;AACtD,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,MAAM,CAAC;AASvC;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAE9D,YAAY,OAAO,GAAE,yBAA8B,EAOlD;IAED,8BAA8B;IAC9B,IAAI,IAAI,IAAI,MAAM,CAGjB;IAED,sDAAsD;IAChD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEvC;IAEK,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAY1D;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxE;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAEK,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAG9B;IAED,6EAA6E;IAC7E,OAAO,CAAC,KAAK;IAUb,wEAAwE;IACxE,OAAO,CAAC,gBAAgB;CAQzB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CacheAdapter } from "../type.js";
|
|
2
|
+
/** Options accepted by {@link WebStorageCacheAdapter}. */
|
|
3
|
+
export interface WebStorageCacheAdapterOptions {
|
|
4
|
+
/** Prefix applied to every key, so two adapters can share one storage area. */
|
|
5
|
+
prefix?: string;
|
|
6
|
+
/** Name used in diagnostics, e.g. `"localStorage"`. */
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* L2 adapter over any Web Storage area.
|
|
11
|
+
*
|
|
12
|
+
* One class drives both `localStorage` and `sessionStorage`: they share the
|
|
13
|
+
* `Storage` interface, and wrapping them twice would duplicate every guard below.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the area is resolved through a getter
|
|
16
|
+
*
|
|
17
|
+
* `localStorage` does not exist in Node, and in a browser it *throws* on access
|
|
18
|
+
* when the user has blocked site data. Reading it in the constructor would make
|
|
19
|
+
* `import`-time behaviour environment-dependent — and a Node test could not even
|
|
20
|
+
* construct the adapter to assert that it degrades. The getter is therefore
|
|
21
|
+
* called per operation and its failure is swallowed; the store behaves as an
|
|
22
|
+
* always-empty cache, which the manager turns into "L1 only".
|
|
23
|
+
*/
|
|
24
|
+
export declare class WebStorageCacheAdapter implements CacheAdapter {
|
|
25
|
+
private readonly resolveStorage;
|
|
26
|
+
private readonly prefix;
|
|
27
|
+
/** Name used in diagnostics. */
|
|
28
|
+
readonly label: string;
|
|
29
|
+
constructor(resolveStorage: () => Storage | undefined, options?: WebStorageCacheAdapterOptions);
|
|
30
|
+
/** `false` when the backing area is missing — the manager then drops L2. */
|
|
31
|
+
get available(): boolean;
|
|
32
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
33
|
+
set(key: string, value: unknown, ttlSeconds: number): Promise<void>;
|
|
34
|
+
delete(key: string): Promise<void>;
|
|
35
|
+
clear(): Promise<void>;
|
|
36
|
+
keys(): Promise<string[]>;
|
|
37
|
+
private storage;
|
|
38
|
+
private key;
|
|
39
|
+
/** Every storage key this adapter owns, prefix included. */
|
|
40
|
+
private keysOf;
|
|
41
|
+
private parse;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=web-storage.d.ts.map
|